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 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 t0 = System.currentTimeMillis()
val outStream = ByteArrayOutputStream() 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.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.signature.ObjectKey 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.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 java.io.InputStream
import com.bumptech.glide.load.engine.Resource as Resource1 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() return VectorGlideModelLoader()
} }
@ -41,25 +49,31 @@ class VectorGlideModelLoaderFactory : ModelLoaderFactory<InputStream, InputStrea
} }
class VectorGlideModelLoader : ModelLoader<InputStream, InputStream> { class VectorGlideModelLoader : ModelLoader<ImageContentRenderer.Data, InputStream> {
override fun handles(model: InputStream): Boolean { override fun handles(model: ImageContentRenderer.Data): Boolean {
// Always handle // Always handle
return true return true
} }
override fun buildLoadData(model: InputStream, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? { override fun buildLoadData(model: ImageContentRenderer.Data, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? {
return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(model, options.get(ELEMENT_TO_DECRYPT))) return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(model, width, height))
} }
} }
class VectorGlideDataFetcher(private val inputStream: InputStream, class VectorGlideDataFetcher(private val data: ImageContentRenderer.Data,
private val elementToDecrypt: ElementToDecrypt?) : DataFetcher<InputStream> { private val width: Int,
private val height: Int) : DataFetcher<InputStream> {
val client = OkHttpClient()
override fun getDataClass(): Class<InputStream> { override fun getDataClass(): Class<InputStream> {
return InputStream::class.java return InputStream::class.java
} }
private var stream: InputStream? = null
override fun cleanup() { override fun cleanup() {
// ? cancel()
} }
override fun getDataSource(): DataSource { override fun getDataSource(): DataSource {
@ -68,16 +82,43 @@ class VectorGlideDataFetcher(private val inputStream: InputStream,
} }
override fun cancel() { 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>) { override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
if (elementToDecrypt?.k?.isNotBlank() == true) { Timber.v("Load data: $data")
// Encrypted stream if (data.isLocalFile()) {
callback.onDataReady(MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)) 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 { } else {
// Not encrypted stream inputStream
callback.onDataReady(inputStream) }
} callback.onDataReady(stream)
} }
} }

View File

@ -20,13 +20,11 @@ import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import android.widget.ImageView import android.widget.ImageView
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.github.piasy.biv.view.BigImageView import com.github.piasy.biv.view.BigImageView
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.riotredesign.core.di.ActiveSessionHolder 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.glide.GlideApp
import im.vector.riotredesign.core.utils.DimensionUtils.dpToPx import im.vector.riotredesign.core.utils.DimensionUtils.dpToPx
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@ -62,26 +60,9 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
val (width, height) = processSize(data, mode) val (width, height) = processSize(data, mode)
imageView.layoutParams.height = height imageView.layoutParams.height = height
imageView.layoutParams.width = width 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 GlideApp
.with(imageView) .with(imageView)
.load(resolvedUrl) .load(data)
.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)
}
}
.dontAnimate() .dontAnimate()
.transform(RoundedCorners(dpToPx(8, imageView.context))) .transform(RoundedCorners(dpToPx(8, imageView.context)))
.thumbnail(0.3f) .thumbnail(0.3f)