forked from GitHub-Mirror/riotX-android
Decrypt video file
This commit is contained in:
parent
1b82ed5abb
commit
12bd85e0a9
@ -26,12 +26,14 @@ import im.vector.matrix.android.api.session.events.model.Content
|
|||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
interface CryptoService {
|
interface CryptoService {
|
||||||
|
|
||||||
@ -103,6 +105,13 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a file.
|
||||||
|
* Result will be a decrypted file, stored in the cache folder. id parameter will be used to create a sub folder to avoid name collision.
|
||||||
|
* You can pass the eventId
|
||||||
|
*/
|
||||||
|
fun decryptFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt, callback: MatrixCallback<File>)
|
||||||
|
|
||||||
fun getEncryptionAlgorithm(roomId: String): String?
|
fun getEncryptionAlgorithm(roomId: String): String?
|
||||||
|
|
||||||
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
||||||
|
@ -48,6 +48,7 @@ import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAct
|
|||||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
|
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
|
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
@ -78,6 +79,7 @@ import im.vector.matrix.android.internal.util.fetchCopied
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -112,6 +114,8 @@ internal class CryptoManager @Inject constructor(
|
|||||||
private val keysBackup: KeysBackup,
|
private val keysBackup: KeysBackup,
|
||||||
//
|
//
|
||||||
private val objectSigner: ObjectSigner,
|
private val objectSigner: ObjectSigner,
|
||||||
|
// File decryptor
|
||||||
|
private val fileDecryptor: FileDecryptor,
|
||||||
//
|
//
|
||||||
private val oneTimeKeysUploader: OneTimeKeysUploader,
|
private val oneTimeKeysUploader: OneTimeKeysUploader,
|
||||||
//
|
//
|
||||||
@ -607,6 +611,10 @@ internal class CryptoManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun decryptFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt, callback: MatrixCallback<File>) {
|
||||||
|
fileDecryptor.decryptFile(id, filename, url, elementToDecrypt, callback)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt an event
|
* Decrypt an event
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
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.MXEncryptedAttachments
|
||||||
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.android.internal.util.md5
|
||||||
|
import im.vector.matrix.android.internal.util.writeToFile
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class FileDecryptor @Inject constructor(private val context: Context,
|
||||||
|
private val sessionParams: SessionParams,
|
||||||
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
|
||||||
|
|
||||||
|
val okHttpClient = OkHttpClient()
|
||||||
|
|
||||||
|
fun decryptFile(id: String,
|
||||||
|
fileName: String,
|
||||||
|
url: String,
|
||||||
|
elementToDecrypt: ElementToDecrypt,
|
||||||
|
callback: MatrixCallback<File>) {
|
||||||
|
GlobalScope.launch(coroutineDispatchers.main) {
|
||||||
|
withContext(coroutineDispatchers.io) {
|
||||||
|
Try {
|
||||||
|
// Create dir tree:
|
||||||
|
// <cache>/DF/<md5(userId)>/<md5(id)>/
|
||||||
|
val tmpFolderRoot = File(context.cacheDir, "DF")
|
||||||
|
val tmpFolderUser = File(tmpFolderRoot, sessionParams.credentials.userId.md5())
|
||||||
|
val tmpFolder = File(tmpFolderUser, id.md5())
|
||||||
|
|
||||||
|
if (!tmpFolder.exists()) {
|
||||||
|
tmpFolder.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
File(tmpFolder, fileName)
|
||||||
|
}.map { destFile ->
|
||||||
|
if (!destFile.exists()) {
|
||||||
|
Try {
|
||||||
|
Timber.v("## decrypt file")
|
||||||
|
|
||||||
|
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
|
||||||
|
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(resolvedUrl)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val response = okHttpClient.newCall(request).execute()
|
||||||
|
val inputStream = response.body()?.byteStream()
|
||||||
|
Timber.v("Response size ${response.body()?.contentLength()} - Stream available: ${inputStream?.available()}")
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
throw IOException()
|
||||||
|
}
|
||||||
|
|
||||||
|
MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error")
|
||||||
|
}
|
||||||
|
.map { inputStream ->
|
||||||
|
writeToFile(inputStream, destFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.foldToCallback(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.attachments
|
package im.vector.matrix.android.internal.crypto.attachments
|
||||||
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
@ -198,7 +197,7 @@ object MXEncryptedAttachments {
|
|||||||
|
|
||||||
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
||||||
|
|
||||||
if (!TextUtils.equals(elementToDecrypt.sha256, currentDigestValue)) {
|
if (elementToDecrypt.sha256 != currentDigestValue) {
|
||||||
Timber.e("## decryptAttachment() : Digest value mismatch")
|
Timber.e("## decryptAttachment() : Digest value mismatch")
|
||||||
outStream.close()
|
outStream.close()
|
||||||
return null
|
return null
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.util
|
||||||
|
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import okio.Okio
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an input stream to a file with Okio
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
fun writeToFile(inputStream: InputStream, outputFile: File) {
|
||||||
|
val source = Okio.buffer(Okio.source(inputStream))
|
||||||
|
val sink = Okio.buffer(Okio.sink(outputFile))
|
||||||
|
|
||||||
|
source.use { input ->
|
||||||
|
sink.use { output ->
|
||||||
|
output.writeAll(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ import arrow.core.Try
|
|||||||
import okio.Okio
|
import okio.Okio
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a string to a file with Okio
|
* Save a string to a file with Okio
|
||||||
|
@ -29,14 +29,7 @@ import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
|
|||||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
|
||||||
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.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
@ -87,9 +80,9 @@ class MessageItemFactory @Inject constructor(
|
|||||||
|
|
||||||
val messageContent: MessageContent =
|
val messageContent: MessageContent =
|
||||||
event.annotations?.editSummary?.aggregatedContent?.toModel()
|
event.annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
?: event.root.getClearContent().toModel()
|
?: event.root.getClearContent().toModel()
|
||||||
?: //Malformed content, we should echo something on screen
|
?: //Malformed content, we should echo something on screen
|
||||||
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
||||||
|
|
||||||
if (messageContent.relatesTo?.type == RelationType.REPLACE) {
|
if (messageContent.relatesTo?.type == RelationType.REPLACE) {
|
||||||
// ignore replace event, the targeted id is already edited
|
// ignore replace event, the targeted id is already edited
|
||||||
@ -99,16 +92,16 @@ class MessageItemFactory @Inject constructor(
|
|||||||
// val ev = all.toModel<Event>()
|
// val ev = all.toModel<Event>()
|
||||||
return when (messageContent) {
|
return when (messageContent) {
|
||||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
|
is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
|
||||||
informationData,
|
informationData,
|
||||||
event.annotations?.editSummary,
|
event.annotations?.editSummary,
|
||||||
highlight,
|
highlight,
|
||||||
callback)
|
callback)
|
||||||
is MessageTextContent -> buildTextMessageItem(event.sendState,
|
is MessageTextContent -> buildTextMessageItem(event.sendState,
|
||||||
messageContent,
|
messageContent,
|
||||||
informationData,
|
informationData,
|
||||||
event.annotations?.editSummary,
|
event.annotations?.editSummary,
|
||||||
highlight,
|
highlight,
|
||||||
callback
|
callback
|
||||||
)
|
)
|
||||||
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback)
|
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback)
|
||||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback)
|
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback)
|
||||||
@ -142,7 +135,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +158,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
}
|
}
|
||||||
.clickListener(
|
.clickListener(
|
||||||
DebouncedClickListener(View.OnClickListener { _ ->
|
DebouncedClickListener(View.OnClickListener { _ ->
|
||||||
@ -218,7 +211,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,8 +232,10 @@ class MessageItemFactory @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val videoData = VideoContentRenderer.Data(
|
val videoData = VideoContentRenderer.Data(
|
||||||
|
eventId = informationData.eventId,
|
||||||
filename = messageContent.body,
|
filename = messageContent.body,
|
||||||
videoUrl = messageContent.url,
|
url = messageContent.encryptedFileInfo?.url ?: messageContent.url,
|
||||||
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
thumbnailMediaData = thumbnailData
|
thumbnailMediaData = thumbnailData
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -262,7 +257,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
|
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +297,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,9 +329,9 @@ class MessageItemFactory @Inject constructor(
|
|||||||
//nop
|
//nop
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editStart,
|
editStart,
|
||||||
editEnd,
|
editEnd,
|
||||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||||
return spannable
|
return spannable
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,7 +367,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +403,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
}))
|
}))
|
||||||
.longClickListener { view ->
|
.longClickListener { view ->
|
||||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||||
?: false
|
?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,6 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO DECRYPT_FILE Decrypt file
|
|
||||||
imageView.showImage(
|
imageView.showImage(
|
||||||
Uri.parse(thumbnail),
|
Uri.parse(thumbnail),
|
||||||
Uri.parse(fullSize)
|
Uri.parse(fullSize)
|
||||||
|
@ -18,26 +18,87 @@ package im.vector.riotx.features.media
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.VideoView
|
import android.widget.VideoView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VideoContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
|
class VideoContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val errorFormatter: ErrorFormatter) {
|
||||||
|
|
||||||
// TODO DECRYPT_FILE Encrypted data
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Data(
|
data class Data(
|
||||||
|
val eventId: String,
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val videoUrl: String?,
|
val url: String?,
|
||||||
|
val elementToDecrypt: ElementToDecrypt?,
|
||||||
val thumbnailMediaData: ImageContentRenderer.Data
|
val thumbnailMediaData: ImageContentRenderer.Data
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
fun render(data: Data, thumbnailView: ImageView, videoView: VideoView) {
|
fun render(data: Data,
|
||||||
|
thumbnailView: ImageView,
|
||||||
|
loadingView: ProgressBar,
|
||||||
|
videoView: VideoView,
|
||||||
|
errorView: TextView) {
|
||||||
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
||||||
val resolvedUrl = contentUrlResolver.resolveFullSize(data.videoUrl)
|
|
||||||
videoView.setVideoPath(resolvedUrl)
|
if (data.elementToDecrypt != null) {
|
||||||
videoView.start()
|
Timber.v("Decrypt video")
|
||||||
|
videoView.isVisible = false
|
||||||
|
|
||||||
|
if (data.url == null) {
|
||||||
|
loadingView.isVisible = false
|
||||||
|
errorView.isVisible = true
|
||||||
|
errorView.setText(R.string.unknown_error)
|
||||||
|
} else {
|
||||||
|
thumbnailView.isVisible = true
|
||||||
|
loadingView.isVisible = true
|
||||||
|
|
||||||
|
activeSessionHolder.getActiveSession()
|
||||||
|
.decryptFile(data.eventId,
|
||||||
|
data.filename,
|
||||||
|
data.url,
|
||||||
|
data.elementToDecrypt,
|
||||||
|
object : MatrixCallback<File> {
|
||||||
|
override fun onSuccess(data: File) {
|
||||||
|
thumbnailView.isVisible = false
|
||||||
|
loadingView.isVisible = false
|
||||||
|
videoView.isVisible = true
|
||||||
|
|
||||||
|
videoView.setVideoPath(data.path)
|
||||||
|
videoView.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
loadingView.isVisible = false
|
||||||
|
errorView.isVisible = true
|
||||||
|
errorView.text = errorFormatter.toHumanReadable(failure)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thumbnailView.isVisible = false
|
||||||
|
loadingView.isVisible = false
|
||||||
|
|
||||||
|
val resolvedUrl = contentUrlResolver.resolveFullSize(data.url)
|
||||||
|
|
||||||
|
if (resolvedUrl == null) {
|
||||||
|
errorView.isVisible = true
|
||||||
|
errorView.setText(R.string.unknown_error)
|
||||||
|
} else {
|
||||||
|
videoView.setVideoPath(resolvedUrl)
|
||||||
|
videoView.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -28,6 +28,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class VideoMediaViewerActivity : VectorBaseActivity() {
|
class VideoMediaViewerActivity : VectorBaseActivity() {
|
||||||
|
|
||||||
|
@Inject lateinit var imageContentRenderer: ImageContentRenderer
|
||||||
@Inject lateinit var videoContentRenderer: VideoContentRenderer
|
@Inject lateinit var videoContentRenderer: VideoContentRenderer
|
||||||
|
|
||||||
override fun injectWith(injector: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
@ -38,12 +39,10 @@ class VideoMediaViewerActivity : VectorBaseActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(im.vector.riotx.R.layout.activity_video_media_viewer)
|
setContentView(im.vector.riotx.R.layout.activity_video_media_viewer)
|
||||||
val mediaData = intent.getParcelableExtra<VideoContentRenderer.Data>(EXTRA_MEDIA_DATA)
|
val mediaData = intent.getParcelableExtra<VideoContentRenderer.Data>(EXTRA_MEDIA_DATA)
|
||||||
if (mediaData.videoUrl.isNullOrEmpty()) {
|
|
||||||
finish()
|
configureToolbar(videoMediaViewerToolbar, mediaData)
|
||||||
} else {
|
imageContentRenderer.render(mediaData.thumbnailMediaData, ImageContentRenderer.Mode.FULL_SIZE, videoMediaViewerThumbnailView)
|
||||||
configureToolbar(videoMediaViewerToolbar, mediaData)
|
videoContentRenderer.render(mediaData, videoMediaViewerThumbnailView, videoMediaViewerLoading, videoMediaViewerVideoView, videoMediaViewerErrorView)
|
||||||
videoContentRenderer.render(mediaData, videoMediaViewerThumbnailView, videoMediaViewerVideoView)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun configureToolbar(toolbar: Toolbar, mediaData: VideoContentRenderer.Data) {
|
private fun configureToolbar(toolbar: Toolbar, mediaData: VideoContentRenderer.Data) {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
@ -32,12 +33,35 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/videoMediaViewerThumbnailView"
|
android:id="@+id/videoMediaViewerThumbnailView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/videoMediaViewerLoading"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<VideoView
|
<VideoView
|
||||||
android:id="@+id/videoMediaViewerVideoView"
|
android:id="@+id/videoMediaViewerVideoView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/videoMediaViewerErrorView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:textColor="@color/riotx_notice"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="Error"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user