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<RoomDetailViewState>(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,