forked from GitHub-Mirror/riotX-android
Decrypt Attachment - WIP
This commit is contained in:
parent
707a4712fc
commit
b54ca5a8a0
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 OpenMarket Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.attachments
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
|
||||||
|
fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? {
|
||||||
|
// Check the validity of some fields
|
||||||
|
if (isValid()) {
|
||||||
|
return ElementToDecrypt(
|
||||||
|
iv = this.iv!!,
|
||||||
|
k = this.key!!.k!!,
|
||||||
|
sha256 = this.hashes!!["sha256"] ?: error("")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent data to decode an attachment
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class ElementToDecrypt(
|
||||||
|
val iv: String,
|
||||||
|
val k: String,
|
||||||
|
val sha256: String
|
||||||
|
) : Parcelable
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto.attachments
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
@ -142,24 +142,27 @@ object MXEncryptedAttachments {
|
|||||||
* @return the decrypted attachment stream
|
* @return the decrypted attachment stream
|
||||||
*/
|
*/
|
||||||
fun decryptAttachment(attachmentStream: InputStream?, encryptedFileInfo: EncryptedFileInfo?): InputStream? {
|
fun decryptAttachment(attachmentStream: InputStream?, encryptedFileInfo: EncryptedFileInfo?): InputStream? {
|
||||||
|
if (encryptedFileInfo?.isValid() != true) {
|
||||||
|
Timber.e("## decryptAttachment() : some fields are not defined, or invalid key fields")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val elementToDecrypt = encryptedFileInfo.toElementToDecrypt()
|
||||||
|
|
||||||
|
return decryptAttachment(attachmentStream, elementToDecrypt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt an attachment
|
||||||
|
*
|
||||||
|
* @param attachmentStream the attachment stream
|
||||||
|
* @param elementToDecrypt the elementToDecrypt info
|
||||||
|
* @return the decrypted attachment stream
|
||||||
|
*/
|
||||||
|
fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?): InputStream? {
|
||||||
// sanity checks
|
// sanity checks
|
||||||
if (null == attachmentStream || null == encryptedFileInfo) {
|
if (null == attachmentStream || elementToDecrypt == null) {
|
||||||
Timber.e("## decryptAttachment() : null parameters")
|
Timber.e("## decryptAttachment() : null stream")
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(encryptedFileInfo.iv)
|
|
||||||
|| null == encryptedFileInfo.key
|
|
||||||
|| null == encryptedFileInfo.hashes
|
|
||||||
|| !encryptedFileInfo.hashes.containsKey("sha256")) {
|
|
||||||
Timber.e("## decryptAttachment() : some fields are not defined")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TextUtils.equals(encryptedFileInfo.key!!.alg, "A256CTR")
|
|
||||||
|| !TextUtils.equals(encryptedFileInfo.key!!.kty, "oct")
|
|
||||||
|| TextUtils.isEmpty(encryptedFileInfo.key!!.k)) {
|
|
||||||
Timber.e("## decryptAttachment() : invalid key fields")
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,8 +180,8 @@ object MXEncryptedAttachments {
|
|||||||
val outStream = ByteArrayOutputStream()
|
val outStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val key = Base64.decode(base64UrlToBase64(encryptedFileInfo.key!!.k), Base64.DEFAULT)
|
val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
|
||||||
val initVectorBytes = Base64.decode(encryptedFileInfo.iv, Base64.DEFAULT)
|
val initVectorBytes = Base64.decode(elementToDecrypt.iv, Base64.DEFAULT)
|
||||||
|
|
||||||
val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||||
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||||
@ -205,7 +208,7 @@ object MXEncryptedAttachments {
|
|||||||
|
|
||||||
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
||||||
|
|
||||||
if (!TextUtils.equals(encryptedFileInfo.hashes["sha256"], currentDigestValue)) {
|
if (!TextUtils.equals(elementToDecrypt.sha256, currentDigestValue)) {
|
||||||
Timber.e("## decryptAttachment() : Digest value mismatch")
|
Timber.e("## decryptAttachment() : Digest value mismatch")
|
||||||
outStream.close()
|
outStream.close()
|
||||||
return null
|
return null
|
@ -33,7 +33,7 @@ data class EncryptedFileInfo(
|
|||||||
* Not documented
|
* Not documented
|
||||||
*/
|
*/
|
||||||
@Json(name = "mimetype")
|
@Json(name = "mimetype")
|
||||||
var mimetype: String,
|
var mimetype: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A JSON Web Key object.
|
* Required. A JSON Web Key object.
|
||||||
@ -45,18 +45,45 @@ data class EncryptedFileInfo(
|
|||||||
* Required. The Initialisation Vector used by AES-CTR, encoded as unpadded base64.
|
* Required. The Initialisation Vector used by AES-CTR, encoded as unpadded base64.
|
||||||
*/
|
*/
|
||||||
@Json(name = "iv")
|
@Json(name = "iv")
|
||||||
var iv: String,
|
var iv: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64.
|
* Required. A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64.
|
||||||
* Clients should support the SHA-256 hash, which uses the key "sha256".
|
* Clients should support the SHA-256 hash, which uses the key "sha256".
|
||||||
*/
|
*/
|
||||||
@Json(name = "hashes")
|
@Json(name = "hashes")
|
||||||
var hashes: Map<String, String>,
|
var hashes: Map<String, String>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. Version of the encrypted attachments protocol. Must be "v2".
|
* Required. Version of the encrypted attachments protocol. Must be "v2".
|
||||||
*/
|
*/
|
||||||
@Json(name = "v")
|
@Json(name = "v")
|
||||||
var v: String? = null
|
var v: String? = null
|
||||||
)
|
) {
|
||||||
|
/**
|
||||||
|
* Check what the spec tells us
|
||||||
|
*/
|
||||||
|
fun isValid(): Boolean {
|
||||||
|
if (url.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key?.isValid() != true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iv.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashes?.containsKey("sha256") != true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v != "v2") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,7 +24,7 @@ data class EncryptedFileKey(
|
|||||||
* Required. Algorithm. Must be "A256CTR".
|
* Required. Algorithm. Must be "A256CTR".
|
||||||
*/
|
*/
|
||||||
@Json(name = "alg")
|
@Json(name = "alg")
|
||||||
var alg: String,
|
var alg: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. Extractable. Must be true. This is a W3C extension.
|
* Required. Extractable. Must be true. This is a W3C extension.
|
||||||
@ -36,18 +36,45 @@ data class EncryptedFileKey(
|
|||||||
* Required. Key operations. Must at least contain "encrypt" and "decrypt".
|
* Required. Key operations. Must at least contain "encrypt" and "decrypt".
|
||||||
*/
|
*/
|
||||||
@Json(name = "key_ops")
|
@Json(name = "key_ops")
|
||||||
var key_ops: List<String>,
|
var key_ops: List<String>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. Key type. Must be "oct".
|
* Required. Key type. Must be "oct".
|
||||||
*/
|
*/
|
||||||
@Json(name = "kty")
|
@Json(name = "kty")
|
||||||
var kty: String,
|
var kty: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required. The key, encoded as urlsafe unpadded base64.
|
* Required. The key, encoded as urlsafe unpadded base64.
|
||||||
*/
|
*/
|
||||||
@Json(name = "k")
|
@Json(name = "k")
|
||||||
var k: String
|
var k: String? = null
|
||||||
)
|
) {
|
||||||
|
/**
|
||||||
|
* Check what the spec tells us
|
||||||
|
*/
|
||||||
|
fun isValid(): Boolean {
|
||||||
|
if (alg != "A256CTR") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ext != true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key_ops?.contains("encrypt") != true || key_ops?.contains("decrypt") != true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kty != "oct") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@ -14,21 +14,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotredesign.core.glide;
|
package im.vector.riotredesign.core.glide
|
||||||
|
|
||||||
import android.content.Context;
|
import com.bumptech.glide.load.Option
|
||||||
import android.util.Log;
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
|
||||||
import com.bumptech.glide.GlideBuilder;
|
const val ElementToDecryptOptionKey = "im.vector.riotx.core.glide.ElementToDecrypt"
|
||||||
import com.bumptech.glide.annotation.GlideModule;
|
|
||||||
import com.bumptech.glide.module.AppGlideModule;
|
|
||||||
|
|
||||||
@GlideModule
|
|
||||||
public final class MyAppGlideModule extends AppGlideModule {
|
|
||||||
|
|
||||||
@Override
|
val ELEMENT_TO_DECRYPT = Option.memory(
|
||||||
public void applyOptions(Context context, GlideBuilder builder) {
|
ElementToDecryptOptionKey, ElementToDecrypt("", "", ""))
|
||||||
builder.setLogLevel(Log.ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotredesign.core.glide
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.GlideBuilder
|
||||||
|
import com.bumptech.glide.Registry
|
||||||
|
import com.bumptech.glide.annotation.GlideModule
|
||||||
|
import com.bumptech.glide.module.AppGlideModule
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
@GlideModule
|
||||||
|
class MyAppGlideModule : AppGlideModule() {
|
||||||
|
|
||||||
|
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||||
|
builder.setLogLevel(Log.ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||||
|
// FIXME This does not work
|
||||||
|
registry.append(InputStream::class.java, InputStream::class.java, VectorGlideModelLoaderFactory())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.riotredesign.core.glide
|
||||||
|
|
||||||
|
import com.bumptech.glide.Priority
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.Options
|
||||||
|
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
|
||||||
|
import com.bumptech.glide.signature.ObjectKey
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
|
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> {
|
||||||
|
return VectorGlideModelLoader()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun teardown() {
|
||||||
|
// Is there something to do here?
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class VectorGlideModelLoader : ModelLoader<InputStream, InputStream> {
|
||||||
|
override fun handles(model: InputStream): 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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VectorGlideDataFetcher(private val inputStream: InputStream,
|
||||||
|
private val elementToDecrypt: ElementToDecrypt?) : DataFetcher<InputStream> {
|
||||||
|
override fun getDataClass(): Class<InputStream> {
|
||||||
|
return InputStream::class.java
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cleanup() {
|
||||||
|
// ?
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDataSource(): DataSource {
|
||||||
|
// ?
|
||||||
|
return DataSource.REMOTE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancel() {
|
||||||
|
// ?
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
|||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
import im.vector.riotredesign.EmojiCompatFontProvider
|
import im.vector.riotredesign.EmojiCompatFontProvider
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||||
@ -179,7 +180,8 @@ class MessageItemFactory @Inject constructor(
|
|||||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||||
val data = ImageContentRenderer.Data(
|
val data = ImageContentRenderer.Data(
|
||||||
filename = messageContent.body,
|
filename = messageContent.body,
|
||||||
url = messageContent.url,
|
url = messageContent.encryptedFileInfo?.url ?: messageContent.url,
|
||||||
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
height = messageContent.info?.height,
|
height = messageContent.info?.height,
|
||||||
maxHeight = maxHeight,
|
maxHeight = maxHeight,
|
||||||
width = messageContent.info?.width,
|
width = messageContent.info?.width,
|
||||||
@ -220,7 +222,8 @@ class MessageItemFactory @Inject constructor(
|
|||||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||||
val thumbnailData = ImageContentRenderer.Data(
|
val thumbnailData = ImageContentRenderer.Data(
|
||||||
filename = messageContent.body,
|
filename = messageContent.body,
|
||||||
url = messageContent.videoInfo?.thumbnailUrl,
|
url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl,
|
||||||
|
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||||
height = messageContent.videoInfo?.height,
|
height = messageContent.videoInfo?.height,
|
||||||
maxHeight = maxHeight,
|
maxHeight = maxHeight,
|
||||||
width = messageContent.videoInfo?.width,
|
width = messageContent.videoInfo?.width,
|
||||||
|
@ -20,11 +20,13 @@ 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.Matrix
|
|
||||||
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.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
|
||||||
@ -37,6 +39,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
data class Data(
|
data class Data(
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val url: String?,
|
val url: String?,
|
||||||
|
val elementToDecrypt: ElementToDecrypt?,
|
||||||
val height: Int?,
|
val height: Int?,
|
||||||
val maxHeight: Int,
|
val maxHeight: Int,
|
||||||
val width: Int?,
|
val width: Int?,
|
||||||
@ -70,6 +73,15 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
GlideApp
|
GlideApp
|
||||||
.with(imageView)
|
.with(imageView)
|
||||||
.load(resolvedUrl)
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.transform(RoundedCorners(dpToPx(8, imageView.context)))
|
.transform(RoundedCorners(dpToPx(8, imageView.context)))
|
||||||
.thumbnail(0.3f)
|
.thumbnail(0.3f)
|
||||||
@ -81,6 +93,8 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
||||||
val fullSize = contentUrlResolver.resolveFullSize(data.url)
|
val fullSize = contentUrlResolver.resolveFullSize(data.url)
|
||||||
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
|
|
||||||
|
// TODO DECRYPT_FILE Decrypt file
|
||||||
imageView.showImage(
|
imageView.showImage(
|
||||||
Uri.parse(thumbnail),
|
Uri.parse(thumbnail),
|
||||||
Uri.parse(fullSize)
|
Uri.parse(fullSize)
|
||||||
|
@ -26,6 +26,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class VideoContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
|
class VideoContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
|
||||||
|
|
||||||
|
// TODO DECRYPT_FILE Encrypted data
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Data(
|
data class Data(
|
||||||
val filename: String,
|
val filename: String,
|
||||||
|
@ -32,6 +32,7 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
@ -46,6 +47,10 @@ import im.vector.riotredesign.core.utils.toast
|
|||||||
import im.vector.riotredesign.features.MainActivity
|
import im.vector.riotredesign.features.MainActivity
|
||||||
import im.vector.riotredesign.features.themes.ThemeUtils
|
import im.vector.riotredesign.features.themes.ThemeUtils
|
||||||
import im.vector.riotredesign.features.workers.signout.SignOutUiWorker
|
import im.vector.riotredesign.features.workers.signout.SignOutUiWorker
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -197,6 +202,18 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
|
|||||||
|
|
||||||
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
notImplemented()
|
notImplemented()
|
||||||
|
|
||||||
|
// TODO DECRYPT_FILE Quick implementation of clear cache, finish this
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
// On UI Thread
|
||||||
|
Glide.get(requireContext()).clearMemory()
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
// On BG thread
|
||||||
|
Glide.get(requireContext()).clearDiskCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ class VectorSettingsPreferencesFragment : VectorSettingsBaseFragment() {
|
|||||||
context?.let { context: Context ->
|
context?.let { context: Context ->
|
||||||
AlertDialog.Builder(context)
|
AlertDialog.Builder(context)
|
||||||
.setSingleChoiceItems(R.array.media_saving_choice,
|
.setSingleChoiceItems(R.array.media_saving_choice,
|
||||||
PreferencesManager.getSelectedMediasSavingPeriod(activity)) { d, n ->
|
PreferencesManager.getSelectedMediasSavingPeriod(activity)) { d, n ->
|
||||||
PreferencesManager.setSelectedMediasSavingPeriod(activity, n)
|
PreferencesManager.setSelectedMediasSavingPeriod(activity, n)
|
||||||
d.cancel()
|
d.cancel()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user