diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index de85438b1c..b8e536cb33 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -110,13 +110,13 @@ interface SendService { * Schedule this message to be resent * @param localEcho the unsent local echo */ - fun resendTextMessage(localEcho: TimelineEvent): Cancelable? + fun resendTextMessage(localEcho: TimelineEvent): Cancelable /** * Schedule this message to be resent * @param localEcho the unsent local echo */ - fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? + fun resendMediaMessage(localEcho: TimelineEvent): Cancelable /** * Remove this failed message from the timeline @@ -124,8 +124,14 @@ interface SendService { */ fun deleteFailedEcho(localEcho: TimelineEvent) + /** + * Delete all the events in one of the sending states + */ fun clearSendingQueue() + /** + * Cancel sending a specific event. It has to be in one of the sending states + */ fun cancelSend(eventId: String) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt index 5672d195cc..df1d39c250 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -20,6 +20,9 @@ package org.matrix.android.sdk.internal.crypto.attachments import android.util.Base64 import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey +import org.matrix.android.sdk.internal.util.base64ToBase64Url +import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64 +import org.matrix.android.sdk.internal.util.base64UrlToBase64 import timber.log.Timber import java.io.ByteArrayOutputStream import java.io.File @@ -226,8 +229,8 @@ internal object MXEncryptedAttachments { /** * Decrypt an attachment * - * @param attachmentStream the attachment stream. Will be closed after this method call. - * @param encryptedFileInfo the encryption file info + * @param attachmentStream the attachment stream. Will be closed after this method call. + * @param elementToDecrypt the element to decrypt the file * @return the decrypted attachment stream */ fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt): InputStream? { @@ -254,7 +257,8 @@ internal object MXEncryptedAttachments { * * @param attachmentStream the attachment stream. Will be closed after this method call. * @param elementToDecrypt the elementToDecrypt info - * @return the decrypted attachment stream + * @param outputStream the outputStream where the decrypted attachment will be write. + * @return true in case of success, false in case of error */ fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?, outputStream: OutputStream): Boolean { // sanity checks @@ -310,25 +314,4 @@ internal object MXEncryptedAttachments { return false } - - /** - * Base64 URL conversion methods - */ - - private fun base64UrlToBase64(base64Url: String): String { - return base64Url.replace('-', '+') - .replace('_', '/') - } - - internal fun base64ToBase64Url(base64: String): String { - return base64.replace("\n".toRegex(), "") - .replace("\\+".toRegex(), "-") - .replace('/', '_') - .replace("=", "") - } - - internal fun base64ToUnpaddedBase64(base64: String): String { - return base64.replace("\n".toRegex(), "") - .replace("=", "") - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MatrixDigestCheckInputStream.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MatrixDigestCheckInputStream.kt index 01de479ff5..7ca5158f64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MatrixDigestCheckInputStream.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MatrixDigestCheckInputStream.kt @@ -17,14 +17,18 @@ package org.matrix.android.sdk.internal.crypto.attachments import android.util.Base64 +import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64 import java.io.FilterInputStream import java.io.IOException import java.io.InputStream import java.security.MessageDigest -class MatrixDigestCheckInputStream(`in`: InputStream?, val expectedDigest: String) : FilterInputStream(`in`) { +class MatrixDigestCheckInputStream( + inputStream: InputStream?, + private val expectedDigest: String +) : FilterInputStream(inputStream) { - val digest = MessageDigest.getInstance("SHA-256") + private val digest = MessageDigest.getInstance("SHA-256") @Throws(IOException::class) override fun read(): Int { @@ -57,7 +61,7 @@ class MatrixDigestCheckInputStream(`in`: InputStream?, val expectedDigest: Strin @Throws(IOException::class) private fun ensureDigest() { - val currentDigestValue = MXEncryptedAttachments.base64ToUnpaddedBase64(Base64.encodeToString(digest.digest(), Base64.DEFAULT)) + val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(digest.digest(), Base64.DEFAULT)) if (currentDigestValue != expectedDigest) { throw IOException("Bad digest") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt index ac33c2666f..45d7d48a18 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.identity.FoundThreePid import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.toMedium -import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments.base64ToBase64Url import org.matrix.android.sdk.internal.crypto.tools.withOlmUtility import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest @@ -32,6 +31,7 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityHashDetail import org.matrix.android.sdk.internal.session.identity.model.IdentityLookUpParams import org.matrix.android.sdk.internal.session.identity.model.IdentityLookUpResponse import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.base64ToBase64Url import java.util.Locale import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/CancelSendTracker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/CancelSendTracker.kt index fe3aad245a..0b79b93cf6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/CancelSendTracker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/CancelSendTracker.kt @@ -1,5 +1,6 @@ /* * Copyright (c) 2020 New Vector Ltd + * 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. @@ -28,7 +29,7 @@ import javax.inject.Inject * Known limitation, for now requests are not persisted */ @SessionScope -class CancelSendTracker @Inject constructor() { +internal class CancelSendTracker @Inject constructor() { data class Request( val localId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index eca0778401..95cd1c699c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -24,17 +24,28 @@ import androidx.work.OneTimeWorkRequest import androidx.work.Operation import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isTextMessage +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent +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.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.OptionItem +import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.CancelableBag import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.session.content.UploadContentWorker @@ -44,16 +55,6 @@ import org.matrix.android.sdk.internal.util.CancelableWork import org.matrix.android.sdk.internal.worker.AlwaysSuccessfulWorker import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.android.sdk.internal.worker.startChain -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent -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.session.room.model.message.MessageWithAttachmentContent -import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import timber.log.Timber import java.util.concurrent.Executors import java.util.concurrent.TimeUnit @@ -137,29 +138,28 @@ internal class DefaultSendService @AssistedInject constructor( .let { timelineSendEventWorkCommon.postWork(roomId, it) } } - override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? { + override fun resendTextMessage(localEcho: TimelineEvent): Cancelable { if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) { localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT) return sendEvent(localEcho.root) } - return null + return NoOpCancellable } - override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? { + override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable { if (localEcho.root.sendState.hasFailed()) { - // TODO this need a refactoring of attachement sending val clearContent = localEcho.root.getClearContent() - val messageContent = clearContent?.toModel() as? MessageWithAttachmentContent ?: return null + val messageContent = clearContent?.toModel() as? MessageWithAttachmentContent ?: return NoOpCancellable - val url = messageContent.getFileUrl() ?: return null + val url = messageContent.getFileUrl() ?: return NoOpCancellable if (url.startsWith("mxc://")) { // We need to resend only the message as the attachment is ok localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT) return sendEvent(localEcho.root) } - // we need to resend the media - when (messageContent) { + // we need to resend the media + return when (messageContent) { is MessageImageContent -> { // The image has not yet been sent val attachmentData = ContentAttachmentData( @@ -172,7 +172,7 @@ internal class DefaultSendService @AssistedInject constructor( type = ContentAttachmentData.Type.IMAGE ) localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT) - return internalSendMedia(listOf(localEcho.root), attachmentData, true) + internalSendMedia(listOf(localEcho.root), attachmentData, true) } is MessageVideoContent -> { val attachmentData = ContentAttachmentData( @@ -186,9 +186,9 @@ internal class DefaultSendService @AssistedInject constructor( type = ContentAttachmentData.Type.VIDEO ) localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT) - return internalSendMedia(listOf(localEcho.root), attachmentData, true) + internalSendMedia(listOf(localEcho.root), attachmentData, true) } - is MessageFileContent -> { + is MessageFileContent -> { val attachmentData = ContentAttachmentData( size = messageContent.info!!.size, mimeType = messageContent.info.mimeType!!, @@ -197,7 +197,7 @@ internal class DefaultSendService @AssistedInject constructor( type = ContentAttachmentData.Type.FILE ) localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT) - return internalSendMedia(listOf(localEcho.root), attachmentData, true) + internalSendMedia(listOf(localEcho.root), attachmentData, true) } is MessageAudioContent -> { val attachmentData = ContentAttachmentData( @@ -209,12 +209,12 @@ internal class DefaultSendService @AssistedInject constructor( type = ContentAttachmentData.Type.AUDIO ) localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT) - return internalSendMedia(listOf(localEcho.root), attachmentData, true) + internalSendMedia(listOf(localEcho.root), attachmentData, true) } + else -> NoOpCancellable } - return null } - return null + return NoOpCancellable } override fun deleteFailedEcho(localEcho: TimelineEvent) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Base64.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Base64.kt new file mode 100644 index 0000000000..76e24c4e31 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Base64.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * 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.internal.util + +/** + * Base64 URL conversion methods + */ + +internal fun base64UrlToBase64(base64Url: String): String { + return base64Url.replace('-', '+') + .replace('_', '/') +} + +internal fun base64ToBase64Url(base64: String): String { + return base64.replace("\n".toRegex(), "") + .replace("\\+".toRegex(), "-") + .replace('/', '_') + .replace("=", "") +} + +internal fun base64ToUnpaddedBase64(base64: String): String { + return base64.replace("\n".toRegex(), "") + .replace("=", "") +} 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 f29ff4c330..ddb21b9f2f 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 @@ -1076,12 +1076,12 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handleCancel(action: RoomDetailAction.CancelSend) { val targetEventId = action.eventId room.getTimeLineEvent(targetEventId)?.let { - // State must be UNDELIVERED or Failed + // State must be in one of the sending states if (!it.root.sendState.isSending()) { - Timber.e("Cannot resend message, it is not failed, Cancel first") + Timber.e("Cannot cancel message, it is not sending") return } - room.cancelSend(action.eventId) + room.cancelSend(targetEventId) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 80d968670a..a49b74c243 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -51,6 +51,7 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap +import java.util.ArrayList /** * Information related to an event and used to display preview in contextual bottom sheet. @@ -232,12 +233,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } add(EventSharedAction.Remove(eventId)) if (vectorPreferences.developerMode()) { - add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent())) - if (timelineEvent.isEncrypted() && timelineEvent.root.mxDecryptionResult != null) { - val decryptedContent = timelineEvent.root.toClearContentStringWithIndent() - ?: stringProvider.getString(R.string.encryption_information_decryption_error) - add(EventSharedAction.ViewDecryptedSource(decryptedContent)) - } + addViewSourceItems(timelineEvent) } } else if (timelineEvent.root.sendState.isSending()) { // TODO is uploading attachment? @@ -307,13 +303,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted add(EventSharedAction.ReRequestKey(timelineEvent.eventId)) } } - - add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent())) - if (timelineEvent.isEncrypted() && timelineEvent.root.mxDecryptionResult != null) { - val decryptedContent = timelineEvent.root.toClearContentStringWithIndent() - ?: stringProvider.getString(R.string.encryption_information_decryption_error) - add(EventSharedAction.ViewDecryptedSource(decryptedContent)) - } + addViewSourceItems(timelineEvent) } add(EventSharedAction.CopyPermalink(eventId)) if (session.myUserId != timelineEvent.root.senderId) { @@ -329,6 +319,15 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } + private fun ArrayList.addViewSourceItems(timelineEvent: TimelineEvent) { + add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent())) + if (timelineEvent.isEncrypted() && timelineEvent.root.mxDecryptionResult != null) { + val decryptedContent = timelineEvent.root.toClearContentStringWithIndent() + ?: stringProvider.getString(R.string.encryption_information_decryption_error) + add(EventSharedAction.ViewDecryptedSource(decryptedContent)) + } + } + private fun canCancel(@Suppress("UNUSED_PARAMETER") event: TimelineEvent): Boolean { return true }