From 38843f74ab251981ef4303e125cce3f4bfcb2e60 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 1 Dec 2020 17:07:24 +0100 Subject: [PATCH 1/5] No need for WRITE_EXTERNAL permission to send attachment to the app (anymore?) --- .../home/room/detail/RoomDetailFragment.kt | 22 +------------------ .../home/room/detail/RoomDetailViewModel.kt | 3 --- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 93b2b69ba5..29036a91fb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1100,18 +1100,6 @@ class RoomDetailFragment @Inject constructor( } } - private val writingFileActivityResultLauncher = registerForPermissionsResult { allGranted -> - if (allGranted) { - val pendingUri = roomDetailViewModel.pendingUri - if (pendingUri != null) { - roomDetailViewModel.pendingUri = null - sendUri(pendingUri) - } - } else { - cleanUpAfterPermissionNotGranted() - } - } - private fun setupComposer() { val composerEditText = composerLayout.composerEditText autoCompleter.setup(composerEditText) @@ -1157,14 +1145,7 @@ class RoomDetailFragment @Inject constructor( } override fun onRichContentSelected(contentUri: Uri): Boolean { - // We need WRITE_EXTERNAL permission - return if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), writingFileActivityResultLauncher)) { - sendUri(contentUri) - } else { - roomDetailViewModel.pendingUri = contentUri - // Always intercept when we request some permission - true - } + return sendUri(contentUri) } } } @@ -1561,7 +1542,6 @@ class RoomDetailFragment @Inject constructor( private fun cleanUpAfterPermissionNotGranted() { // Reset all pending data roomDetailViewModel.pendingAction = null - roomDetailViewModel.pendingUri = null attachmentsHelper.pendingType = null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 7bba9728ca..6db2a9205a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -128,9 +128,6 @@ class RoomDetailViewModel @AssistedInject constructor( // Slot to keep a pending action during permission request var pendingAction: RoomDetailAction? = null - // Slot to keep a pending uri during permission request - var pendingUri: Uri? = null - // Slot to store if we want to prevent preview of attachment var preventAttachmentPreview = false From eb30b9fae9df12082b051d6cfa3bebe99c172ad8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 1 Dec 2020 17:11:05 +0100 Subject: [PATCH 2/5] Show preview when sending attachment from the keyboard (#2440) It's actually a revert of a3b205b310fa10c8a82b22c2bd3cbdd348ce92f3 --- CHANGES.md | 1 + .../home/room/detail/RoomDetailFragment.kt | 24 +++++++------------ .../home/room/detail/RoomDetailViewModel.kt | 3 --- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a4aa0b7a0f..206e48170f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Bugfix 🐛: - Fix cancellation of sending event (#2438) - Double bottomsheet effect after verify with passphrase - EditText cursor jumps to the start while typing fast (#2469) + - Show preview when sending attachment from the keyboard (#2440) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 29036a91fb..c471b90e8e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1176,11 +1176,9 @@ class RoomDetailFragment @Inject constructor( } private fun sendUri(uri: Uri): Boolean { - roomDetailViewModel.preventAttachmentPreview = true val shareIntent = Intent(Intent.ACTION_SEND, uri) val isHandled = attachmentsHelper.handleShareIntent(requireContext(), shareIntent) if (!isHandled) { - roomDetailViewModel.preventAttachmentPreview = false Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show() } return isHandled @@ -1936,24 +1934,18 @@ class RoomDetailFragment @Inject constructor( // AttachmentsHelper.Callback override fun onContentAttachmentsReady(attachments: List) { - if (roomDetailViewModel.preventAttachmentPreview) { - roomDetailViewModel.preventAttachmentPreview = false - roomDetailViewModel.handle(RoomDetailAction.SendMedia(attachments, false)) - } else { - val grouped = attachments.toGroupedContentAttachmentData() - if (grouped.notPreviewables.isNotEmpty()) { - // Send the not previewable attachments right now (?) - roomDetailViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false)) - } - if (grouped.previewables.isNotEmpty()) { - val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables)) - contentAttachmentActivityResultLauncher.launch(intent) - } + val grouped = attachments.toGroupedContentAttachmentData() + if (grouped.notPreviewables.isNotEmpty()) { + // Send the not previewable attachments right now (?) + roomDetailViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false)) + } + if (grouped.previewables.isNotEmpty()) { + val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables)) + contentAttachmentActivityResultLauncher.launch(intent) } } override fun onAttachmentsProcessFailed() { - roomDetailViewModel.preventAttachmentPreview = false Toast.makeText(requireContext(), R.string.error_attachment, Toast.LENGTH_SHORT).show() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 6db2a9205a..13362c8a2e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -128,9 +128,6 @@ class RoomDetailViewModel @AssistedInject constructor( // Slot to keep a pending action during permission request var pendingAction: RoomDetailAction? = null - // Slot to store if we want to prevent preview of attachment - var preventAttachmentPreview = false - private var trackUnreadMessages = AtomicBoolean(false) private var mostRecentDisplayedEvent: TimelineEvent? = null From 439029467ad0c65d28ed8df1fb1f4a4e5f14e4cc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 1 Dec 2020 17:37:06 +0100 Subject: [PATCH 3/5] Attachment preview also for Gif files --- .idea/dictionaries/bmarty.xml | 2 ++ .../vector/app/features/attachments/ContentAttachmentData.kt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index 5ad39614b7..16cc35cebe 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -24,6 +24,8 @@ pbkdf pids pkcs + previewable + previewables riotx signin signout diff --git a/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt b/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt index bd13c0dac4..3ca4f1b13e 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt @@ -18,10 +18,12 @@ package im.vector.app.features.attachments import org.matrix.android.sdk.api.session.content.ContentAttachmentData +private val listOfPreviewableMimeTypes = listOf("image/jpeg", "image/png", "image/jpg", "image/gif") + fun ContentAttachmentData.isPreviewable(): Boolean { // For now the preview only supports still image return type == ContentAttachmentData.Type.IMAGE - && listOf("image/jpeg", "image/png", "image/jpg").contains(getSafeMimeType() ?: "") + && listOfPreviewableMimeTypes.contains(getSafeMimeType() ?: "") } data class GroupedContentAttachmentData( From 21271b6510a0a01144f6491dfeda6137a39df8f2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 1 Dec 2020 17:55:37 +0100 Subject: [PATCH 4/5] Do not compress GIFs (#1616, #1254) --- CHANGES.md | 1 + .../sdk/internal/session/content/UploadContentWorker.kt | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 206e48170f..422d084db6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ Bugfix 🐛: - Double bottomsheet effect after verify with passphrase - EditText cursor jumps to the start while typing fast (#2469) - Show preview when sending attachment from the keyboard (#2440) + - Do not compress GIFs (#1616, #1254) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 77f39a7768..a72141e0ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -151,7 +151,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter params.attachment.size ) - if (attachment.type == ContentAttachmentData.Type.IMAGE && params.compressBeforeSending) { + if (attachment.type == ContentAttachmentData.Type.IMAGE + // Do not compress gif + && attachment.mimeType != "image/gif" + && params.compressBeforeSending) { fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE) .also { compressedFile -> // Get new Bitmap size From ca75eae0aa6ed7d9aae85679274825acf2bbf7b0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 2 Dec 2020 10:00:06 +0100 Subject: [PATCH 5/5] Create MimeTypes object --- .../session/content/ContentAttachmentData.kt | 3 +- .../room/model/message/MessageImageContent.kt | 3 +- .../matrix/android/sdk/api/util/MimeTypes.kt | 38 +++++++++++++++++++ .../session/content/ThumbnailExtractor.kt | 3 +- .../session/content/UploadContentWorker.kt | 7 ++-- .../session/profile/DefaultProfileService.kt | 3 +- .../room/create/CreateRoomBodyBuilder.kt | 3 +- .../session/room/state/DefaultStateService.kt | 3 +- .../app/core/resources/ResourceUtils.kt | 12 ++---- .../core/utils/ExternalApplicationsUtil.kt | 28 ++++++++------ .../features/attachments/AttachmentsMapper.kt | 11 ++++-- .../attachments/ContentAttachmentData.kt | 8 +++- .../attachments/preview/Extensions.kt | 6 ++- .../timeline/factory/MessageItemFactory.kt | 3 +- .../media/DataAttachmentRoomProvider.kt | 3 +- .../media/RoomEventsAttachmentProvider.kt | 3 +- .../app/features/rageshake/BugReporter.kt | 5 ++- 17 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt index 4677c2be32..4164b84ecd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt @@ -21,6 +21,7 @@ import android.os.Parcelable import androidx.exifinterface.media.ExifInterface import com.squareup.moshi.JsonClass import kotlinx.android.parcel.Parcelize +import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType @Parcelize @JsonClass(generateAdapter = true) @@ -45,5 +46,5 @@ data class ContentAttachmentData( VIDEO } - fun getSafeMimeType() = if (mimeType == "image/jpg") "image/jpeg" else mimeType + fun getSafeMimeType() = mimeType?.normalizeMimeType() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt index 859f7fd104..73e27b64e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) @@ -54,5 +55,5 @@ data class MessageImageContent( @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null ) : MessageImageInfoContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: "image/*" + get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt new file mode 100644 index 0000000000..c74999b4ab --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.util + +import org.matrix.android.sdk.api.extensions.orFalse + +// The Android SDK does not provide constant for mime type, add some of them here +object MimeTypes { + const val Any: String = "*/*" + const val OctetStream = "application/octet-stream" + + const val Images = "image/*" + + const val Png = "image/png" + const val BadJpg = "image/jpg" + const val Jpeg = "image/jpeg" + const val Gif = "image/gif" + + fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this + + fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse() + fun String?.isMimeTypeVideo() = this?.startsWith("video/").orFalse() + fun String?.isMimeTypeAudio() = this?.startsWith("audio/").orFalse() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt index 8c3aad6a1f..4b31db59b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.Bitmap import android.media.MediaMetadataRetriever import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.util.MimeTypes import timber.log.Timber import java.io.ByteArrayOutputStream @@ -58,7 +59,7 @@ internal object ThumbnailExtractor { height = thumbnailHeight, size = thumbnailSize.toLong(), bytes = outputStream.toByteArray(), - mimeType = "image/jpeg" + mimeType = MimeTypes.Jpeg ) thumbnail.recycle() outputStream.reset() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index a72141e0ab..672d407d25 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent +import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo import org.matrix.android.sdk.internal.database.mapper.ContentMapper @@ -153,7 +154,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter if (attachment.type == ContentAttachmentData.Type.IMAGE // Do not compress gif - && attachment.mimeType != "image/gif" + && attachment.mimeType != MimeTypes.Gif && params.compressBeforeSending) { fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE) .also { compressedFile -> @@ -194,7 +195,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter Timber.v("## FileService: Uploading file") fileUploader - .uploadFile(encryptedFile, attachment.name, "application/octet-stream", progressListener) + .uploadFile(encryptedFile, attachment.name, MimeTypes.OctetStream, progressListener) } else { Timber.v("## FileService: Clear file") encryptedFile = null @@ -261,7 +262,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType) val contentUploadResponse = fileUploader.uploadByteArray(encryptionResult.encryptedByteArray, "thumb_${params.attachment.name}", - "application/octet-stream", + MimeTypes.OctetStream, thumbnailProgressListener) UploadThumbnailResult( contentUploadResponse.contentUri, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index 5265e4f17d..500d43408e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.database.model.UserThreePidEntity @@ -80,7 +81,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback): Cancelable { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) { - val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg") + val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg) setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) userStore.updateAvatar(userId, response.contentUri) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt index 79ff9db087..fb840b4eb3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.toMedium import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.di.AuthenticatedIdentity @@ -96,7 +97,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( fileUploader.uploadFromUri( uri = avatarUri, filename = UUID.randomUUID().toString(), - mimeType = "image/jpeg") + mimeType = MimeTypes.Jpeg) } ?.let { response -> Event( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 6015d945c4..a93ec8e797 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask @@ -164,7 +165,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") + val response = fileUploader.uploadFromUri(avatarUri, fileName, MimeTypes.Jpeg) awaitCallback { sendStateEvent( eventType = EventType.STATE_ROOM_AVATAR, diff --git a/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt b/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt index 7ab2271c57..f14c9b834d 100644 --- a/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt +++ b/vector/src/main/java/im/vector/app/core/resources/ResourceUtils.kt @@ -20,17 +20,11 @@ import android.content.Context import android.net.Uri import android.webkit.MimeTypeMap import im.vector.app.core.utils.getFileExtension +import org.matrix.android.sdk.api.util.MimeTypes +import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType import timber.log.Timber import java.io.InputStream -/** - * Mime types - */ -const val MIME_TYPE_JPEG = "image/jpeg" -const val MIME_TYPE_JPG = "image/jpg" -const val MIME_TYPE_IMAGE_ALL = "image/*" -const val MIME_TYPE_ALL_CONTENT = "*/*" - data class Resource( var mContentStream: InputStream? = null, var mMimeType: String? = null @@ -55,7 +49,7 @@ data class Resource( * @return true if the opened resource is a jpeg one. */ fun isJpegResource(): Boolean { - return MIME_TYPE_JPEG == mMimeType || MIME_TYPE_JPG == mMimeType + return mMimeType.normalizeMimeType() == MimeTypes.Jpeg } } diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index 4c6aa51348..45db8ea91d 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -48,6 +48,10 @@ import okio.buffer import okio.sink import okio.source import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.util.MimeTypes +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeAudio +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeImage +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeVideo import timber.log.Timber import java.io.File import java.io.FileInputStream @@ -138,7 +142,7 @@ fun openFileSelection(activity: Activity, fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultipleSelection) fileIntent.addCategory(Intent.CATEGORY_OPENABLE) - fileIntent.type = "*/*" + fileIntent.type = MimeTypes.Any try { activityResultLauncher @@ -182,7 +186,7 @@ fun openCamera(activity: Activity, titlePrefix: String, requestCode: Int): Strin // The Galaxy S not only requires the name of the file to output the image to, but will also not // set the mime type of the picture it just took (!!!). We assume that the Galaxy S takes image/jpegs // so the attachment uploader doesn't freak out about there being no mimetype in the content database. - values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") + values.put(MediaStore.Images.Media.MIME_TYPE, MimeTypes.Jpeg) var dummyUri: Uri? = null try { dummyUri = activity.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) @@ -344,10 +348,10 @@ fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) } val externalContentUri = when { - mediaMimeType?.startsWith("image/") == true -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI - mediaMimeType?.startsWith("video/") == true -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI - mediaMimeType?.startsWith("audio/") == true -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - else -> MediaStore.Downloads.EXTERNAL_CONTENT_URI + mediaMimeType?.isMimeTypeImage() == true -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + mediaMimeType?.isMimeTypeVideo() == true -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + mediaMimeType?.isMimeTypeAudio() == true -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> MediaStore.Downloads.EXTERNAL_CONTENT_URI } val uri = context.contentResolver.insert(externalContentUri, values) @@ -365,7 +369,7 @@ fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String notificationUtils.buildDownloadFileNotification( uri, filename, - mediaMimeType ?: "application/octet-stream" + mediaMimeType ?: MimeTypes.OctetStream ).let { notification -> notificationUtils.showNotificationMessage("DL", uri.hashCode(), notification) } @@ -385,10 +389,10 @@ private fun saveMediaLegacy(context: Context, mediaMimeType: String?, title: Str GlobalScope.launch(Dispatchers.IO) { val dest = when { - mediaMimeType?.startsWith("image/") == true -> Environment.DIRECTORY_PICTURES - mediaMimeType?.startsWith("video/") == true -> Environment.DIRECTORY_MOVIES - mediaMimeType?.startsWith("audio/") == true -> Environment.DIRECTORY_MUSIC - else -> Environment.DIRECTORY_DOWNLOADS + mediaMimeType?.isMimeTypeImage() == true -> Environment.DIRECTORY_PICTURES + mediaMimeType?.isMimeTypeVideo() == true -> Environment.DIRECTORY_MOVIES + mediaMimeType?.isMimeTypeAudio() == true -> Environment.DIRECTORY_MUSIC + else -> Environment.DIRECTORY_DOWNLOADS } val downloadDir = Environment.getExternalStoragePublicDirectory(dest) try { @@ -405,7 +409,7 @@ private fun saveMediaLegacy(context: Context, mediaMimeType: String?, title: Str savedFile.name, title, true, - mediaMimeType ?: "application/octet-stream", + mediaMimeType ?: MimeTypes.OctetStream, savedFile.absolutePath, savedFile.length(), true) diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt index 9c9d8f8017..4e8dcaacb7 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentsMapper.kt @@ -23,6 +23,9 @@ import im.vector.lib.multipicker.entity.MultiPickerFileType import im.vector.lib.multipicker.entity.MultiPickerImageType import im.vector.lib.multipicker.entity.MultiPickerVideoType import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeAudio +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeImage +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeVideo import timber.log.Timber fun MultiPickerContactType.toContactAttachment(): ContactAttachment { @@ -59,10 +62,10 @@ fun MultiPickerAudioType.toContentAttachmentData(): ContentAttachmentData { private fun MultiPickerBaseType.mapType(): ContentAttachmentData.Type { return when { - mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE - mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO - mimeType?.startsWith("audio/") == true -> ContentAttachmentData.Type.AUDIO - else -> ContentAttachmentData.Type.FILE + mimeType?.isMimeTypeImage() == true -> ContentAttachmentData.Type.IMAGE + mimeType?.isMimeTypeVideo() == true -> ContentAttachmentData.Type.VIDEO + mimeType?.isMimeTypeAudio() == true -> ContentAttachmentData.Type.AUDIO + else -> ContentAttachmentData.Type.FILE } } diff --git a/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt b/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt index 3ca4f1b13e..e35ab96365 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/ContentAttachmentData.kt @@ -17,8 +17,14 @@ package im.vector.app.features.attachments import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.util.MimeTypes -private val listOfPreviewableMimeTypes = listOf("image/jpeg", "image/png", "image/jpg", "image/gif") +private val listOfPreviewableMimeTypes = listOf( + MimeTypes.Jpeg, + MimeTypes.BadJpg, + MimeTypes.Png, + MimeTypes.Gif +) fun ContentAttachmentData.isPreviewable(): Boolean { // For now the preview only supports still image diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt index bd06f8cf0b..853f9f8997 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/Extensions.kt @@ -17,12 +17,14 @@ package im.vector.app.features.attachments.preview import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.util.MimeTypes +import org.matrix.android.sdk.api.util.MimeTypes.isMimeTypeImage /** * All images are editable, expect Gif */ fun ContentAttachmentData.isEditable(): Boolean { return type == ContentAttachmentData.Type.IMAGE - && getSafeMimeType()?.startsWith("image/") == true - && getSafeMimeType() != "image/gif" + && getSafeMimeType()?.isMimeTypeImage() == true + && getSafeMimeType() != MimeTypes.Gif } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 34086043da..2a98fd2dd7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -88,6 +88,7 @@ import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent +import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import javax.inject.Inject @@ -311,7 +312,7 @@ class MessageItemFactory @Inject constructor( .leftGuideline(avatarSizeProvider.leftGuideline) .imageContentRenderer(imageContentRenderer) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) - .playable(messageContent.info?.mimeType == "image/gif") + .playable(messageContent.info?.mimeType == MimeTypes.Gif) .highlighted(highlight) .mediaData(data) .apply { diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 584b13f32b..328d8f943e 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.util.MimeTypes import java.io.File class DataAttachmentRoomProvider( @@ -38,7 +39,7 @@ class DataAttachmentRoomProvider( return getItem(position).let { when (it) { is ImageContentRenderer.Data -> { - if (it.mimeType == "image/gif") { + if (it.mimeType == MimeTypes.Gif) { AttachmentInfo.AnimatedImage( uid = it.eventId, url = it.url ?: "", diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 569d006fba..53c5dac9ad 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File @@ -56,7 +57,7 @@ class RoomEventsAttachmentProvider( allowNonMxcUrls = it.root.sendState.isSending() ) - if (content.mimeType == "image/gif") { + if (content.mimeType == MimeTypes.Gif) { AttachmentInfo.AnimatedImage( uid = it.eventId, url = content.url ?: "", diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 96248187aa..7be7624a48 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -46,6 +46,7 @@ import okhttp3.Response import org.json.JSONException import org.json.JSONObject import org.matrix.android.sdk.api.Matrix +import org.matrix.android.sdk.api.util.MimeTypes import timber.log.Timber import java.io.File import java.io.IOException @@ -274,7 +275,7 @@ class BugReporter @Inject constructor( // add the gzipped files for (file in gzippedFiles) { - builder.addFormDataPart("compressed-log", file.name, file.asRequestBody("application/octet-stream".toMediaTypeOrNull())) + builder.addFormDataPart("compressed-log", file.name, file.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull())) } mBugReportFiles.addAll(gzippedFiles) @@ -295,7 +296,7 @@ class BugReporter @Inject constructor( } builder.addFormDataPart("file", - logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody("application/octet-stream".toMediaTypeOrNull())) + logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody(MimeTypes.OctetStream.toMediaTypeOrNull())) } catch (e: Exception) { Timber.e(e, "## sendBugReport() : fail to write screenshot$e") }