diff --git a/changelog.d/7967.feature b/changelog.d/7967.feature new file mode 100644 index 0000000000..9b4ba43e2b --- /dev/null +++ b/changelog.d/7967.feature @@ -0,0 +1 @@ +[Voice Broadcast] Use MSC3912 to delete server side all the related events diff --git a/changelog.d/7988.sdk b/changelog.d/7988.sdk new file mode 100644 index 0000000000..3c5f5d40d9 --- /dev/null +++ b/changelog.d/7988.sdk @@ -0,0 +1 @@ +Implement [MSC3912](https://github.com/matrix-org/matrix-spec-proposals/pull/3912): Relation-based redactions diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index 11638837cc..96e52469c3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -75,6 +75,11 @@ data class HomeServerCapabilities( * True if the home server supports remote toggle of Pusher for a given device. */ val canRemotelyTogglePushNotificationsOfDevices: Boolean = false, + + /** + * True if the home server supports event redaction with relations. + */ + var canRedactEventWithRelations: Boolean = false, ) { enum class RoomCapabilitySupport { 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 6a6fadc95a..07036f4b65 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 @@ -156,11 +156,12 @@ interface SendService { /** * Redact (delete) the given event. - * @param event The event to redact - * @param reason Optional reason string + * @param event the event to redact + * @param reason optional reason string + * @param withRelations the list of relation types to redact with this event * @param additionalContent additional content to put in the event content */ - fun redactEvent(event: Event, reason: String?, additionalContent: Content? = null): Cancelable + fun redactEvent(event: Event, reason: String?, withRelations: List? = null, additionalContent: Content? = null): Cancelable /** * Schedule this message to be resent. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index f4de6a9ae9..4d8e90cf35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -58,6 +58,8 @@ private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882" private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771" private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773" private const val FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881 = "org.matrix.msc3881" +private const val FEATURE_EVENT_REDACTION_WITH_RELATIONS = "org.matrix.msc3912" +private const val FEATURE_EVENT_REDACTION_WITH_RELATIONS_STABLE = "org.matrix.msc3912.stable" /** * Return true if the SDK supports this homeserver version. @@ -153,3 +155,13 @@ private fun Versions.getMaxVersion(): HomeServerVersion { internal fun Versions.doesServerSupportRemoteToggleOfPushNotifications(): Boolean { return unstableFeatures?.get(FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881).orFalse() } + +/** + * Indicate if the server supports MSC3912: https://github.com/matrix-org/matrix-spec-proposals/pull/3912. + * + * @return true if event redaction with relations is supported + */ +internal fun Versions.doesServerSupportRedactEventWithRelations(): Boolean { + return unstableFeatures?.get(FEATURE_EVENT_REDACTION_WITH_RELATIONS).orFalse() || + unstableFeatures?.get(FEATURE_EVENT_REDACTION_WITH_RELATIONS_STABLE).orFalse() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt index 56bdc8cae8..b060748a61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt @@ -15,9 +15,12 @@ */ package org.matrix.android.sdk.internal.crypto.tasks +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -26,22 +29,34 @@ internal interface RedactEventTask : Task { val txID: String, val roomId: String, val eventId: String, - val reason: String? + val reason: String?, + val withRelations: List?, ) } internal class DefaultRedactEventTask @Inject constructor( private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, ) : RedactEventTask { override suspend fun execute(params: RedactEventTask.Params): String { + val withRelations = if (homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.canRedactEventWithRelations.orFalse() && + !params.withRelations.isNullOrEmpty()) { + params.withRelations + } else { + null + } + val response = executeRequest(globalErrorReceiver) { roomAPI.redactEvent( txId = params.txID, roomId = params.roomId, eventId = params.eventId, - reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) + body = EventRedactBody( + reason = params.reason, + withRelations = withRelations, + ) ) } return response.eventId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 2b7e9a04a1..fe55beb997 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -65,6 +65,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -73,7 +74,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 48L, + schemaVersion = 49L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -131,5 +132,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 46) MigrateSessionTo046(realm).perform() if (oldVersion < 47) MigrateSessionTo047(realm).perform() if (oldVersion < 48) MigrateSessionTo048(realm).perform() + if (oldVersion < 49) MigrateSessionTo049(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 89657ad882..83f3e87d05 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -47,6 +47,7 @@ internal object HomeServerCapabilitiesMapper { canLoginWithQrCode = entity.canLoginWithQrCode, canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications, canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices, + canRedactEventWithRelations = entity.canRedactEventWithRelations, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt new file mode 100644 index 0000000000..31a5305777 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo049.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 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.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo049(realm: DynamicRealm) : RealmMigrator(realm, 49) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, Boolean::class.java) + ?.transform { obj -> + obj.set(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, false) + } + ?.forceRefreshOfHomeServerCapabilities() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 2b60f7723c..9acdcde7e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -34,6 +34,7 @@ internal open class HomeServerCapabilitiesEntity( var canLoginWithQrCode: Boolean = false, var canUseThreadReadReceiptsAndNotifications: Boolean = false, var canRemotelyTogglePushNotificationsOfDevices: Boolean = false, + var canRedactEventWithRelations: Boolean = false, ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 11e86a5c51..5a6107821d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin +import org.matrix.android.sdk.internal.auth.version.doesServerSupportRedactEventWithRelations import org.matrix.android.sdk.internal.auth.version.doesServerSupportRemoteToggleOfPushNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads @@ -154,6 +155,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( getVersionResult.doesServerSupportQrCodeLogin() homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices = getVersionResult.doesServerSupportRemoteToggleOfPushNotifications() + homeServerCapabilitiesEntity.canRedactEventWithRelations = + getVersionResult.doesServerSupportRedactEventWithRelations() } if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 34b6ee525d..aa4bdb1dd4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse import org.matrix.android.sdk.internal.session.room.relation.threads.ThreadSummariesResponse import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody import org.matrix.android.sdk.internal.session.room.send.SendResponse +import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody import org.matrix.android.sdk.internal.session.room.tags.TagBody import org.matrix.android.sdk.internal.session.room.timeline.EventContextResponse import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse @@ -61,7 +62,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms") suspend fun publicRooms( @Query("server") server: String?, - @Body publicRoomsParams: PublicRoomsParams + @Body publicRoomsParams: PublicRoomsParams, ): PublicRoomsResponse /** @@ -91,7 +92,7 @@ internal interface RoomAPI { @Query("from") from: String, @Query("dir") dir: String, @Query("limit") limit: Int?, - @Query("filter") filter: String? + @Query("filter") filter: String?, ): PaginationResponse /** @@ -107,7 +108,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Query("at") syncToken: String?, @Query("membership") membership: Membership?, - @Query("not_membership") notMembership: Membership? + @Query("not_membership") notMembership: Membership?, ): RoomMembersResponse /** @@ -123,7 +124,7 @@ internal interface RoomAPI { @Path("txId") txId: String, @Path("roomId") roomId: String, @Path("eventType") eventType: String, - @Body content: Content? + @Body content: Content?, ): SendResponse /** @@ -139,7 +140,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("eventId") eventId: String, @Query("limit") limit: Int, - @Query("filter") filter: String? = null + @Query("filter") filter: String? = null, ): EventContextResponse /** @@ -151,7 +152,7 @@ internal interface RoomAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") suspend fun getEvent( @Path("roomId") roomId: String, - @Path("eventId") eventId: String + @Path("eventId") eventId: String, ): Event /** @@ -163,7 +164,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") suspend fun sendReadMarker( @Path("roomId") roomId: String, - @Body markers: Map + @Body markers: Map, ) /** @@ -174,7 +175,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("receiptType") receiptType: String, @Path("eventId") eventId: String, - @Body body: ReadBody + @Body body: ReadBody, ) /** @@ -187,7 +188,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") suspend fun invite( @Path("roomId") roomId: String, - @Body body: InviteBody + @Body body: InviteBody, ) /** @@ -199,7 +200,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") suspend fun invite3pid( @Path("roomId") roomId: String, - @Body body: ThreePidInviteBody + @Body body: ThreePidInviteBody, ) /** @@ -213,7 +214,7 @@ internal interface RoomAPI { suspend fun sendStateEvent( @Path("roomId") roomId: String, @Path("state_event_type") stateEventType: String, - @Body params: JsonDict + @Body params: JsonDict, ): SendResponse /** @@ -229,7 +230,7 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("state_event_type") stateEventType: String, @Path("state_key") stateKey: String, - @Body params: JsonDict + @Body params: JsonDict, ): SendResponse /** @@ -257,7 +258,7 @@ internal interface RoomAPI { @Path("eventType") eventType: String, @Query("from") from: String? = null, @Query("to") to: String? = null, - @Query("limit") limit: Int? = null + @Query("limit") limit: Int? = null, ): RelationsResponse /** @@ -277,7 +278,7 @@ internal interface RoomAPI { @Path("relationType") relationType: String, @Query("from") from: String? = null, @Query("to") to: String? = null, - @Query("limit") limit: Int? = null + @Query("limit") limit: Int? = null, ): RelationsResponse /** @@ -291,7 +292,7 @@ internal interface RoomAPI { suspend fun join( @Path("roomIdOrAlias") roomIdOrAlias: String, @Query("server_name") viaServers: List, - @Body params: JsonDict + @Body params: JsonDict, ): JoinRoomResponse /** @@ -303,7 +304,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") suspend fun leave( @Path("roomId") roomId: String, - @Body params: Map + @Body params: Map, ) /** @@ -315,7 +316,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban") suspend fun ban( @Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason + @Body userIdAndReason: UserIdAndReason, ) /** @@ -327,7 +328,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban") suspend fun unban( @Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason + @Body userIdAndReason: UserIdAndReason, ) /** @@ -339,7 +340,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick") suspend fun kick( @Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason + @Body userIdAndReason: UserIdAndReason, ) /** @@ -350,14 +351,14 @@ internal interface RoomAPI { * @param txId the transaction Id * @param roomId the room id * @param eventId the event to delete - * @param reason json containing reason key {"reason": "Indecent material"} + * @param body body containing reason key {"reason": "Indecent material"} and with_relations */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}") suspend fun redactEvent( @Path("txnId") txId: String, @Path("roomId") roomId: String, @Path("eventId") eventId: String, - @Body reason: Map + @Body body: EventRedactBody, ): SendResponse /** @@ -371,7 +372,7 @@ internal interface RoomAPI { suspend fun reportContent( @Path("roomId") roomId: String, @Path("eventId") eventId: String, - @Body body: ReportContentBody + @Body body: ReportContentBody, ) /** @@ -388,7 +389,7 @@ internal interface RoomAPI { suspend fun sendTypingState( @Path("roomId") roomId: String, @Path("userId") userId: String, - @Body body: TypingBody + @Body body: TypingBody, ) /* @@ -403,7 +404,7 @@ internal interface RoomAPI { @Path("userId") userId: String, @Path("roomId") roomId: String, @Path("tag") tag: String, - @Body body: TagBody + @Body body: TagBody, ) /** @@ -413,7 +414,7 @@ internal interface RoomAPI { suspend fun deleteTag( @Path("userId") userId: String, @Path("roomId") roomId: String, - @Path("tag") tag: String + @Path("tag") tag: String, ) /** @@ -424,7 +425,7 @@ internal interface RoomAPI { @Path("userId") userId: String, @Path("roomId") roomId: String, @Path("type") type: String, - @Body content: JsonDict + @Body content: JsonDict, ) /** @@ -437,7 +438,7 @@ internal interface RoomAPI { suspend fun deleteRoomAccountData( @Path("userId") userId: String, @Path("roomId") roomId: String, - @Path("type") type: String + @Path("type") type: String, ) /** @@ -450,7 +451,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade") suspend fun upgradeRoom( @Path("roomId") roomId: String, - @Body body: RoomUpgradeBody + @Body body: RoomUpgradeBody, ): RoomUpgradeResponse /** @@ -462,7 +463,7 @@ internal interface RoomAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary") suspend fun getRoomSummary( @Path("roomIdOrAlias") roomidOrAlias: String, - @Query("via") viaServers: List? + @Query("via") viaServers: List?, ): RoomStrippedState @GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/threads") @@ -470,6 +471,6 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Query("include") include: String? = "all", @Query("from") from: String? = null, - @Query("limit") limit: Int? = null + @Query("limit") limit: Int? = null, ): ThreadSummariesResponse } 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 9cdbc7ff46..d29e7d8f36 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 @@ -140,11 +140,11 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable { + override fun redactEvent(event: Event, reason: String?, withRelations: List?, additionalContent: Content?): Cancelable { // TODO manage media/attachements? - val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, additionalContent) + val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, withRelations, additionalContent) .also { createLocalEcho(it) } - return eventSenderProcessor.postRedaction(redactionEcho, reason) + return eventSenderProcessor.postRedaction(redactionEcho, reason, withRelations) } override fun resendTextMessage(localEcho: TimelineEvent): Cancelable { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index d974c597ac..38024b7aa8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -70,6 +70,7 @@ import org.matrix.android.sdk.api.util.TextContent import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory +import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils import org.matrix.android.sdk.internal.util.time.Clock import java.util.UUID @@ -795,8 +796,16 @@ internal class LocalEchoEventFactory @Inject constructor( } } */ - fun createRedactEvent(roomId: String, eventId: String, reason: String?, additionalContent: Content? = null): Event { + fun createRedactEvent(roomId: String, eventId: String, reason: String?, withRelations: List? = null, additionalContent: Content? = null): Event { val localId = LocalEcho.createLocalEchoId() + val content = if (reason != null || withRelations != null) { + EventRedactBody( + reason = reason, + withRelations = withRelations, + ).toContent().plus(additionalContent.orEmpty()) + } else { + additionalContent + } return Event( roomId = roomId, originServerTs = dummyOriginServerTs(), @@ -804,7 +813,7 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.REDACTION, redacts = eventId, - content = reason?.let { mapOf("reason" to it).toContent().plus(additionalContent.orEmpty()) } ?: additionalContent, + content = content, unsignedData = UnsignedData(age = null, transactionId = localId) ) } @@ -830,10 +839,10 @@ internal class LocalEchoEventFactory @Inject constructor( createMessageEvent( roomId, textContent.toThreadTextContent( - rootThreadEventId = rootThreadEventId, - latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), - msgType = MessageType.MSGTYPE_TEXT - ), + rootThreadEventId = rootThreadEventId, + latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), + msgType = MessageType.MSGTYPE_TEXT + ), additionalContent, ) } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt index 765c282b65..576f31c64c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt @@ -20,8 +20,8 @@ import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker @@ -43,27 +43,29 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters, ses val roomId: String, val eventId: String, val reason: String?, + val withRelations: List? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams @Inject lateinit var roomAPI: RoomAPI @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver + @Inject lateinit var redactEventTask: RedactEventTask override fun injectWith(injector: SessionComponent) { injector.inject(this) } override suspend fun doSafeWork(params: Params): Result { - val eventId = params.eventId return runCatching { - executeRequest(globalErrorReceiver) { - roomAPI.redactEvent( - params.txID, - params.roomId, - eventId, - if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) - ) - } + redactEventTask.execute( + RedactEventTask.Params( + txID = params.txID, + roomId = params.roomId, + eventId = params.eventId, + reason = params.reason, + withRelations = params.withRelations, + ) + ) }.fold( { Result.success() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/model/EventRedactBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/model/EventRedactBody.kt new file mode 100644 index 0000000000..cf2bc0dc4f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/model/EventRedactBody.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2023 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.session.room.send.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class EventRedactBody( + @Json(name = "reason") + val reason: String? = null, + + @Json(name = "org.matrix.msc3912.with_relations") + val withRelations: List? = null, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt index 050e321b9c..b285e90c9a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt @@ -26,9 +26,9 @@ internal interface EventSenderProcessor : SessionLifecycleObserver { fun postEvent(event: Event, encrypt: Boolean): Cancelable - fun postRedaction(redactionLocalEcho: Event, reason: String?): Cancelable + fun postRedaction(redactionLocalEcho: Event, reason: String?, withRelations: List? = null): Cancelable - fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?): Cancelable + fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?, withRelations: List? = null): Cancelable fun postTask(task: QueuedTask): Cancelable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index 2c7eea1e54..929fe7b9a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt @@ -101,12 +101,18 @@ internal class EventSenderProcessorCoroutine @Inject constructor( return postTask(task) } - override fun postRedaction(redactionLocalEcho: Event, reason: String?): Cancelable { - return postRedaction(redactionLocalEcho.eventId!!, redactionLocalEcho.redacts!!, redactionLocalEcho.roomId!!, reason) + override fun postRedaction(redactionLocalEcho: Event, reason: String?, withRelations: List?): Cancelable { + return postRedaction(redactionLocalEcho.eventId!!, redactionLocalEcho.redacts!!, redactionLocalEcho.roomId!!, reason, withRelations) } - override fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?): Cancelable { - val task = queuedTaskFactory.createRedactTask(redactionLocalEchoId, eventToRedactId, roomId, reason) + override fun postRedaction( + redactionLocalEchoId: String, + eventToRedactId: String, + roomId: String, + reason: String?, + withRelations: List? + ): Cancelable { + val task = queuedTaskFactory.createRedactTask(redactionLocalEchoId, eventToRedactId, roomId, reason, withRelations) return postTask(task) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt index 0eedd4bd4d..a900e4ae5d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt @@ -19,9 +19,11 @@ package org.matrix.android.sdk.internal.session.room.send.queue import android.content.Context import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository +import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody import timber.log.Timber import javax.inject.Inject @@ -107,10 +109,18 @@ internal class QueueMemento @Inject constructor( info.redactionLocalEcho?.let { localEchoRepository.getUpToDateEcho(it) }?.let { localEchoRepository.updateSendState(it.eventId!!, it.roomId, SendState.UNSENT) // try to get reason - val reason = it.content?.get("reason") as? String + val body = it.content.toModel() if (it.redacts != null && it.roomId != null) { Timber.d("## Send -Reschedule redact $info") - eventProcessor.postTask(queuedTaskFactory.createRedactTask(it.eventId, it.redacts, it.roomId, reason)) + eventProcessor.postTask( + queuedTaskFactory.createRedactTask( + redactionLocalEcho = it.eventId, + eventId = it.redacts, + roomId = it.roomId, + reason = body?.reason, + withRelations = body?.withRelations, + ) + ) } } // postTask(queuedTaskFactory.createRedactTask(info.eventToRedactId, info.) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTaskFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTaskFactory.kt index 90bb47c435..46df7e29f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTaskFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTaskFactory.kt @@ -43,12 +43,13 @@ internal class QueuedTaskFactory @Inject constructor( ) } - fun createRedactTask(redactionLocalEcho: String, eventId: String, roomId: String, reason: String?): QueuedTask { + fun createRedactTask(redactionLocalEcho: String, eventId: String, roomId: String, reason: String?, withRelations: List? = null): QueuedTask { return RedactQueuedTask( redactionLocalEchoId = redactionLocalEcho, toRedactEventId = eventId, roomId = roomId, reason = reason, + withRelations = withRelations, redactEventTask = redactEventTask, localEchoRepository = localEchoRepository, cancelSendTracker = cancelSendTracker diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt index 0e3d88aa79..f484c24aae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/RedactQueuedTask.kt @@ -26,13 +26,14 @@ internal class RedactQueuedTask( val redactionLocalEchoId: String, private val roomId: String, private val reason: String?, + private val withRelations: List?, private val redactEventTask: RedactEventTask, private val localEchoRepository: LocalEchoRepository, private val cancelSendTracker: CancelSendTracker ) : QueuedTask(queueIdentifier = roomId, taskIdentifier = redactionLocalEchoId) { override suspend fun doExecute() { - redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason)) + redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason, withRelations)) } override fun onTaskFailed() { diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt index 16e8405887..49dd74d16f 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt @@ -20,6 +20,7 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.model.isVoiceBroadcast import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -49,3 +50,7 @@ fun TimelineEvent.getVectorLastMessageContent(): MessageContent? { else -> getLastMessageContent() } } + +fun TimelineEvent.isVoiceBroadcast(): Boolean { + return root.isVoiceBroadcast() +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index c2a4f4b956..72d9fc8a16 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -32,6 +32,7 @@ import im.vector.app.R import im.vector.app.SpaceStateHandler import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.isVoiceBroadcast import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.BuildMeta @@ -859,12 +860,18 @@ class TimelineViewModel @AssistedInject constructor( private fun handleRedactEvent(action: RoomDetailAction.RedactAction) { val event = room?.getTimelineEvent(action.targetEventId) ?: return - if (event.isLiveLocation()) { - viewModelScope.launch { - redactLiveLocationShareEventUseCase.execute(event.root, room, action.reason) + when { + event.isLiveLocation() -> { + viewModelScope.launch { + redactLiveLocationShareEventUseCase.execute(event.root, room, action.reason) + } + } + event.isVoiceBroadcast() -> { + room.sendService().redactEvent(event.root, action.reason, listOf(RelationType.REFERENCE)) + } + else -> { + room.sendService().redactEvent(event.root, action.reason) } - } else { - room.sendService().redactEvent(event.root, action.reason) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt index d464a253d3..bcc0b39f8e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt @@ -49,7 +49,9 @@ value class VoiceBroadcastEvent(val root: Event) { get() = root.content.toModel() } +fun Event.isVoiceBroadcast() = type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + /** * Map a [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. */ -fun Event.asVoiceBroadcastEvent() = if (type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null +fun Event.asVoiceBroadcastEvent() = if (isVoiceBroadcast()) VoiceBroadcastEvent(this) else null