Glide: try to handle encrypted image. [WIP]

This commit is contained in:
ganfra 2019-06-28 19:31:32 +02:00 committed by Benoit Marty
parent b54ca5a8a0
commit 164c8dab09
3 changed files with 59 additions and 46 deletions

View File

@ -166,15 +166,6 @@ object MXEncryptedAttachments {
return null
}

// detect if there is no data to decrypt
try {
if (0 == attachmentStream.available()) {
return ByteArrayInputStream(ByteArray(0))
}
} catch (e: Exception) {
Timber.e(e, "Fail to retrieve the file size")
}

val t0 = System.currentTimeMillis()

val outStream = ByteArrayOutputStream()

View File

@ -24,14 +24,22 @@ import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.signature.ObjectKey
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
import im.vector.riotredesign.features.media.ImageContentRenderer
import okhttp3.OkHttpClient
import okhttp3.Request
import timber.log.Timber
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import com.bumptech.glide.load.engine.Resource as Resource1

class VectorGlideModelLoaderFactory : ModelLoaderFactory<InputStream, InputStream> {

override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<InputStream, InputStream> {
class VectorGlideModelLoaderFactory : ModelLoaderFactory<ImageContentRenderer.Data, InputStream> {

override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ImageContentRenderer.Data, InputStream> {
return VectorGlideModelLoader()
}

@ -41,25 +49,31 @@ class VectorGlideModelLoaderFactory : ModelLoaderFactory<InputStream, InputStrea

}

class VectorGlideModelLoader : ModelLoader<InputStream, InputStream> {
override fun handles(model: InputStream): Boolean {
class VectorGlideModelLoader : ModelLoader<ImageContentRenderer.Data, InputStream> {
override fun handles(model: ImageContentRenderer.Data): Boolean {
// Always handle
return true
}

override fun buildLoadData(model: InputStream, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? {
return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(model, options.get(ELEMENT_TO_DECRYPT)))
override fun buildLoadData(model: ImageContentRenderer.Data, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? {
return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(model, width, height))
}
}

class VectorGlideDataFetcher(private val inputStream: InputStream,
private val elementToDecrypt: ElementToDecrypt?) : DataFetcher<InputStream> {
class VectorGlideDataFetcher(private val data: ImageContentRenderer.Data,
private val width: Int,
private val height: Int) : DataFetcher<InputStream> {

val client = OkHttpClient()

override fun getDataClass(): Class<InputStream> {
return InputStream::class.java
}

private var stream: InputStream? = null

override fun cleanup() {
// ?
cancel()
}

override fun getDataSource(): DataSource {
@ -68,16 +82,43 @@ class VectorGlideDataFetcher(private val inputStream: InputStream,
}

override fun cancel() {
// ?
if (stream != null) {
try {
stream?.close() // interrupts decode if any
stream = null
} catch (ignore: IOException) {
Timber.e(ignore)
}
}
}

override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
if (elementToDecrypt?.k?.isNotBlank() == true) {
// Encrypted stream
callback.onDataReady(MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt))
} else {
// Not encrypted stream
callback.onDataReady(inputStream)
Timber.v("Load data: $data")
if (data.isLocalFile()) {
val initialFile = File(data.url)
callback.onDataReady(FileInputStream(initialFile))
return
}
val contentUrlResolver = Matrix.getInstance().currentSession?.contentUrlResolver() ?: return
val url = contentUrlResolver.resolveFullSize(data.url)
?: return

val request = Request.Builder()
.url(url)
.build()

val response = client.newCall(request).execute()
val inputStream = response.body()?.byteStream()
Timber.v("Response size ${response.body()?.contentLength()} - Stream available: ${inputStream?.available()}")
if (!response.isSuccessful) {
callback.onLoadFailed(IOException("Unexpected code $response"))
return
}
stream = if (data.elementToDecrypt != null && data.elementToDecrypt.k.isNotBlank()) {
MXEncryptedAttachments.decryptAttachment(inputStream, data.elementToDecrypt)
} else {
inputStream
}
callback.onDataReady(stream)
}
}

View File

@ -20,13 +20,11 @@ import android.net.Uri
import android.os.Parcelable
import android.widget.ImageView
import androidx.exifinterface.media.ExifInterface
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.github.piasy.biv.view.BigImageView
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.glide.ELEMENT_TO_DECRYPT
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.utils.DimensionUtils.dpToPx
import kotlinx.android.parcel.Parcelize
@ -62,26 +60,9 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
val (width, height) = processSize(data, mode)
imageView.layoutParams.height = height
imageView.layoutParams.width = width
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val resolvedUrl = when (mode) {
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
}
//Fallback to base url
?: data.url

GlideApp
.with(imageView)
.load(resolvedUrl)
.apply {
// Give element to decrypt to Glide
if (data.elementToDecrypt != null) {
set(ELEMENT_TO_DECRYPT, data.elementToDecrypt)
// And disable cache
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
}
}
.load(data)
.dontAnimate()
.transform(RoundedCorners(dpToPx(8, imageView.context)))
.thumbnail(0.3f)