From 3f74c4e9335877ffa9bd047918bb483a4b05cac6 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 21 Jun 2019 11:38:15 +0200
Subject: [PATCH 01/12] Report change from
 https://github.com/matrix-org/matrix-android-sdk/pull/471

---
 .../android/api/session/room/model/create/CreateRoomParams.kt   | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt
index 5cf6941bf1..5758de3e69 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt
@@ -128,6 +128,7 @@ class CreateRoomParams {
             contentMap["algorithm"] = algorithm
 
             val algoEvent = Event(type = EventType.ENCRYPTION,
+                    stateKey = "",
                     content = contentMap.toContent()
             )
 
@@ -161,6 +162,7 @@ class CreateRoomParams {
             contentMap["history_visibility"] = historyVisibility
 
             val historyVisibilityEvent = Event(type = EventType.STATE_HISTORY_VISIBILITY,
+                    stateKey = "",
                     content = contentMap.toContent())
 
             if (null == initialStates) {

From 8e76700c8d54845120cc0ee96f822fea56889506 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 21 Jun 2019 12:04:48 +0200
Subject: [PATCH 02/12] Handle redacted e2e event

---
 .../matrix/android/api/session/events/model/Event.kt     | 4 ++++
 .../session/room/timeline/TimelineEventFactory.kt        | 5 ++++-
 .../room/detail/timeline/factory/MessageItemFactory.kt   | 2 +-
 .../room/detail/timeline/factory/TimelineItemFactory.kt  | 9 ++++++++-
 4 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
index 5ae49f78f2..26587359ab 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
@@ -228,4 +228,8 @@ data class Event(
         }
     }
 
+    /**
+     * Tells if the event is redacted
+     */
+    fun isRedacted() = unsignedData?.redactedEvent != null
 }
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt
index e98e0d352c..4fb237c396 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt
@@ -107,7 +107,7 @@ internal class InMemoryTimelineEventFactory @Inject constructor(private val room
                     senderRoomMember?.avatarUrl)
         }
         val event = eventEntity.asDomain()
-        if (event.getClearType() == EventType.ENCRYPTED) {
+        if (event.getClearType() == EventType.ENCRYPTED && !event.isRedacted()) {
             handleEncryptedEvent(event, eventEntity.localId)
         }
 
@@ -141,6 +141,9 @@ internal class InMemoryTimelineEventFactory @Inject constructor(private val room
                 Timber.e(failure, "Encrypted event: decryption failed")
                 if (failure is MXDecryptionException) {
                     event.setCryptoError(failure.cryptoError)
+                } else {
+                    // Other error
+                    Timber.e("Other error, should be handled")
                 }
             }
         }
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 5cb7c94346..f3d40bc642 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -71,7 +71,7 @@ class MessageItemFactory @Inject constructor(
 
         val informationData = messageInformationDataFactory.create(event, nextEvent)
 
-        if (event.root.unsignedData?.redactedEvent != null) {
+        if (event.root.isRedacted()) {
             //message is redacted
             return buildRedactedItem(informationData, highlight, callback)
         }
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
index 32edb0b824..84f4f287e5 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
@@ -55,7 +55,14 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
 
                 // Crypto
                 EventType.ENCRYPTION        -> encryptionItemFactory.create(event, highlight, callback)
-                EventType.ENCRYPTED         -> encryptedItemFactory.create(event, nextEvent, highlight, callback)
+                EventType.ENCRYPTED         -> {
+                    if (event.root.isRedacted()) {
+                        // Redacted event, let the MessageItemFactory handle it
+                        messageItemFactory.create(event, nextEvent, highlight, callback)
+                    } else {
+                        encryptedItemFactory.create(event, nextEvent, highlight, callback)
+                    }
+                }
 
                 // Unhandled event types (yet)
                 EventType.STATE_ROOM_THIRD_PARTY_INVITE,

From 707a4712fc3a94252dd3703c50624a4e4dc54e16 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit.marty@gmail.com>
Date: Fri, 21 Jun 2019 15:08:09 +0200
Subject: [PATCH 03/12] Add some javadoc from Matrix spec and add
 EncryptedFileInfo where necessary

---
 .../session/room/model/message/AudioInfo.kt   | 11 ++++++
 .../session/room/model/message/FileInfo.kt    | 23 ++++++++++-
 .../session/room/model/message/ImageInfo.kt   | 39 ++++++++++++++++++-
 .../room/model/message/LocationInfo.kt        | 15 ++++++-
 .../room/model/message/MessageAudioContent.kt | 28 +++++++++++--
 .../model/message/MessageEncyptedContent.kt   | 27 +++++++++++++
 .../room/model/message/MessageFileContent.kt  | 27 ++++++++++++-
 .../room/model/message/MessageImageContent.kt | 27 ++++++++++++-
 .../model/message/MessageLocationContent.kt   | 18 ++++++++-
 .../room/model/message/MessageVideoContent.kt | 28 +++++++++++--
 .../room/model/message/ThumbnailInfo.kt       | 15 +++++++
 .../session/room/model/message/VideoInfo.kt   | 35 ++++++++++++++++-
 .../crypto/model/rest/EncryptedFileInfo.kt    | 34 ++++++++++++++++
 .../crypto/model/rest/EncryptedFileKey.kt     | 25 ++++++++++++
 .../session/content/UploadContentWorker.kt    |  2 +-
 .../room/send/LocalEchoEventFactory.kt        |  4 +-
 .../timeline/factory/MessageItemFactory.kt    |  6 +--
 17 files changed, 343 insertions(+), 21 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEncyptedContent.kt

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt
index 6a28a1b349..de30d46024 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt
@@ -21,7 +21,18 @@ import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
 data class AudioInfo(
+        /**
+         * The mimetype of the audio e.g. "audio/aac".
+         */
         @Json(name = "mimetype") val mimeType: String,
+
+        /**
+         * The size of the audio clip in bytes.
+         */
         @Json(name = "size") val size: Long = 0,
+
+        /**
+         * The duration of the audio in milliseconds.
+         */
         @Json(name = "duration") val duration: Int = 0
 )
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt
index 16dc4d23f1..d5dfb04f19 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt
@@ -18,11 +18,32 @@ package im.vector.matrix.android.api.session.room.model.message
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
 data class FileInfo(
+        /**
+         * The mimetype of the file e.g. application/msword.
+         */
         @Json(name = "mimetype") val mimeType: String?,
+
+        /**
+         * The size of the file in bytes.
+         */
         @Json(name = "size") val size: Long = 0,
+
+        /**
+         * Metadata about the image referred to in thumbnail_url.
+         */
         @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null,
-        @Json(name = "thumbnail_url") val thumbnailUrl: String? = null
+
+        /**
+         * The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted.
+         */
+        @Json(name = "thumbnail_url") val thumbnailUrl: String? = null,
+
+        /**
+         * Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted.
+         */
+        @Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null
 )
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt
index 69e88e052c..729bc604c1 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt
@@ -18,15 +18,52 @@ package im.vector.matrix.android.api.session.room.model.message
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
 data class ImageInfo(
+        /**
+         * The mimetype of the image, e.g. "image/jpeg".
+         */
         @Json(name = "mimetype") val mimeType: String?,
+
+        /**
+         * The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file.
+         */
         @Json(name = "w") val width: Int = 0,
+
+        /**
+         * The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file.
+         */
         @Json(name = "h") val height: Int = 0,
+
+        /**
+         * Size of the image in bytes.
+         */
         @Json(name = "size") val size: Int = 0,
+
+        /**
+         * Not documented
+         */
         @Json(name = "rotation") val rotation: Int = 0,
+
+        /**
+         * Not documented
+         */
         @Json(name = "orientation") val orientation: Int = 0,
+
+        /**
+         * Metadata about the image referred to in thumbnail_url.
+         */
         @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null,
-        @Json(name = "thumbnail_url") val thumbnailUrl: String? = null
+
+        /**
+         * The URL (typically MXC URI) to a thumbnail of the image. Only present if the thumbnail is unencrypted.
+         */
+        @Json(name = "thumbnail_url") val thumbnailUrl: String? = null,
+
+        /**
+         * Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted.
+         */
+        @Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null
 )
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt
index 0f911a9bcd..f00d48826b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt
@@ -18,9 +18,22 @@ package im.vector.matrix.android.api.session.room.model.message
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
 data class LocationInfo(
+        /**
+         * The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted.
+         */
         @Json(name = "thumbnail_url") val thumbnailUrl: String? = null,
-        @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null
+
+        /**
+         * Metadata about the image referred to in thumbnail_url.
+         */
+        @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null,
+
+        /**
+         * Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted.
+         */
+        @Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null
 )
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt
index 9b33b00758..7a9ccf7ace 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt
@@ -20,13 +20,35 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import im.vector.matrix.android.api.session.events.model.Content
 import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
 data class MessageAudioContent(
+        /**
+         * Not documented
+         */
         @Json(name = "msgtype") override val type: String,
+
+        /**
+         * Required. A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'.
+         */
         @Json(name = "body") override val body: String,
-        @Json(name = "info") val info: AudioInfo? = null,
+
+        /**
+         * Metadata for the audio clip referred to in url.
+         */
+        @Json(name = "info") val audioInfo: AudioInfo? = null,
+
+        /**
+         * Required. Required if the file is not encrypted. The URL (typically MXC URI) to the audio clip.
+         */
         @Json(name = "url") val url: String? = null,
+
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
-        @Json(name = "m.new_content") override val newContent: Content? = null
-) : MessageContent
\ No newline at end of file
+        @Json(name = "m.new_content") override val newContent: Content? = null,
+
+        /**
+         * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
+         */
+        @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
+) : MessageEncyptedContent
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEncyptedContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEncyptedContent.kt
new file mode 100644
index 0000000000..3a98701c2f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEncyptedContent.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.api.session.room.model.message
+
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
+
+
+/**
+ * Interface for message which can contains encrypted data
+ */
+interface MessageEncyptedContent : MessageContent {
+    val encryptedFileInfo: EncryptedFileInfo?
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt
index 8f58294ca9..1b7f179885 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt
@@ -20,14 +20,37 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import im.vector.matrix.android.api.session.events.model.Content
 import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
 data class MessageFileContent(
+        /**
+         * Not documented
+         */
         @Json(name = "msgtype") override val type: String,
+
+        /**
+         * Required. A human-readable description of the file. This is recommended to be the filename of the original upload.
+         */
         @Json(name = "body") override val body: String,
+
+        /**
+         * The original filename of the uploaded file.
+         */
         @Json(name = "filename") val filename: String? = null,
+
+        /**
+         * Information about the file referred to in url.
+         */
         @Json(name = "info") val info: FileInfo? = null,
+
+        /**
+         * Required. Required if the file is unencrypted. The URL (typically MXC URI) to the file.
+         */
         @Json(name = "url") val url: String? = null,
+
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
-        @Json(name = "m.new_content") override val newContent: Content? = null
-) : MessageContent
\ No newline at end of file
+        @Json(name = "m.new_content") override val newContent: Content? = null,
+
+        @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
+) : MessageEncyptedContent
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt
index 2c978b97b4..50feb4841f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt
@@ -20,13 +20,36 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import im.vector.matrix.android.api.session.events.model.Content
 import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
 data class MessageImageContent(
+        /**
+         * Required. Must be 'm.image'.
+         */
         @Json(name = "msgtype") override val type: String,
+
+        /**
+         * Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
+         * or some kind of content description for accessibility e.g. 'image attachment'.
+         */
         @Json(name = "body") override val body: String,
+
+        /**
+         * Metadata about the image referred to in url.
+         */
         @Json(name = "info") val info: ImageInfo? = null,
+
+        /**
+         * Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
+         */
         @Json(name = "url") val url: String? = null,
+
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
-        @Json(name = "m.new_content") override val newContent: Content? = null
-) : MessageContent
\ No newline at end of file
+        @Json(name = "m.new_content") override val newContent: Content? = null,
+
+        /**
+         * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
+         */
+        @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
+) : MessageEncyptedContent
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt
index ddd67af9e4..deddec12a5 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt
@@ -23,10 +23,26 @@ import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultC
 
 @JsonClass(generateAdapter = true)
 data class MessageLocationContent(
+        /**
+         * Not documented
+         */
         @Json(name = "msgtype") override val type: String,
+
+        /**
+         * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'.
+         */
         @Json(name = "body") override val body: String,
+
+        /**
+         * Required. A geo URI representing this location.
+         */
         @Json(name = "geo_uri") val geoUri: String,
-        @Json(name = "info") val info: LocationInfo? = null,
+
+        /**
+         *
+         */
+        @Json(name = "info") val locationInfo: LocationInfo? = null,
+
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
         @Json(name = "m.new_content") override val newContent: Content? = null
 ) : MessageContent
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt
index 40c2994245..11845d4df7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt
@@ -20,13 +20,35 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import im.vector.matrix.android.api.session.events.model.Content
 import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
 data class MessageVideoContent(
+        /**
+         * Required. Must be 'm.video'.
+         */
         @Json(name = "msgtype") override val type: String,
+
+        /**
+         * Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
+         */
         @Json(name = "body") override val body: String,
-        @Json(name = "info") val info: VideoInfo? = null,
+
+        /**
+         * Metadata about the video clip referred to in url.
+         */
+        @Json(name = "info") val videoInfo: VideoInfo? = null,
+
+        /**
+         * Required. Required if the file is unencrypted. The URL (typically MXC URI) to the video clip.
+         */
         @Json(name = "url") val url: String? = null,
+
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
-        @Json(name = "m.new_content") override val newContent: Content? = null
-) : MessageContent
\ No newline at end of file
+        @Json(name = "m.new_content") override val newContent: Content? = null,
+
+        /**
+         * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
+         */
+        @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
+) : MessageEncyptedContent
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt
index aa68d8f037..1e308d79fa 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt
@@ -21,8 +21,23 @@ import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
 data class ThumbnailInfo(
+        /**
+         * The intended display width of the image in pixels. This may differ from the intrinsic dimensions of the image file.
+         */
         @Json(name = "w") val width: Int = 0,
+
+        /**
+         * The intended display height of the image in pixels. This may differ from the intrinsic dimensions of the image file.
+         */
         @Json(name = "h") val height: Int = 0,
+
+        /**
+         * Size of the image in bytes.
+         */
         @Json(name = "size") val size: Long = 0,
+
+        /**
+         * The mimetype of the image, e.g. "image/jpeg".
+         */
         @Json(name = "mimetype") val mimeType: String
 )
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt
index 701a62728b..0622037896 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt
@@ -18,14 +18,47 @@ package im.vector.matrix.android.api.session.room.model.message
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
 data class VideoInfo(
+        /**
+         * The mimetype of the video e.g. "video/mp4".
+         */
         @Json(name = "mimetype") val mimeType: String,
+
+        /**
+         * The width of the video in pixels.
+         */
         @Json(name = "w") val width: Int = 0,
+
+        /**
+         * The height of the video in pixels.
+         */
         @Json(name = "h") val height: Int = 0,
+
+        /**
+         * The size of the video in bytes.
+         */
         @Json(name = "size") val size: Long = 0,
+
+        /**
+         * The duration of the video in milliseconds.
+         */
         @Json(name = "duration") val duration: Int = 0,
+
+        /**
+         * Metadata about the image referred to in thumbnail_url.
+         */
         @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null,
-        @Json(name = "thumbnail_url") val thumbnailUrl: String? = null
+
+        /**
+         * The URL (typically MXC URI) to an image thumbnail of the video clip. Only present if the thumbnail is unencrypted.
+         */
+        @Json(name = "thumbnail_url") val thumbnailUrl: String? = null,
+
+        /**
+         * Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted.
+         */
+        @Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null
 )
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt
index 3e0d912f64..f483ba392d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt
@@ -15,14 +15,48 @@
  */
 package im.vector.matrix.android.internal.crypto.model.rest
 
+import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
+/**
+ * In Matrix specs: EncryptedFile
+ */
 @JsonClass(generateAdapter = true)
 data class EncryptedFileInfo(
+        /**
+         * Required. The URL to the file.
+         */
+        @Json(name = "url")
         var url: String? = null,
+
+        /**
+         * Not documented
+         */
+        @Json(name = "mimetype")
         var mimetype: String,
+
+        /**
+         * Required. A JSON Web Key object.
+         */
+        @Json(name = "key")
         var key: EncryptedFileKey? = null,
+
+        /**
+         * Required. The Initialisation Vector used by AES-CTR, encoded as unpadded base64.
+         */
+        @Json(name = "iv")
         var iv: String,
+
+        /**
+         * 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".
+         */
+        @Json(name = "hashes")
         var hashes: Map<String, String>,
+
+        /**
+         * Required. Version of the encrypted attachments protocol. Must be "v2".
+         */
+        @Json(name = "v")
         var v: String? = null
 )
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt
index 433a3619d5..3ea603c0d2 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt
@@ -15,14 +15,39 @@
  */
 package im.vector.matrix.android.internal.crypto.model.rest
 
+import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
 data class EncryptedFileKey(
+        /**
+         * Required. Algorithm. Must be "A256CTR".
+         */
+        @Json(name = "alg")
         var alg: String,
+
+        /**
+         * Required. Extractable. Must be true. This is a W3C extension.
+         */
+        @Json(name = "ext")
         var ext: Boolean? = null,
+
+        /**
+         * Required. Key operations. Must at least contain "encrypt" and "decrypt".
+         */
+        @Json(name = "key_ops")
         var key_ops: List<String>,
+
+        /**
+         * Required. Key type. Must be "oct".
+         */
+        @Json(name = "kty")
         var kty: String,
+
+        /**
+         * Required. The key, encoded as urlsafe unpadded base64.
+         */
+        @Json(name = "k")
         var k: String
 )
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
index 4c3235e150..b459d28856 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
@@ -124,7 +124,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
     }
 
     private fun MessageVideoContent.update(url: String, thumbnailUrl: String?): MessageVideoContent {
-        return copy(url = url, info = info?.copy(thumbnailUrl = thumbnailUrl))
+        return copy(url = url, videoInfo = videoInfo?.copy(thumbnailUrl = thumbnailUrl))
     }
 
     private fun MessageFileContent.update(url: String): MessageFileContent {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
index 6e80e7c65c..a5437d1f4c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt
@@ -179,7 +179,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
         val content = MessageVideoContent(
                 type = MessageType.MSGTYPE_VIDEO,
                 body = attachment.name ?: "video",
-                info = VideoInfo(
+                videoInfo = VideoInfo(
                         mimeType = attachment.mimeType,
                         width = width,
                         height = height,
@@ -198,7 +198,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
         val content = MessageAudioContent(
                 type = MessageType.MSGTYPE_AUDIO,
                 body = attachment.name ?: "audio",
-                info = AudioInfo(
+                audioInfo = AudioInfo(
                         mimeType = attachment.mimeType ?: "audio/mpeg",
                         size = attachment.size
                 ),
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index f3d40bc642..7eda1dacc2 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -220,10 +220,10 @@ class MessageItemFactory @Inject constructor(
         val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
         val thumbnailData = ImageContentRenderer.Data(
                 filename = messageContent.body,
-                url = messageContent.info?.thumbnailUrl,
-                height = messageContent.info?.height,
+                url = messageContent.videoInfo?.thumbnailUrl,
+                height = messageContent.videoInfo?.height,
                 maxHeight = maxHeight,
-                width = messageContent.info?.width,
+                width = messageContent.videoInfo?.width,
                 maxWidth = maxWidth
         )
 

From b54ca5a8a0da6cb57c29127604140e427087c18f Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 24 Jun 2019 22:33:30 +0200
Subject: [PATCH 04/12] Decrypt Attachment - WIP

---
 .../crypto/attachments/ElementToDecrypt.kt    | 46 ++++++++++
 .../MXEncryptedAttachments.kt                 | 45 +++++-----
 .../crypto/model/rest/EncryptedFileInfo.kt    | 35 +++++++-
 .../crypto/model/rest/EncryptedFileKey.kt     | 37 +++++++--
 ...eModule.java => ElementToDecryptOption.kt} | 21 ++---
 .../core/glide/MyAppGlideModule.kt            | 40 +++++++++
 .../core/glide/VectorGlideModelLoader.kt      | 83 +++++++++++++++++++
 .../timeline/factory/MessageItemFactory.kt    |  7 +-
 .../features/media/ImageContentRenderer.kt    | 16 +++-
 .../features/media/VideoContentRenderer.kt    |  1 +
 .../settings/VectorSettingsGeneralFragment.kt | 17 ++++
 .../VectorSettingsPreferencesFragment.kt      |  2 +-
 12 files changed, 302 insertions(+), 48 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/ElementToDecrypt.kt
 rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/{ => attachments}/MXEncryptedAttachments.kt (90%)
 rename vector/src/main/java/im/vector/riotredesign/core/glide/{MyAppGlideModule.java => ElementToDecryptOption.kt} (53%)
 create mode 100644 vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.kt
 create mode 100644 vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/ElementToDecrypt.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/ElementToDecrypt.kt
new file mode 100644
index 0000000000..9f1bf7085e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/ElementToDecrypt.kt
@@ -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
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
similarity index 90%
rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEncryptedAttachments.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
index 47b84af1b0..9c98d58c76 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -14,7 +14,7 @@
  * 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.util.Base64
@@ -142,24 +142,27 @@ object MXEncryptedAttachments {
      * @return the decrypted attachment stream
      */
     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
-        if (null == attachmentStream || null == encryptedFileInfo) {
-            Timber.e("## decryptAttachment() : null parameters")
-            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")
+        if (null == attachmentStream || elementToDecrypt == null) {
+            Timber.e("## decryptAttachment() : null stream")
             return null
         }
 
@@ -177,8 +180,8 @@ object MXEncryptedAttachments {
         val outStream = ByteArrayOutputStream()
 
         try {
-            val key = Base64.decode(base64UrlToBase64(encryptedFileInfo.key!!.k), Base64.DEFAULT)
-            val initVectorBytes = Base64.decode(encryptedFileInfo.iv, Base64.DEFAULT)
+            val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
+            val initVectorBytes = Base64.decode(elementToDecrypt.iv, Base64.DEFAULT)
 
             val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
             val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
@@ -205,7 +208,7 @@ object MXEncryptedAttachments {
 
             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")
                 outStream.close()
                 return null
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt
index f483ba392d..5e09b20c91 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileInfo.kt
@@ -33,7 +33,7 @@ data class EncryptedFileInfo(
          * Not documented
          */
         @Json(name = "mimetype")
-        var mimetype: String,
+        var mimetype: String? = null,
 
         /**
          * 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.
          */
         @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.
          * Clients should support the SHA-256 hash, which uses the key "sha256".
          */
         @Json(name = "hashes")
-        var hashes: Map<String, String>,
+        var hashes: Map<String, String>? = null,
 
         /**
          * Required. Version of the encrypted attachments protocol. Must be "v2".
          */
         @Json(name = "v")
         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
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt
index 3ea603c0d2..3cf1e3083a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/EncryptedFileKey.kt
@@ -24,7 +24,7 @@ data class EncryptedFileKey(
          * Required. Algorithm. Must be "A256CTR".
          */
         @Json(name = "alg")
-        var alg: String,
+        var alg: String? = null,
 
         /**
          * 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".
          */
         @Json(name = "key_ops")
-        var key_ops: List<String>,
+        var key_ops: List<String>? = null,
 
         /**
          * Required. Key type. Must be "oct".
          */
         @Json(name = "kty")
-        var kty: String,
+        var kty: String? = null,
 
         /**
          * Required. The key, encoded as urlsafe unpadded base64.
          */
         @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
+    }
+}
 
diff --git a/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.java b/vector/src/main/java/im/vector/riotredesign/core/glide/ElementToDecryptOption.kt
similarity index 53%
rename from vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.java
rename to vector/src/main/java/im/vector/riotredesign/core/glide/ElementToDecryptOption.kt
index 08fed579b1..7980502911 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.java
+++ b/vector/src/main/java/im/vector/riotredesign/core/glide/ElementToDecryptOption.kt
@@ -5,7 +5,7 @@
  * 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
+ *     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,
@@ -14,21 +14,14 @@
  * limitations under the License.
  */
 
-package im.vector.riotredesign.core.glide;
+package im.vector.riotredesign.core.glide
 
-import android.content.Context;
-import android.util.Log;
+import com.bumptech.glide.load.Option
+import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
 
-import com.bumptech.glide.GlideBuilder;
-import com.bumptech.glide.annotation.GlideModule;
-import com.bumptech.glide.module.AppGlideModule;
+const val ElementToDecryptOptionKey = "im.vector.riotx.core.glide.ElementToDecrypt"
 
-@GlideModule
-public final class MyAppGlideModule extends AppGlideModule {
 
-    @Override
-    public void applyOptions(Context context, GlideBuilder builder) {
-        builder.setLogLevel(Log.ERROR);
-    }
+val ELEMENT_TO_DECRYPT = Option.memory(
+        ElementToDecryptOptionKey, ElementToDecrypt("", "", ""))
 
-}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.kt b/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.kt
new file mode 100644
index 0000000000..8937ad4894
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.kt
@@ -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())
+    }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt
new file mode 100644
index 0000000000..8556739deb
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt
@@ -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)
+        }
+    }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 7eda1dacc2..4bb900d1b7 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -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.send.SendState
 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.R
 import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
@@ -179,7 +180,8 @@ class MessageItemFactory @Inject constructor(
         val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
         val data = ImageContentRenderer.Data(
                 filename = messageContent.body,
-                url = messageContent.url,
+                url = messageContent.encryptedFileInfo?.url ?: messageContent.url,
+                elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
                 height = messageContent.info?.height,
                 maxHeight = maxHeight,
                 width = messageContent.info?.width,
@@ -220,7 +222,8 @@ class MessageItemFactory @Inject constructor(
         val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
         val thumbnailData = ImageContentRenderer.Data(
                 filename = messageContent.body,
-                url = messageContent.videoInfo?.thumbnailUrl,
+                url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl,
+                elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
                 height = messageContent.videoInfo?.height,
                 maxHeight = maxHeight,
                 width = messageContent.videoInfo?.width,
diff --git a/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
index 1e50f90bc2..5fcb443bbd 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
@@ -20,11 +20,13 @@ 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.Matrix
 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
@@ -37,6 +39,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
     data class Data(
             val filename: String,
             val url: String?,
+            val elementToDecrypt: ElementToDecrypt?,
             val height: Int?,
             val maxHeight: Int,
             val width: Int?,
@@ -70,6 +73,15 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
         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)
+                    }
+                }
                 .dontAnimate()
                 .transform(RoundedCorners(dpToPx(8, imageView.context)))
                 .thumbnail(0.3f)
@@ -81,6 +93,8 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
         val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
         val fullSize = contentUrlResolver.resolveFullSize(data.url)
         val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
+
+        // TODO DECRYPT_FILE Decrypt file
         imageView.showImage(
                 Uri.parse(thumbnail),
                 Uri.parse(fullSize)
diff --git a/vector/src/main/java/im/vector/riotredesign/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/riotredesign/features/media/VideoContentRenderer.kt
index 8fd4b43e46..2eb5f062a3 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/media/VideoContentRenderer.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/media/VideoContentRenderer.kt
@@ -26,6 +26,7 @@ import javax.inject.Inject
 
 class VideoContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
 
+    // TODO DECRYPT_FILE Encrypted data
     @Parcelize
     data class Data(
             val filename: String,
diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsGeneralFragment.kt
index f059ef62d1..aa7ce30446 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsGeneralFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsGeneralFragment.kt
@@ -32,6 +32,7 @@ import androidx.core.view.isVisible
 import androidx.preference.EditTextPreference
 import androidx.preference.Preference
 import androidx.preference.PreferenceCategory
+import com.bumptech.glide.Glide
 import com.google.android.material.textfield.TextInputEditText
 import com.google.android.material.textfield.TextInputLayout
 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.themes.ThemeUtils
 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.util.*
 
@@ -197,6 +202,18 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
 
             it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
                 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
                 displayLoadingView()
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt
index a77f0f73d8..0a82ef9f76 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/settings/VectorSettingsPreferencesFragment.kt
@@ -119,7 +119,7 @@ class VectorSettingsPreferencesFragment : VectorSettingsBaseFragment() {
                 context?.let { context: Context ->
                     AlertDialog.Builder(context)
                             .setSingleChoiceItems(R.array.media_saving_choice,
-                                                  PreferencesManager.getSelectedMediasSavingPeriod(activity)) { d, n ->
+                                    PreferencesManager.getSelectedMediasSavingPeriod(activity)) { d, n ->
                                 PreferencesManager.setSelectedMediasSavingPeriod(activity, n)
                                 d.cancel()
 

From 164c8dab0943905574b5847a5e4b3348324ab5f7 Mon Sep 17 00:00:00 2001
From: ganfra <francois.ganard@gmail.com>
Date: Fri, 28 Jun 2019 19:31:32 +0200
Subject: [PATCH 05/12] Glide: try to handle encrypted image. [WIP]

---
 .../attachments/MXEncryptedAttachments.kt     |  9 ---
 .../core/glide/VectorGlideModelLoader.kt      | 75 ++++++++++++++-----
 .../features/media/ImageContentRenderer.kt    | 21 +-----
 3 files changed, 59 insertions(+), 46 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
index 9c98d58c76..033ce1f002 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -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()
diff --git a/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt
index 8556739deb..844309caf0 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt
+++ b/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt
@@ -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)
     }
 }
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
index 5fcb443bbd..e12ad7152f 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
@@ -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)

From 014d03893a71fc134fad025e2a4f00d9bd1629b9 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 1 Jul 2019 19:59:51 +0200
Subject: [PATCH 06/12] Fix issue after rebase and use classic request for
 clear image

---
 .../core/glide/MyAppGlideModule.kt            |  7 ++++--
 .../core/glide/VectorGlideModelLoader.kt      | 20 +++++++++------
 .../features/media/ImageContentRenderer.kt    | 25 ++++++++++++++++---
 3 files changed, 39 insertions(+), 13 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.kt b/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.kt
index 8937ad4894..54d3104c20 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.kt
+++ b/vector/src/main/java/im/vector/riotredesign/core/glide/MyAppGlideModule.kt
@@ -24,6 +24,8 @@ import com.bumptech.glide.GlideBuilder
 import com.bumptech.glide.Registry
 import com.bumptech.glide.annotation.GlideModule
 import com.bumptech.glide.module.AppGlideModule
+import im.vector.riotredesign.core.extensions.vectorComponent
+import im.vector.riotredesign.features.media.ImageContentRenderer
 import java.io.InputStream
 
 @GlideModule
@@ -34,7 +36,8 @@ class MyAppGlideModule : AppGlideModule() {
     }
 
     override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
-        // FIXME This does not work
-        registry.append(InputStream::class.java, InputStream::class.java, VectorGlideModelLoaderFactory())
+        registry.append(ImageContentRenderer.Data::class.java,
+                InputStream::class.java,
+                VectorGlideModelLoaderFactory(context.vectorComponent().activeSessionHolder()))
     }
 }
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt
index 844309caf0..dd62cedc49 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt
+++ b/vector/src/main/java/im/vector/riotredesign/core/glide/VectorGlideModelLoader.kt
@@ -24,8 +24,8 @@ 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.api.Matrix
 import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
+import im.vector.riotredesign.core.di.ActiveSessionHolder
 import im.vector.riotredesign.features.media.ImageContentRenderer
 import okhttp3.OkHttpClient
 import okhttp3.Request
@@ -37,10 +37,11 @@ import java.io.InputStream
 import com.bumptech.glide.load.engine.Resource as Resource1
 
 
-class VectorGlideModelLoaderFactory : ModelLoaderFactory<ImageContentRenderer.Data, InputStream> {
+class VectorGlideModelLoaderFactory(private val activeSessionHolder: ActiveSessionHolder)
+    : ModelLoaderFactory<ImageContentRenderer.Data, InputStream> {
 
     override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ImageContentRenderer.Data, InputStream> {
-        return VectorGlideModelLoader()
+        return VectorGlideModelLoader(activeSessionHolder)
     }
 
     override fun teardown() {
@@ -49,20 +50,23 @@ class VectorGlideModelLoaderFactory : ModelLoaderFactory<ImageContentRenderer.Da
 
 }
 
-class VectorGlideModelLoader : ModelLoader<ImageContentRenderer.Data, InputStream> {
+class VectorGlideModelLoader(private val activeSessionHolder: ActiveSessionHolder)
+    : ModelLoader<ImageContentRenderer.Data, InputStream> {
     override fun handles(model: ImageContentRenderer.Data): Boolean {
         // Always handle
         return true
     }
 
     override fun buildLoadData(model: ImageContentRenderer.Data, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? {
-        return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(model, width, height))
+        return ModelLoader.LoadData(ObjectKey(model), VectorGlideDataFetcher(activeSessionHolder, model, width, height))
     }
 }
 
-class VectorGlideDataFetcher(private val data: ImageContentRenderer.Data,
+class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolder,
+                             private val data: ImageContentRenderer.Data,
                              private val width: Int,
-                             private val height: Int) : DataFetcher<InputStream> {
+                             private val height: Int)
+    : DataFetcher<InputStream> {
 
     val client = OkHttpClient()
 
@@ -99,7 +103,7 @@ class VectorGlideDataFetcher(private val data: ImageContentRenderer.Data,
             callback.onDataReady(FileInputStream(initialFile))
             return
         }
-        val contentUrlResolver = Matrix.getInstance().currentSession?.contentUrlResolver() ?: return
+        val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
         val url = contentUrlResolver.resolveFullSize(data.url)
                 ?: return
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
index e12ad7152f..15b7e82fff 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/media/ImageContentRenderer.kt
@@ -60,9 +60,28 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
         val (width, height) = processSize(data, mode)
         imageView.layoutParams.height = height
         imageView.layoutParams.width = width
-        GlideApp
-                .with(imageView)
-                .load(data)
+
+        val glideRequest = if (data.elementToDecrypt != null) {
+            // Encrypted image
+            GlideApp
+                    .with(imageView)
+                    .load(data)
+        } else {
+            // Clear image
+            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)
+        }
+
+        glideRequest
                 .dontAnimate()
                 .transform(RoundedCorners(dpToPx(8, imageView.context)))
                 .thumbnail(0.3f)

From 0c2d3f36c3c323db18e4c87ca0050f933db84910 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit.marty@gmail.com>
Date: Tue, 2 Jul 2019 09:53:17 +0200
Subject: [PATCH 07/12] Encrypt file WIP

---
 .../attachments/MXEncryptedAttachments.kt     |   4 +-
 .../session/content/UploadContentWorker.kt    | 107 ++++++++++++++----
 .../session/room/send/DefaultSendService.kt   |   6 +-
 3 files changed, 91 insertions(+), 26 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
index 033ce1f002..7fe8242157 100755
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -42,7 +42,7 @@ object MXEncryptedAttachments {
      */
     data class EncryptionResult(
             var encryptedFileInfo: EncryptedFileInfo,
-            var encryptedStream: InputStream
+            var encryptedByteArray: ByteArray
     )
 
     /***
@@ -112,7 +112,7 @@ object MXEncryptedAttachments {
                             hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))!!),
                             v = "v2"
                     ),
-                    encryptedStream = ByteArrayInputStream(outStream.toByteArray())
+                    encryptedByteArray = outStream.toByteArray()
             )
 
             outStream.close()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
index b459d28856..68dac27642 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
@@ -25,13 +25,17 @@ import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.events.model.toContent
 import im.vector.matrix.android.api.session.events.model.toModel
 import im.vector.matrix.android.api.session.room.model.message.*
+import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments
+import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
 import im.vector.matrix.android.internal.network.ProgressRequestBody
 import im.vector.matrix.android.internal.session.room.send.SendEventWorker
 import im.vector.matrix.android.internal.worker.SessionWorkerParams
 import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 import im.vector.matrix.android.internal.worker.getSessionComponent
 import timber.log.Timber
+import java.io.ByteArrayInputStream
 import java.io.File
+import java.io.FileInputStream
 import javax.inject.Inject
 
 
@@ -42,7 +46,8 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
             override val userId: String,
             val roomId: String,
             val event: Event,
-            val attachment: ContentAttachmentData
+            val attachment: ContentAttachmentData,
+            val isRoomEncrypted: Boolean
     ) : SessionWorkerParams
 
     @Inject lateinit var fileUploader: FileUploader
@@ -58,17 +63,33 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
         val eventId = params.event.eventId ?: return Result.success()
         val attachment = params.attachment
 
+        val isRoomEncrypted = params.isRoomEncrypted
+
+
         val thumbnailData = ThumbnailExtractor.extractThumbnail(params.attachment)
         val attachmentFile = createAttachmentFile(attachment) ?: return Result.failure()
         var uploadedThumbnailUrl: String? = null
+        var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
 
         if (thumbnailData != null) {
-            fileUploader
-                    .uploadByteArray(thumbnailData.bytes, "thumb_${attachment.name}", thumbnailData.mimeType)
+            if (isRoomEncrypted) {
+                Timber.v("Encrypt thumbnail")
+                val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
+                        ?: return Result.failure()
+
+                uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
+
+                fileUploader
+                        .uploadByteArray(encryptionResult.encryptedByteArray, "thumb_${attachment.name}", thumbnailData.mimeType)
+            } else {
+                fileUploader
+                        .uploadByteArray(thumbnailData.bytes, "thumb_${attachment.name}", thumbnailData.mimeType)
+            }
                     .fold(
                             { Timber.e(it) },
                             { uploadedThumbnailUrl = it.contentUri }
                     )
+
         }
 
         val progressListener = object : ProgressRequestBody.Listener {
@@ -76,12 +97,28 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
                 contentUploadStateTracker.setProgress(eventId, current, total)
             }
         }
-        return fileUploader
-                .uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
+
+        var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
+
+        return if (isRoomEncrypted) {
+            Timber.v("Encrypt file")
+
+            val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType)
+                    ?: return Result.failure()
+
+            uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
+
+            fileUploader
+                    .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, attachment.mimeType, progressListener)
+        } else {
+            fileUploader
+                    .uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
+        }
                 .fold(
                         { handleFailure(params) },
-                        { handleSuccess(params, it.contentUri, uploadedThumbnailUrl) }
+                        { handleSuccess(params, it.contentUri, uploadedFileEncryptedFileInfo, uploadedThumbnailUrl, uploadedThumbnailEncryptedFileInfo) }
                 )
+
     }
 
     private fun createAttachmentFile(attachment: ContentAttachmentData): File? {
@@ -100,39 +137,67 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
 
     private fun handleSuccess(params: Params,
                               attachmentUrl: String,
-                              thumbnailUrl: String?): Result {
+                              encryptedFileInfo: EncryptedFileInfo?,
+                              thumbnailUrl: String?,
+                              thumbnailEncryptedFileInfo: EncryptedFileInfo?): Result {
         contentUploadStateTracker.setSuccess(params.event.eventId!!)
-        val event = updateEvent(params.event, attachmentUrl, thumbnailUrl)
+        val event = updateEvent(params.event, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
         val sendParams = SendEventWorker.Params(params.userId, params.roomId, event)
         return Result.success(WorkerParamsFactory.toData(sendParams))
     }
 
-    private fun updateEvent(event: Event, url: String, thumbnailUrl: String? = null): Event {
+    private fun updateEvent(event: Event,
+                            url: String,
+                            encryptedFileInfo: EncryptedFileInfo?,
+                            thumbnailUrl: String? = null,
+                            thumbnailEncryptedFileInfo: EncryptedFileInfo?): Event {
         val messageContent: MessageContent = event.content.toModel() ?: return event
         val updatedContent = when (messageContent) {
-            is MessageImageContent -> messageContent.update(url)
-            is MessageVideoContent -> messageContent.update(url, thumbnailUrl)
-            is MessageFileContent  -> messageContent.update(url)
-            is MessageAudioContent -> messageContent.update(url)
+            is MessageImageContent -> messageContent.update(url, encryptedFileInfo)
+            is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
+            is MessageFileContent  -> messageContent.update(url, encryptedFileInfo)
+            is MessageAudioContent -> messageContent.update(url, encryptedFileInfo)
             else                   -> messageContent
         }
         return event.copy(content = updatedContent.toContent())
     }
 
-    private fun MessageImageContent.update(url: String): MessageImageContent {
-        return copy(url = url)
+    private fun MessageImageContent.update(url: String,
+                                           encryptedFileInfo: EncryptedFileInfo?): MessageImageContent {
+        return copy(
+                url = url,
+                encryptedFileInfo = encryptedFileInfo
+        )
     }
 
-    private fun MessageVideoContent.update(url: String, thumbnailUrl: String?): MessageVideoContent {
-        return copy(url = url, videoInfo = videoInfo?.copy(thumbnailUrl = thumbnailUrl))
+    private fun MessageVideoContent.update(url: String,
+                                           encryptedFileInfo: EncryptedFileInfo?,
+                                           thumbnailUrl: String?,
+                                           thumbnailEncryptedFileInfo: EncryptedFileInfo?): MessageVideoContent {
+        return copy(
+                url = url,
+                encryptedFileInfo = encryptedFileInfo,
+                videoInfo = videoInfo?.copy(
+                        thumbnailUrl = thumbnailUrl,
+                        thumbnailFile = thumbnailEncryptedFileInfo
+                )
+        )
     }
 
-    private fun MessageFileContent.update(url: String): MessageFileContent {
-        return copy(url = url)
+    private fun MessageFileContent.update(url: String,
+                                          encryptedFileInfo: EncryptedFileInfo?): MessageFileContent {
+        return copy(
+                url = url,
+                encryptedFileInfo = encryptedFileInfo
+        )
     }
 
-    private fun MessageAudioContent.update(url: String): MessageAudioContent {
-        return copy(url = url)
+    private fun MessageAudioContent.update(url: String,
+                                           encryptedFileInfo: EncryptedFileInfo?): MessageAudioContent {
+        return copy(
+                url = url,
+                encryptedFileInfo = encryptedFileInfo
+        )
     }
 
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
index fb6f1be09d..eaf0c41475 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
@@ -101,7 +101,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
         val event = localEchoEventFactory.createMediaEvent(roomId, attachment).also {
             saveLocalEcho(it)
         }
-        val uploadWork = createUploadMediaWork(event, attachment)
+        val uploadWork = createUploadMediaWork(event, attachment, cryptoService.isRoomEncrypted(roomId))
         val sendWork = createSendEventWork(event)
 
         WorkManager.getInstance(context)
@@ -148,8 +148,8 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
         return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData)
     }
 
-    private fun createUploadMediaWork(event: Event, attachment: ContentAttachmentData): OneTimeWorkRequest {
-        val uploadMediaWorkerParams = UploadContentWorker.Params(credentials.userId, roomId, event, attachment)
+    private fun createUploadMediaWork(event: Event, attachment: ContentAttachmentData, isRoomEncrypted: Boolean): OneTimeWorkRequest {
+        val uploadMediaWorkerParams = UploadContentWorker.Params(credentials.userId, roomId, event, attachment, isRoomEncrypted)
         val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
 
         return OneTimeWorkRequestBuilder<UploadContentWorker>()

From f0e43d31f531eddb7ceba72489fbe754cebd8215 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Jul 2019 12:34:56 +0200
Subject: [PATCH 08/12] Encrypt file WIP

---
 .../internal/session/content/FileUploader.kt  |  1 -
 .../session/content/UploadContentWorker.kt    | 29 ++++++++++---------
 .../session/room/send/DefaultSendService.kt   | 23 +++++++++++----
 3 files changed, 33 insertions(+), 20 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
index 610520844f..2ec17248d1 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt
@@ -54,7 +54,6 @@ internal class FileUploader @Inject constructor(@Authenticated
 
         val uploadBody = RequestBody.create(MediaType.parse(mimeType), byteArray)
         return upload(uploadBody, filename, progressListener)
-
     }
 
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
index 68dac27642..a6bdda3c28 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
@@ -72,7 +72,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
         var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
 
         if (thumbnailData != null) {
-            if (isRoomEncrypted) {
+            val contentUploadResponse = if (isRoomEncrypted) {
                 Timber.v("Encrypt thumbnail")
                 val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType)
                         ?: return Result.failure()
@@ -85,11 +85,12 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
                 fileUploader
                         .uploadByteArray(thumbnailData.bytes, "thumb_${attachment.name}", thumbnailData.mimeType)
             }
+
+            contentUploadResponse
                     .fold(
                             { Timber.e(it) },
                             { uploadedThumbnailUrl = it.contentUri }
                     )
-
         }
 
         val progressListener = object : ProgressRequestBody.Listener {
@@ -100,7 +101,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
 
         var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
 
-        return if (isRoomEncrypted) {
+        val contentUploadResponse = if (isRoomEncrypted) {
             Timber.v("Encrypt file")
 
             val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.mimeType)
@@ -114,11 +115,12 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
             fileUploader
                     .uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
         }
+
+        return contentUploadResponse
                 .fold(
                         { handleFailure(params) },
                         { handleSuccess(params, it.contentUri, uploadedFileEncryptedFileInfo, uploadedThumbnailUrl, uploadedThumbnailEncryptedFileInfo) }
                 )
-
     }
 
     private fun createAttachmentFile(attachment: ContentAttachmentData): File? {
@@ -165,8 +167,8 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
     private fun MessageImageContent.update(url: String,
                                            encryptedFileInfo: EncryptedFileInfo?): MessageImageContent {
         return copy(
-                url = url,
-                encryptedFileInfo = encryptedFileInfo
+                url = if (encryptedFileInfo == null) url else null,
+                encryptedFileInfo = encryptedFileInfo?.copy(url = url)
         )
     }
 
@@ -175,11 +177,10 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
                                            thumbnailUrl: String?,
                                            thumbnailEncryptedFileInfo: EncryptedFileInfo?): MessageVideoContent {
         return copy(
-                url = url,
-                encryptedFileInfo = encryptedFileInfo,
+                url = if (encryptedFileInfo == null) url else null,
                 videoInfo = videoInfo?.copy(
-                        thumbnailUrl = thumbnailUrl,
-                        thumbnailFile = thumbnailEncryptedFileInfo
+                        thumbnailUrl = if (thumbnailEncryptedFileInfo == null) thumbnailUrl else null,
+                        thumbnailFile = thumbnailEncryptedFileInfo?.copy(url = url)
                 )
         )
     }
@@ -187,16 +188,16 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
     private fun MessageFileContent.update(url: String,
                                           encryptedFileInfo: EncryptedFileInfo?): MessageFileContent {
         return copy(
-                url = url,
-                encryptedFileInfo = encryptedFileInfo
+                url = if (encryptedFileInfo == null) url else null,
+                encryptedFileInfo = encryptedFileInfo?.copy(url = url)
         )
     }
 
     private fun MessageAudioContent.update(url: String,
                                            encryptedFileInfo: EncryptedFileInfo?): MessageAudioContent {
         return copy(
-                url = url,
-                encryptedFileInfo = encryptedFileInfo
+                url = if (encryptedFileInfo == null) url else null,
+                encryptedFileInfo = encryptedFileInfo?.copy(url = url)
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
index eaf0c41475..27e9c62e00 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
@@ -101,13 +101,26 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
         val event = localEchoEventFactory.createMediaEvent(roomId, attachment).also {
             saveLocalEcho(it)
         }
-        val uploadWork = createUploadMediaWork(event, attachment, cryptoService.isRoomEncrypted(roomId))
+
+        val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
+
+        val uploadWork = createUploadMediaWork(event, attachment, isRoomEncrypted)
         val sendWork = createSendEventWork(event)
 
-        WorkManager.getInstance(context)
-                .beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
-                .then(sendWork)
-                .enqueue()
+        if (isRoomEncrypted) {
+            val encryptWork = createEncryptEventWork(event)
+
+            WorkManager.getInstance(context)
+                    .beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
+                    .then(encryptWork)
+                    .then(sendWork)
+                    .enqueue()
+        } else {
+            WorkManager.getInstance(context)
+                    .beginUniqueWork(buildWorkIdentifier(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
+                    .then(sendWork)
+                    .enqueue()
+        }
 
         return CancelableWork(context, sendWork.id)
     }

From 994ee1d23ff549bd7966298c3deb53c9d4f2ca56 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Jul 2019 14:07:48 +0200
Subject: [PATCH 09/12] Encrypt file + propagate error between chained workers

---
 .../session/content/UploadContentWorker.kt    | 22 +++++++++++----
 .../session/group/GetGroupDataWorker.kt       | 12 ++++-----
 .../room/relation/DefaultRelationService.kt   | 11 ++++----
 .../room/relation/SendRelationWorker.kt       |  8 +++++-
 .../session/room/send/EncryptEventWorker.kt   | 27 +++++++++++--------
 .../session/room/send/RedactEventWorker.kt    | 12 +++++++--
 .../session/room/send/SendEventWorker.kt      | 11 ++++++--
 .../internal/worker/SessionWorkerParams.kt    |  5 +++-
 8 files changed, 74 insertions(+), 34 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
index a6bdda3c28..2597ef4e31 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt
@@ -47,7 +47,8 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
             val roomId: String,
             val event: Event,
             val attachment: ContentAttachmentData,
-            val isRoomEncrypted: Boolean
+            val isRoomEncrypted: Boolean,
+            override var lastFailureMessage: String? = null
     ) : SessionWorkerParams
 
     @Inject lateinit var fileUploader: FileUploader
@@ -57,6 +58,11 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.success()
 
+        if (params.lastFailureMessage != null) {
+            // Transmit the error
+            return Result.success(inputData)
+        }
+
         val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
         sessionComponent.inject(this)
 
@@ -110,7 +116,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
             uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
 
             fileUploader
-                    .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, attachment.mimeType, progressListener)
+                    .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
         } else {
             fileUploader
                     .uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
@@ -118,7 +124,7 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
 
         return contentUploadResponse
                 .fold(
-                        { handleFailure(params) },
+                        { handleFailure(params, it) },
                         { handleSuccess(params, it.contentUri, uploadedFileEncryptedFileInfo, uploadedThumbnailUrl, uploadedThumbnailEncryptedFileInfo) }
                 )
     }
@@ -132,9 +138,15 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters) :
         }
     }
 
-    private fun handleFailure(params: Params): Result {
+    private fun handleFailure(params: Params, failure: Throwable): Result {
         contentUploadStateTracker.setFailure(params.event.eventId!!)
-        return Result.success()
+        return Result.success(
+                WorkerParamsFactory.toData(
+                        params.copy(
+                                lastFailureMessage = failure.localizedMessage
+                        )
+                )
+        )
     }
 
     private fun handleSuccess(params: Params,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
index 4b3d645377..a619b83a4d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
@@ -20,28 +20,26 @@ import android.content.Context
 import androidx.work.CoroutineWorker
 import androidx.work.WorkerParameters
 import arrow.core.Try
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
 import com.squareup.moshi.JsonClass
-import im.vector.matrix.android.internal.worker.DelegateWorkerFactory
 import im.vector.matrix.android.internal.worker.SessionWorkerParams
 import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 import im.vector.matrix.android.internal.worker.getSessionComponent
 import javax.inject.Inject
 
-internal class GetGroupDataWorker (context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
+internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
             override val userId: String,
-            val groupIds: List<String>
-    ): SessionWorkerParams
+            val groupIds: List<String>,
+            override var lastFailureMessage: String? = null
+    ) : SessionWorkerParams
 
     @Inject lateinit var getGroupDataTask: GetGroupDataTask
 
     override suspend fun doWork(): Result {
         val params = WorkerParamsFactory.fromData<Params>(inputData)
-                     ?: return Result.failure()
+                ?: return Result.failure()
 
         val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
         sessionComponent.inject(this)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
index 8d93c8b05a..31c381f4a2 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
@@ -31,10 +31,8 @@ import im.vector.matrix.android.api.util.Cancelable
 import im.vector.matrix.android.internal.database.RealmLiveData
 import im.vector.matrix.android.internal.database.helper.addSendingEvent
 import im.vector.matrix.android.internal.database.mapper.asDomain
-import im.vector.matrix.android.internal.database.model.ChunkEntity
 import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
 import im.vector.matrix.android.internal.database.model.RoomEntity
-import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
 import im.vector.matrix.android.internal.database.query.where
 import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker
 import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
@@ -107,9 +105,12 @@ internal class DefaultRelationService @Inject constructor(private val context: C
 
     //TODO duplicate with send service?
     private fun createRedactEventWork(localEvent: Event, eventId: String, reason: String?): OneTimeWorkRequest {
-
-        val sendContentWorkerParams = RedactEventWorker.Params(credentials.userId, localEvent.eventId!!,
-                roomId, eventId, reason)
+        val sendContentWorkerParams = RedactEventWorker.Params(
+                credentials.userId,
+                localEvent.eventId!!,
+                roomId,
+                eventId,
+                reason)
         val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
         return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData)
     }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt
index 16e5f52fc0..a04c7b3ed6 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/SendRelationWorker.kt
@@ -39,7 +39,8 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
             override val userId: String,
             val roomId: String,
             val event: Event,
-            val relationType: String? = null
+            val relationType: String? = null,
+            override var lastFailureMessage: String?
     ) : SessionWorkerParams
 
     @Inject lateinit var roomAPI: RoomAPI
@@ -48,6 +49,11 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.failure()
 
+        if (params.lastFailureMessage != null) {
+            // Transmit the error
+            return Result.success(inputData)
+        }
+
         val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
         sessionComponent.inject(this)
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
index 9e68f6d13e..44046a1dea 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
@@ -41,7 +41,8 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
             val roomId: String,
             val event: Event,
             /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/
-            val keepKeys: List<String>? = null
+            val keepKeys: List<String>? = null,
+            override var lastFailureMessage: String? = null
     ) : SessionWorkerParams
 
     @Inject lateinit var crypto: CryptoService
@@ -52,6 +53,11 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.success()
 
+        if (params.lastFailureMessage != null) {
+            // Transmit the error
+            return Result.success(inputData)
+        }
+
         val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
         sessionComponent.inject(this)
 
@@ -105,16 +111,15 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
             )
             val nextWorkerParams = SendEventWorker.Params(params.userId, params.roomId, encryptedEvent)
             return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
-
+        } else {
+            val sendState = when (error) {
+                is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES
+                else                   -> SendState.UNDELIVERED
+            }
+            localEchoUpdater.updateSendState(localEvent.eventId, sendState)
+            //always return success, or the chain will be stuck for ever!
+            val nextWorkerParams = SendEventWorker.Params(params.userId, params.roomId, localEvent, error?.localizedMessage ?: "Error")
+            return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
         }
-
-        val safeError = error
-        val sendState = when (safeError) {
-            is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES
-            else                   -> SendState.UNDELIVERED
-        }
-        localEchoUpdater.updateSendState(localEvent.eventId, sendState)
-        //always return success, or the chain will be stuck for ever!
-        return Result.success()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt
index b3e2edcf19..38e0e23b6e 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt
@@ -35,7 +35,8 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
             val txID: String,
             val roomId: String,
             val eventId: String,
-            val reason: String?
+            val reason: String?,
+            override var lastFailureMessage: String? = null
     ) : SessionWorkerParams
 
     @Inject lateinit var roomAPI: RoomAPI
@@ -44,6 +45,11 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.failure()
 
+        if (params.lastFailureMessage != null) {
+            // Transmit the error
+            return Result.success(inputData)
+        }
+
         val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
         sessionComponent.inject(this)
 
@@ -62,7 +68,9 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
                 else                         -> {
                     //TODO mark as failed to send?
                     //always return success, or the chain will be stuck for ever!
-                    Result.success()
+                    Result.success(WorkerParamsFactory.toData(params.copy(
+                            lastFailureMessage = it.localizedMessage
+                    )))
                 }
             }
         }, {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
index 6b6585ef8f..315ea4574d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
@@ -38,14 +38,14 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam
     internal data class Params(
             override val userId: String,
             val roomId: String,
-            val event: Event
+            val event: Event,
+            override var lastFailureMessage: String? = null
     ) : SessionWorkerParams
 
     @Inject lateinit var localEchoUpdater: LocalEchoUpdater
     @Inject lateinit var roomAPI: RoomAPI
 
     override suspend fun doWork(): Result {
-
         val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.success()
 
@@ -57,6 +57,13 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam
             return Result.success()
         }
 
+        if (params.lastFailureMessage != null) {
+            localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED)
+
+            // Transmit the error
+            return Result.success(inputData)
+        }
+
         localEchoUpdater.updateSendState(event.eventId, SendState.SENDING)
         val result = executeRequest<SendResponse> {
             apiCall = roomAPI.send(
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt
index 874254cc2a..99fe3142dc 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/SessionWorkerParams.kt
@@ -18,4 +18,7 @@ package im.vector.matrix.android.internal.worker
 
 interface SessionWorkerParams {
     val userId: String
-}
\ No newline at end of file
+
+    // Null is no error occurs. When chaining Workers, first step is to check that there is no lastFailureMessage from the previous workers
+    var lastFailureMessage: String?
+}

From 14a2570ea45ebe14d87ec5b800b2b682171c251f Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Jul 2019 14:35:22 +0200
Subject: [PATCH 10/12] Preview of encrypted images (first and fast
 implementation)

---
 .../media/ImageMediaViewerActivity.kt         | 25 ++++++++++++++++---
 .../layout/activity_image_media_viewer.xml    |  8 ++++++
 2 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/features/media/ImageMediaViewerActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/media/ImageMediaViewerActivity.kt
index c68f594f7e..fc5e272557 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/media/ImageMediaViewerActivity.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/media/ImageMediaViewerActivity.kt
@@ -20,9 +20,11 @@ import android.content.Context
 import android.content.Intent
 import android.os.Bundle
 import androidx.appcompat.widget.Toolbar
+import androidx.core.view.isVisible
 import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator
 import com.github.piasy.biv.view.GlideImageViewFactory
 import im.vector.riotredesign.core.di.ScreenComponent
+import im.vector.riotredesign.core.glide.GlideApp
 import im.vector.riotredesign.core.platform.VectorBaseActivity
 import kotlinx.android.synthetic.main.activity_image_media_viewer.*
 import javax.inject.Inject
@@ -44,9 +46,26 @@ class ImageMediaViewerActivity : VectorBaseActivity() {
             finish()
         } else {
             configureToolbar(imageMediaViewerToolbar, mediaData)
-            imageMediaViewerImageView.setImageViewFactory(GlideImageViewFactory())
-            imageMediaViewerImageView.setProgressIndicator(ProgressPieIndicator())
-            imageContentRenderer.render(mediaData, imageMediaViewerImageView)
+
+            if (mediaData.elementToDecrypt != null) {
+                // Encrypted image
+                imageMediaViewerImageView.isVisible = false
+                encryptedImageView.isVisible = true
+
+                GlideApp
+                        .with(this)
+                        .load(mediaData)
+                        .dontAnimate()
+                        .into(encryptedImageView)
+            } else {
+                // Clear image
+                imageMediaViewerImageView.isVisible = true
+                encryptedImageView.isVisible = false
+
+                imageMediaViewerImageView.setImageViewFactory(GlideImageViewFactory())
+                imageMediaViewerImageView.setProgressIndicator(ProgressPieIndicator())
+                imageContentRenderer.render(mediaData, imageMediaViewerImageView)
+            }
         }
     }
 
diff --git a/vector/src/main/res/layout/activity_image_media_viewer.xml b/vector/src/main/res/layout/activity_image_media_viewer.xml
index 194b9d6066..61d5d286f9 100644
--- a/vector/src/main/res/layout/activity_image_media_viewer.xml
+++ b/vector/src/main/res/layout/activity_image_media_viewer.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
@@ -18,4 +19,11 @@
         app:failureImageInitScaleType="center"
         app:optimizeDisplay="true" />
 
+    <ImageView
+        android:id="@+id/encryptedImageView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        tools:visibility="visible" />
+
 </LinearLayout>
\ No newline at end of file

From 18a821f3f6548b06fc755cd403b3998583ba8ddf Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Jul 2019 14:43:07 +0200
Subject: [PATCH 11/12] add message

---
 .../features/home/room/detail/RoomDetailFragment.kt           | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
index 3deda66786..8c15900d92 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
@@ -616,11 +616,11 @@ class RoomDetailFragment :
     }
 
     override fun onFileMessageClicked(messageFileContent: MessageFileContent) {
-        vectorBaseActivity.notImplemented()
+        vectorBaseActivity.notImplemented("open file")
     }
 
     override fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) {
-        vectorBaseActivity.notImplemented()
+        vectorBaseActivity.notImplemented("open audio file")
     }
 
     override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) {

From 0980a41752377b68a3c2ea8551fc38986f49071f Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Jul 2019 14:59:44 +0200
Subject: [PATCH 12/12] use file name with extension

---
 .../java/im/vector/riotredesign/core/intent/Filename.kt     | 4 ++--
 .../features/home/room/detail/RoomDetailViewModel.kt        | 6 +++++-
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/core/intent/Filename.kt b/vector/src/main/java/im/vector/riotredesign/core/intent/Filename.kt
index eba9d01b6d..78a0471604 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/intent/Filename.kt
+++ b/vector/src/main/java/im/vector/riotredesign/core/intent/Filename.kt
@@ -21,9 +21,9 @@ import android.database.Cursor
 import android.net.Uri
 import android.provider.OpenableColumns
 
-fun getFilenameFromUri(context: Context, uri: Uri): String? {
+fun getFilenameFromUri(context: Context?, uri: Uri): String? {
     var result: String? = null
-    if (uri.scheme == "content") {
+    if (context != null && uri.scheme == "content") {
         val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
         try {
             if (cursor != null && cursor.moveToFirst()) {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
index 8d23ac0f8e..805843c6ce 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
@@ -16,6 +16,7 @@
 
 package im.vector.riotredesign.features.home.room.detail
 
+import android.net.Uri
 import android.text.TextUtils
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
@@ -36,6 +37,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageType
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
 import im.vector.matrix.rx.rx
 import im.vector.riotredesign.R
+import im.vector.riotredesign.core.intent.getFilenameFromUri
 import im.vector.riotredesign.core.platform.VectorViewModel
 import im.vector.riotredesign.core.resources.UserPreferencesProvider
 import im.vector.riotredesign.core.utils.LiveEvent
@@ -360,13 +362,15 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
 
     private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
         val attachments = action.mediaFiles.map {
+            val nameWithExtension = getFilenameFromUri(null, Uri.parse(it.path))
+
             ContentAttachmentData(
                     size = it.size,
                     duration = it.duration,
                     date = it.date,
                     height = it.height,
                     width = it.width,
-                    name = it.name,
+                    name = nameWithExtension ?: it.name,
                     path = it.path,
                     mimeType = it.mimeType,
                     type = ContentAttachmentData.Type.values()[it.mediaType]