From 53572a3be6b6f3eeb17b02e5d1ce2745b9370e47 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Sep 2019 11:41:22 +0200 Subject: [PATCH 1/4] Fix crash observed on the PlayStore --- .../room/detail/timeline/action/MessageMenuViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index a39ad0feca..525bd6ff83 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -45,7 +45,7 @@ sealed class SimpleAction(@StringRes val titleRes: Int, @DrawableRes val iconRes data class Edit(val eventId: String) : SimpleAction(R.string.edit, R.drawable.ic_edit) data class Quote(val eventId: String) : SimpleAction(R.string.quote, R.drawable.ic_quote) data class Reply(val eventId: String) : SimpleAction(R.string.reply, R.drawable.ic_reply) - data class Share(val imageUrl: String?) : SimpleAction(R.string.share, R.drawable.ic_share) + data class Share(val imageUrl: String) : SimpleAction(R.string.share, R.drawable.ic_share) data class Resend(val eventId: String) : SimpleAction(R.string.global_retry, R.drawable.ic_refresh_cw) data class Remove(val eventId: String) : SimpleAction(R.string.remove, R.drawable.ic_trash) data class Delete(val eventId: String) : SimpleAction(R.string.delete, R.drawable.ic_delete) @@ -166,7 +166,9 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M if (canShare(type)) { if (messageContent is MessageImageContent) { - add(SimpleAction.Share(session.contentUrlResolver().resolveFullSize(messageContent.url))) + session.contentUrlResolver().resolveFullSize(messageContent.url)?.let { url -> + add(SimpleAction.Share(url)) + } } //TODO } From f34f28b668a4b70cc10bf96c9fa45717d16da08d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Sep 2019 15:04:07 +0200 Subject: [PATCH 2/4] Add Exif orientation info to ContentAttachmentData --- matrix-sdk-android/build.gradle | 3 + .../session/content/ContentAttachmentData.kt | 2 + .../room/send/LocalEchoEventFactory.kt | 1 + .../vector/riotx/core/di/VectorComponent.kt | 3 + .../im/vector/riotx/core/images/ImageTools.kt | 76 +++++++++++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 12 ++- 6 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 2781875f5f..328cfbee20 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -118,6 +118,9 @@ dependencies { implementation "ru.noties.markwon:core:$markwon_version" + // Image + implementation 'androidx.exifinterface:exifinterface:1.0.0' + // Database implementation 'com.github.Zhuinden:realm-monarchy:0.5.1' kapt 'dk.ilios:realmfieldnameshelper:1.1.1' diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt index c8dca8692c..466b7eede3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.content import android.os.Parcelable +import androidx.exifinterface.media.ExifInterface import kotlinx.android.parcel.Parcelize @Parcelize @@ -26,6 +27,7 @@ data class ContentAttachmentData( val date: Long = 0, val height: Long? = 0, val width: Long? = 0, + val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED, val name: String? = null, val path: String, val mimeType: String, 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 ffc539471b..519a686570 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 @@ -180,6 +180,7 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use mimeType = attachment.mimeType, width = attachment.width?.toInt() ?: 0, height = attachment.height?.toInt() ?: 0, + orientation = attachment.exifOrientation, size = attachment.size.toInt() ), url = attachment.path diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index 4aab312e2f..d896ce252b 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.EmojiCompatWrapper import im.vector.riotx.VectorApplication +import im.vector.riotx.core.images.ImageTools import im.vector.riotx.core.pushers.PushersManager import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.configuration.VectorConfiguration @@ -66,6 +67,8 @@ interface VectorComponent { fun dimensionConverter(): DimensionConverter + fun imageTools(): ImageTools + fun vectorConfiguration(): VectorConfiguration fun avatarRenderer(): AvatarRenderer diff --git a/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt b/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt new file mode 100644 index 0000000000..6ada4e95fb --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt @@ -0,0 +1,76 @@ +/* + * 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.riotx.core.images + +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.provider.MediaStore +import androidx.exifinterface.media.ExifInterface +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ImageTools @Inject constructor(private val context: Context) { + + + /** + * Gets the [ExifInterface] value for the orientation for this local bitmap Uri. + * + * @param uri The URI to find the orientation for. Must be local. + * @return The orientation value, which may be [ExifInterface.ORIENTATION_UNDEFINED]. + */ + fun getOrientationForBitmap(uri: Uri): Int { + var orientation = ExifInterface.ORIENTATION_UNDEFINED + + if (uri.scheme == "content") { + val proj = arrayOf(MediaStore.Images.Media.DATA) + var cursor: Cursor? = null + try { + cursor = context.contentResolver.query(uri, proj, null, null, null) + if (cursor != null && cursor.count > 0) { + cursor.moveToFirst() + val idxData = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) + val path = cursor.getString(idxData) + if (path.isNullOrBlank()) { + Timber.w("Cannot find path in media db for uri $uri") + return orientation + } + val exif = ExifInterface(path) + orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED) + } + } catch (e: Exception) { + // eg SecurityException from com.google.android.apps.photos.content.GooglePhotosImageProvider URIs + // eg IOException from trying to parse the returned path as a file when it is an http uri. + Timber.e(e, "Cannot get orientation for bitmap") + } finally { + cursor?.close() + } + } else if (uri.scheme == "file") { + try { + val exif = ExifInterface(uri.path) + orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED) + } catch (e: Exception) { + Timber.e(e, "Cannot get EXIF for file uri $uri") + } + + } + + return orientation + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 4b8e46b0d9..1da0e51d44 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -54,6 +54,7 @@ import im.vector.matrix.rx.unwrap import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.extensions.postLiveEvent +import im.vector.riotx.core.images.ImageTools import im.vector.riotx.core.intent.getFilenameFromUri import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.UserPreferencesProvider @@ -76,6 +77,7 @@ import java.util.concurrent.TimeUnit class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState, private val userPreferencesProvider: UserPreferencesProvider, private val vectorPreferences: VectorPreferences, + private val imageTools: ImageTools, private val session: Session ) : VectorViewModel(initialState) { @@ -470,7 +472,14 @@ 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)) + val pathWithScheme = if (it.path.startsWith("/")) { + "file://" + it.path + } else { + it.path + } + + val uri = Uri.parse(pathWithScheme) + val nameWithExtension = getFilenameFromUri(null, uri) ContentAttachmentData( size = it.size, @@ -478,6 +487,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro date = it.date, height = it.height, width = it.width, + exifOrientation = imageTools.getOrientationForBitmap(uri), name = nameWithExtension ?: it.name, path = it.path, mimeType = it.mimeType, From 3f9b7813bc0cebe5900850bdf71f3b7ed0b0968d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Sep 2019 15:40:59 +0200 Subject: [PATCH 3/4] Remove undocumented attribute and fix issue with image size when it contains exif rotation --- CHANGES.md | 1 + .../session/room/model/message/ImageInfo.kt | 10 --------- .../room/send/LocalEchoEventFactory.kt | 20 +++++++++++++++--- .../timeline/factory/MessageItemFactory.kt | 4 +--- .../features/media/ImageContentRenderer.kt | 21 +++---------------- 5 files changed, 22 insertions(+), 34 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index edaa8b3c01..58daa4b8e4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ Bugfix: - Fix opening a permalink: the targeted event is displayed twice (#556) - Fix opening a permalink paginates all the history up to the last event (#282) - after login, the icon in the top left is a green 'A' for (all communities) rather than my avatar (#267) + - Picture uploads are unreliable, pictures are shown in wrong aspect ratio on desktop client (#517) Translations: - 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 729bc604c1..40651d0aa8 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 @@ -42,16 +42,6 @@ data class ImageInfo( */ @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. */ 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 519a686570..9ed2dbad97 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 @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room.send import android.media.MediaMetadataRetriever +import androidx.exifinterface.media.ExifInterface import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.R import im.vector.matrix.android.api.permalinks.PermalinkFactory @@ -173,14 +174,27 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use private fun createImageEvent(roomId: String, attachment: ContentAttachmentData): Event { + var width = attachment.width + var height = attachment.height + + when (attachment.exifOrientation) { + ExifInterface.ORIENTATION_ROTATE_90, + ExifInterface.ORIENTATION_TRANSVERSE, + ExifInterface.ORIENTATION_ROTATE_270, + ExifInterface.ORIENTATION_TRANSPOSE -> { + val tmp = width + width = height + height = tmp + } + } + val content = MessageImageContent( type = MessageType.MSGTYPE_IMAGE, body = attachment.name ?: "image", info = ImageInfo( mimeType = attachment.mimeType, - width = attachment.width?.toInt() ?: 0, - height = attachment.height?.toInt() ?: 0, - orientation = attachment.exifOrientation, + width = width?.toInt() ?: 0, + height = height?.toInt() ?: 0, size = attachment.size.toInt() ), url = attachment.path diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9e67ffb590..a654973899 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -189,9 +189,7 @@ class MessageItemFactory @Inject constructor( height = messageContent.info?.height, maxHeight = maxHeight, width = messageContent.info?.width, - maxWidth = maxWidth, - orientation = messageContent.info?.orientation, - rotation = messageContent.info?.rotation + maxWidth = maxWidth ) return MessageImageVideoItem_() .attributes(attributes) diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt index 3fb559ee6d..5f65f15d42 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt @@ -20,7 +20,6 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.os.Parcelable import android.widget.ImageView -import androidx.exifinterface.media.ExifInterface import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.resource.bitmap.RoundedCorners @@ -49,9 +48,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: val height: Int?, val maxHeight: Int, val width: Int?, - val maxWidth: Int, - val orientation: Int? = null, - val rotation: Int? = null + val maxWidth: Int ) : Parcelable { fun isLocalFile() = url.isLocalFile() @@ -152,26 +149,14 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: private fun processSize(data: Data, mode: Mode): Pair { val maxImageWidth = data.maxWidth val maxImageHeight = data.maxHeight - val rotationAngle = data.rotation ?: 0 - val orientation = data.orientation ?: ExifInterface.ORIENTATION_NORMAL - var width = data.width ?: maxImageWidth - var height = data.height ?: maxImageHeight + val width = data.width ?: maxImageWidth + val height = data.height ?: maxImageHeight var finalHeight = -1 var finalWidth = -1 // if the image size is known // compute the expected height if (width > 0 && height > 0) { - // swap width and height if the image is side oriented - if (rotationAngle == 90 || rotationAngle == 270) { - val tmp = width - width = height - height = tmp - } else if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) { - val tmp = width - width = height - height = tmp - } if (mode == Mode.FULL_SIZE) { finalHeight = height finalWidth = width From 2cd1d697fe0a882015050e1de87b70f0b8057c2f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Oct 2019 10:49:58 +0200 Subject: [PATCH 4/4] Cleanup after Gafnra's review --- .../src/main/java/im/vector/riotx/core/di/VectorComponent.kt | 3 --- vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt | 2 -- 2 files changed, 5 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index d896ce252b..4aab312e2f 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.Session import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.EmojiCompatWrapper import im.vector.riotx.VectorApplication -import im.vector.riotx.core.images.ImageTools import im.vector.riotx.core.pushers.PushersManager import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.configuration.VectorConfiguration @@ -67,8 +66,6 @@ interface VectorComponent { fun dimensionConverter(): DimensionConverter - fun imageTools(): ImageTools - fun vectorConfiguration(): VectorConfiguration fun avatarRenderer(): AvatarRenderer diff --git a/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt b/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt index 6ada4e95fb..a2257117df 100644 --- a/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt +++ b/vector/src/main/java/im/vector/riotx/core/images/ImageTools.kt @@ -23,9 +23,7 @@ import android.provider.MediaStore import androidx.exifinterface.media.ExifInterface import timber.log.Timber import javax.inject.Inject -import javax.inject.Singleton -@Singleton class ImageTools @Inject constructor(private val context: Context) {