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 99479d87..9b6e364e 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 @@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.di.MoshiProvider import timber.log.Timber import java.util.* +import kotlin.collections.HashMap typealias Content = JsonDict @@ -146,21 +147,25 @@ data class Event( val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) - } - mClearEvent?.apply { - mSenderCurve25519Key = decryptionResult.senderCurve25519Key - mClaimedEd25519Key = decryptionResult.claimedEd25519Key - mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain - try { - // Add "m.relates_to" data from e2e event to the unencrypted event - // TODO - //if (getWireContent().getAsJsonObject().has("m.relates_to")) { - // clearEvent!!.getContentAsJsonObject() - // .add("m.relates_to", getWireContent().getAsJsonObject().get("m.relates_to")) - //} - } catch (e: Exception) { - Timber.e(e, "Unable to restore 'm.relates_to' the clear event") + if (mClearEvent != null) { + mSenderCurve25519Key = decryptionResult.senderCurve25519Key + mClaimedEd25519Key = decryptionResult.claimedEd25519Key + mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain + + try { + content?.get("m.relates_to")?.let { clearRelates -> + mClearEvent = mClearEvent?.copy( + content = HashMap(mClearEvent!!.content).apply { + this["m.relates_to"] = clearRelates + } + ) + } + } catch (e: Exception) { + Timber.e(e, "Unable to restore 'm.relates_to' the clear event") + } } + + } } mCryptoError = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index cd7be990..38935f19 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -69,7 +69,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, credentials) - val relationService = DefaultRelationService(context, credentials, roomId, eventFactory, findReactionEventForUndoTask, monarchy, taskExecutor) + val relationService = DefaultRelationService(context, credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, monarchy, taskExecutor) return DefaultRoom( roomId, 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 9b07bb28..cf0b3f1c 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 @@ -21,6 +21,7 @@ import androidx.work.OneTimeWorkRequest import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.message.MessageType @@ -33,6 +34,7 @@ import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryE 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 import im.vector.matrix.android.internal.session.room.send.RedactEventWorker import im.vector.matrix.android.internal.session.room.send.SendEventWorker @@ -49,12 +51,12 @@ internal class DefaultRelationService @Inject constructor(private val context: C private val credentials: Credentials, private val roomId: String, private val eventFactory: LocalEchoEventFactory, + private val cryptoService: CryptoService, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val monarchy: Monarchy, private val taskExecutor: TaskExecutor) : RelationService { - override fun sendReaction(reaction: String, targetEventId: String): Cancelable { val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction) .also { @@ -65,13 +67,8 @@ internal class DefaultRelationService @Inject constructor(private val context: C return CancelableWork(context, sendRelationWork.id) } - private fun createSendRelationWork(event: Event): OneTimeWorkRequest { - val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) - val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) - - return TimelineSendEventWorkCommon.createWork(sendWorkData) - + return createSendEventWork(event) } override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ { @@ -119,31 +116,44 @@ internal class DefaultRelationService @Inject constructor(private val context: C val event = eventFactory.createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, MessageType.MSGTYPE_TEXT, compatibilityBodyText).also { saveLocalEcho(it) } - val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) - val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) - - //TODO use relation API? - - val workRequest = TimelineSendEventWorkCommon.createWork(sendWorkData) + val workRequest = createSendEventWork(event) TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) return CancelableWork(context, workRequest.id) } - override fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? { val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText)?.also { saveLocalEcho(it) } ?: return null - val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) - val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) + if (cryptoService.isRoomEncrypted(roomId)) { + val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) + val workRequest = createSendEventWork(event) + TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) + return CancelableWork(context, encryptWork.id) + + } else { + val workRequest = createSendEventWork(event) + TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) + return CancelableWork(context, workRequest.id) + } - val workRequest = TimelineSendEventWorkCommon.createWork(sendWorkData) - TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) - return CancelableWork(context, workRequest.id) } + private fun createEncryptEventWork(event: Event, keepKeys: List?): OneTimeWorkRequest { + // Same parameter + val params = EncryptEventWorker.Params(credentials.userId, roomId, event, keepKeys) + val sendWorkData = WorkerParamsFactory.toData(params) + return TimelineSendEventWorkCommon.createWork(sendWorkData) + } + + private fun createSendEventWork(event: Event): OneTimeWorkRequest { + val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event) + val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) + val workRequest = TimelineSendEventWorkCommon.createWork(sendWorkData) + return workRequest + } override fun getEventSummaryLive(eventId: String): LiveData> { return monarchy.findAllMappedWithChanges( 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 359eb181..f7503672 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 @@ -39,11 +39,15 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) internal data class Params( override val userId: String, val roomId: String, - val event: Event + val event: Event, + /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/ + val keepKeys: List? = null ) : SessionWorkerParams - @Inject lateinit var crypto: CryptoService - @Inject lateinit var localEchoUpdater: LocalEchoUpdater + @Inject + lateinit var crypto: CryptoService + @Inject + lateinit var localEchoUpdater: LocalEchoUpdater override fun doWork(): Result { @@ -65,8 +69,13 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) var result: MXEncryptEventContentResult? = null var error: Throwable? = null + val localMutableContent = HashMap(localEvent.content) + params.keepKeys?.forEach { + localMutableContent.remove(it) + } + try { - crypto.encryptEventContent(localEvent.content!!, localEvent.type, params.roomId, object : MatrixCallback { + crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, object : MatrixCallback { override fun onSuccess(data: MXEncryptEventContentResult) { result = data latch.countDown() @@ -83,15 +92,24 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) } latch.await() - val safeResult = result - if (safeResult != null) { + if (result != null) { + var modifiedContent = HashMap(result?.eventContent) + params.keepKeys?.forEach { toKeep -> + localEvent.content?.get(toKeep)?.let { + //put it back in the encrypted thing + modifiedContent[toKeep] = it + } + } + val safeResult = result!!.copy(eventContent = modifiedContent) val encryptedEvent = localEvent.copy( type = safeResult.eventType, content = safeResult.eventContent ) val nextWorkerParams = SendEventWorker.Params(params.userId, params.roomId, encryptedEvent) return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) + } + val safeError = error val sendState = when (safeError) { is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/JsonCanonicalizer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/JsonCanonicalizer.kt index 9c8c78a3..41dd0f7d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/JsonCanonicalizer.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/JsonCanonicalizer.kt @@ -100,7 +100,7 @@ object JsonCanonicalizer { return result.toString() } - is String -> return "\"" + src.toString() + "\"" + is String -> return JSONObject.quote(src.toString()) else -> return src.toString() } } 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 f7a3b964..081c83a6 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 @@ -247,7 +247,7 @@ class RoomDetailFragment : //TODO this is used at several places, find way to refactor? val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() - ?: event.root.content.toModel() + ?: event.root.getClearContent().toModel() val nonFormattedBody = messageContent?.body ?: "" var formattedBody: CharSequence? = null if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 30cc1d1e..e0120371 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -15,10 +15,8 @@ */ package im.vector.riotredesign.features.home.room.detail.timeline.action -import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session @@ -33,11 +31,10 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.core.utils.isSingleEmoji import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import org.json.JSONObject -import im.vector.riotredesign.core.utils.isSingleEmoji - data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null) @@ -80,11 +77,6 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M const val ACTION_FLAG = "ACTION_FLAG" const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT" const val ACTION_VIEW_REACTIONS = "ACTION_VIEW_REACTIONS" - - override fun create(viewModelContext: ViewModelContext, state: MessageMenuState): MessageMenuViewModel? { - val fragment: MessageMenuFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.messageMenuViewModelFactory.create(state) - } } init { @@ -95,7 +87,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M val event = session.getRoom(state.roomId)?.getTimeLineEvent(state.eventId) ?: return state val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() - ?: event.root.content.toModel() + ?: event.root.getClearContent().toModel() val type = messageContent?.type val actions = if (!event.sendState.isSent()) {