From c095cc73cec34516673e275aad86d7d3d5c999e9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Fri, 1 Jul 2022 17:07:40 +0200 Subject: [PATCH 01/19] Introducing use case to check if an event can be redacted --- .../action/CheckIfCanRedactEventUseCase.kt | 38 ++++++ .../action/MessageActionsViewModel.kt | 8 +- .../CheckIfCanRedactEventUseCaseTest.kt | 115 ++++++++++++++++++ 3 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt create mode 100644 vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt new file mode 100644 index 0000000000..222df8f914 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.action + +import im.vector.app.core.di.ActiveSessionHolder +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import javax.inject.Inject + +class CheckIfCanRedactEventUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder +) { + + fun execute(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean { + // Only some event types are supported for the moment + val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) + + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + if (event.root.getClearType() !in canRedactEventTypes) return false + // Message sent by the current user can always be redacted + if (event.root.senderId == activeSessionHolder.getActiveSession().myUserId) return true + // Check permission for messages sent by other users + return actionPermissions.canRedact + } +} 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 30786dc77a..3dfb6744e0 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 @@ -82,6 +82,7 @@ class MessageActionsViewModel @AssistedInject constructor( private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val vectorPreferences: VectorPreferences, private val checkIfCanReplyEventUseCase: CheckIfCanReplyEventUseCase, + private val checkIfCanRedactEventUseCase: CheckIfCanRedactEventUseCase, ) : VectorViewModel(initialState) { private val informationData = initialState.informationData @@ -518,12 +519,7 @@ class MessageActionsViewModel @AssistedInject constructor( } private fun canRedact(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean { - // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment - if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START) return false - // Message sent by the current user can always be redacted - if (event.root.senderId == session.myUserId) return true - // Check permission for messages sent by other users - return actionPermissions.canRedact + return checkIfCanRedactEventUseCase.execute(event, actionPermissions) } private fun canRetry(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean { diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt new file mode 100644 index 0000000000..08dd5dac5b --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.action + +import im.vector.app.test.fakes.FakeActiveSessionHolder +import io.mockk.mockk +import org.amshove.kluent.shouldBe +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +class CheckIfCanRedactEventUseCaseTest { + + private val fakeActiveSessionHolder = FakeActiveSessionHolder() + + private val checkIfCanRedactEventUseCase = CheckIfCanRedactEventUseCase( + activeSessionHolder = fakeActiveSessionHolder.instance + ) + + @Test + fun `given an event which can be redacted and owned by user when use case executes then the result is true`() { + val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) + + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + + canRedactEventTypes.forEach { eventType -> + val event = givenAnEvent( + eventType = eventType, + senderId = fakeActiveSessionHolder.fakeSession.myUserId + ) + + val actionPermissions = givenActionPermissions(canRedact = false) + + val result = checkIfCanRedactEventUseCase.execute(event, actionPermissions) + + result shouldBe true + } + } + + @Test + fun `given redact permission and an event which can be redacted and sent by another user when use case executes then the result is true`() { + val event = givenAnEvent( + eventType = EventType.MESSAGE, + senderId = "user-id" + ) + + val actionPermissions = givenActionPermissions(canRedact = true) + + val result = checkIfCanRedactEventUseCase.execute(event, actionPermissions) + + result shouldBe true + } + + @Test + fun `given an event which cannot be redacted when use case executes then the result is false`() { + val event = givenAnEvent( + eventType = EventType.CALL_ANSWER, + senderId = fakeActiveSessionHolder.fakeSession.myUserId + ) + + val actionPermissions = givenActionPermissions(canRedact = false) + + val result = checkIfCanRedactEventUseCase.execute(event, actionPermissions) + + result shouldBe false + } + + @Test + fun `given missing redact permission and an event which can be redacted and sent by another user when use case executes then the result is false`() { + val event = givenAnEvent( + eventType = EventType.MESSAGE, + senderId = "user-id" + ) + + val actionPermissions = givenActionPermissions(canRedact = false) + + val result = checkIfCanRedactEventUseCase.execute(event, actionPermissions) + + result shouldBe false + } + + private fun givenAnEvent(eventType: String, senderId: String): TimelineEvent { + val eventId = "event-id" + return TimelineEvent( + root = Event( + eventId = eventId, + type = eventType, + senderId = senderId + ), + localId = 123L, + eventId = eventId, + displayIndex = 1, + ownedByThreadChunk = false, + senderInfo = mockk() + ) + } + + private fun givenActionPermissions(canRedact: Boolean): ActionPermissions { + return ActionPermissions(canRedact = canRedact) + } +} From e26759fa89bcef8c9e0b213954f25c1521aab6b6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Fri, 1 Jul 2022 17:23:31 +0200 Subject: [PATCH 02/19] Adding changelog entry --- changelog.d/6437.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6437.feature diff --git a/changelog.d/6437.feature b/changelog.d/6437.feature new file mode 100644 index 0000000000..fb24819daf --- /dev/null +++ b/changelog.d/6437.feature @@ -0,0 +1 @@ +[Location sharing] - Delete action on a live message From bad4eba153eb5b917492cdbf1e54158fae70cd99 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 4 Jul 2022 15:33:52 +0200 Subject: [PATCH 03/19] Remove non necessary @MainThread annotations --- .../sdk/api/session/room/location/LocationSharingService.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt index 14095b67c0..a92542fcf5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt @@ -62,13 +62,11 @@ interface LocationSharingService { /** * Returns a LiveData on the list of current running live location shares. */ - @MainThread fun getRunningLiveLocationShareSummaries(): LiveData> /** * Returns a LiveData on the live location share summary with the given eventId. * @param beaconInfoEventId event id of the initial beacon info state event */ - @MainThread fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData> } From c404454cd7bc2667b1cbd21d08da3566a1521b8f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 5 Jul 2022 14:26:02 +0200 Subject: [PATCH 04/19] Adding related event ids of live in entity --- ...iveLocationShareAggregatedSummaryEntity.kt | 6 +++++ .../LiveLocationAggregationProcessor.kt | 22 +++++++++++++++++++ .../LiveLocationAggregationProcessorTest.kt | 13 +++++++---- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt index c5df8e9338..08ea06bb1e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.database.model.livelocation +import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey @@ -29,6 +30,11 @@ internal open class LiveLocationShareAggregatedSummaryEntity( @PrimaryKey var eventId: String = "", + /** + * List of event ids used to compute the aggregated summary data. + */ + var relatedEventIds: RealmList = RealmList(), + var roomId: String = "", var userId: String = "", diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt index 921749122b..3f5b1e1360 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import androidx.work.ExistingWorkPolicy import io.realm.Realm +import io.realm.RealmList import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toContent @@ -73,6 +74,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor( eventId = targetEventId ) + if (!isLive && !event.eventId.isNullOrEmpty()) { + // in this case, the received event is a new state event related to the previous one + addRelatedEventId(event.eventId, aggregatedSummary) + } + // remote event can stay with isLive == true while the local summary is no more active val isActive = aggregatedSummary.isActive.orTrue() && isLive val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } @@ -144,6 +150,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor( roomId = roomId, eventId = relatedEventId ) + + if (!event.eventId.isNullOrEmpty()) { + addRelatedEventId(event.eventId, aggregatedSummary) + } + val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 val currentLocationTimestamp = ContentMapper .map(aggregatedSummary.lastLocationContent) @@ -160,6 +171,17 @@ internal class LiveLocationAggregationProcessor @Inject constructor( } } + private fun addRelatedEventId( + eventId: String, + aggregatedSummary: LiveLocationShareAggregatedSummaryEntity + ) { + Timber.d("adding related event id $eventId to summary of id ${aggregatedSummary.eventId}") + val updatedEventIds = aggregatedSummary.relatedEventIds.toMutableList().also { + it.add(eventId) + } + aggregatedSummary.relatedEventIds = RealmList(*updatedEventIds.toTypedArray()) + } + private fun deactivateAllPreviousBeacons(realm: Realm, roomId: String, userId: String, currentEventId: String) { LiveLocationShareAggregatedSummaryEntity .findActiveLiveInRoomForUser( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt index 933087af2b..a5e91714b7 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import androidx.work.ExistingWorkPolicy import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldContain import org.junit.Test import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.UnsignedData @@ -199,9 +200,10 @@ internal class LiveLocationAggregationProcessorTest { age = 123, replacesState = AN_EVENT_ID ) + val stateEventId = "state-event-id" val event = Event( senderId = A_SENDER_ID, - eventId = "", + eventId = stateEventId, unsignedData = unsignedData ) val beaconInfo = MessageBeaconInfoContent( @@ -237,6 +239,7 @@ internal class LiveLocationAggregationProcessorTest { aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID aggregatedEntity.isActive shouldBeEqualTo false + aggregatedEntity.relatedEventIds shouldContain stateEventId aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS aggregatedEntity.lastLocationContent shouldBeEqualTo null previousEntities.forEach { entity -> @@ -324,7 +327,7 @@ internal class LiveLocationAggregationProcessorTest { val lastBeaconLocationContent = MessageBeaconLocationDataContent( unstableTimestampMillis = A_TIMESTAMP ) - givenLastSummaryQueryReturns( + val aggregatedEntity = givenLastSummaryQueryReturns( eventId = AN_EVENT_ID, roomId = A_ROOM_ID, beaconLocationContent = lastBeaconLocationContent @@ -340,6 +343,7 @@ internal class LiveLocationAggregationProcessorTest { ) result shouldBeEqualTo false + aggregatedEntity.relatedEventIds shouldContain AN_EVENT_ID } @Test @@ -353,7 +357,7 @@ internal class LiveLocationAggregationProcessorTest { val lastBeaconLocationContent = MessageBeaconLocationDataContent( unstableTimestampMillis = A_TIMESTAMP - 60_000 ) - val entity = givenLastSummaryQueryReturns( + val aggregatedEntity = givenLastSummaryQueryReturns( eventId = AN_EVENT_ID, roomId = A_ROOM_ID, beaconLocationContent = lastBeaconLocationContent @@ -369,7 +373,8 @@ internal class LiveLocationAggregationProcessorTest { ) result shouldBeEqualTo true - val savedLocationData = ContentMapper.map(entity.lastLocationContent).toModel() + aggregatedEntity.relatedEventIds shouldContain AN_EVENT_ID + val savedLocationData = ContentMapper.map(aggregatedEntity.lastLocationContent).toModel() savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI } From c9273dd067d6c8d23163a19e2d82f2d21b31d56b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 5 Jul 2022 15:26:42 +0200 Subject: [PATCH 05/19] Adding task to redact live location share related events --- ...cationShareAggregatedSummaryEntityQuery.kt | 15 +++ .../sdk/internal/session/room/RoomModule.kt | 5 + .../location/RedactLiveLocationShareTask.kt | 79 ++++++++++++ .../DefaultRedactLiveLocationShareTaskTest.kt | 116 ++++++++++++++++++ .../test/fakes/FakeEventSenderProcessor.kt | 4 + .../test/fakes/FakeLocalEchoEventFactory.kt | 66 +++++++--- .../sdk/test/fakes/FakeRealmConfiguration.kt | 41 +++++++ 7 files changed, 308 insertions(+), 18 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt index d69f251f6f..fbf7e963a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt @@ -23,6 +23,14 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where( + realm: Realm, + eventId: String, +): RealmQuery { + return realm.where() + .equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId) +} + internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where( realm: Realm, roomId: String, @@ -72,6 +80,13 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get( return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst() } +internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get( + realm: Realm, + eventId: String, +): LiveLocationShareAggregatedSummaryEntity? { + return LiveLocationShareAggregatedSummaryEntity.where(realm, eventId).findFirst() +} + internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveInRoomForUser( realm: Realm, roomId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index e1dd22a211..d01324a35f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -58,11 +58,13 @@ import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVi import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.internal.session.room.location.DefaultRedactLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.DefaultSendLiveLocationTask import org.matrix.android.sdk.internal.session.room.location.DefaultSendStaticLocationTask import org.matrix.android.sdk.internal.session.room.location.DefaultStartLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.DefaultStopLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.internal.session.room.location.RedactLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.SendLiveLocationTask import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask @@ -339,4 +341,7 @@ internal abstract class RoomModule { @Binds abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask + + @Binds + abstract fun bindRedactLiveLocationShareTask(task: DefaultRedactLiveLocationShareTask): RedactLiveLocationShareTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt new file mode 100644 index 0000000000..9883d1204b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022 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.location + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.internal.database.awaitTransaction +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory +import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor +import org.matrix.android.sdk.internal.task.Task +import timber.log.Timber +import javax.inject.Inject + +internal interface RedactLiveLocationShareTask : Task { + data class Params( + val roomId: String, + val beaconInfoEventId: String, + val reason: String? + ) +} + +internal class DefaultRedactLiveLocationShareTask @Inject constructor( + @SessionDatabase private val realmConfiguration: RealmConfiguration, + private val localEchoEventFactory: LocalEchoEventFactory, + private val eventSenderProcessor: EventSenderProcessor, +) : RedactLiveLocationShareTask { + + override suspend fun execute(params: RedactLiveLocationShareTask.Params) { + val relatedEventIds = getRelatedEventIdsOfLive(params.beaconInfoEventId) + Timber.d("beacon with id ${params.beaconInfoEventId} has related event ids: ${relatedEventIds.joinToString(", ")}") + + relatedEventIds.forEach { eventId -> + redactEvent( + eventId = eventId, + roomId = params.roomId, + reason = params.reason + ) + } + + redactEvent( + eventId = params.beaconInfoEventId, + roomId = params.roomId, + reason = params.reason + ) + } + + private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List { + return awaitTransaction(realmConfiguration) { realm -> + val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get( + realm = realm, + eventId = beaconInfoEventId + ) + aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList() + } + } + + private fun redactEvent(eventId: String, roomId: String, reason: String?) { + Timber.d("redacting event of id $eventId") + val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, eventId, reason) + localEchoEventFactory.createLocalEcho(redactionEcho) + eventSenderProcessor.postRedaction(redactionEcho, reason) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt new file mode 100644 index 0000000000..66428ac064 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2022 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.location + +import io.mockk.unmockkAll +import io.realm.RealmList +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeEventSenderProcessor +import org.matrix.android.sdk.test.fakes.FakeLocalEchoEventFactory +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.FakeRealmConfiguration +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst + +private const val A_ROOM_ID = "room-id" +private const val AN_EVENT_ID = "event-id" +private const val AN_EVENT_ID_1 = "event-id-1" +private const val AN_EVENT_ID_2 = "event-id-2" +private const val AN_EVENT_ID_3 = "event-id-3" +private const val A_REASON = "reason" + +@ExperimentalCoroutinesApi +class DefaultRedactLiveLocationShareTaskTest { + + private val fakeRealmConfiguration = FakeRealmConfiguration() + private val fakeLocalEchoEventFactory = FakeLocalEchoEventFactory() + private val fakeEventSenderProcessor = FakeEventSenderProcessor() + private val fakeRealm = FakeRealm() + + private val defaultRedactLiveLocationShareTask = DefaultRedactLiveLocationShareTask( + realmConfiguration = fakeRealmConfiguration.instance, + localEchoEventFactory = fakeLocalEchoEventFactory.instance, + eventSenderProcessor = fakeEventSenderProcessor + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters when calling the task then it is correctly executed`() = runTest { + val params = RedactLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + reason = A_REASON + ) + fakeRealmConfiguration.givenAwaitTransaction>(fakeRealm.instance) + val relatedEventIds = listOf(AN_EVENT_ID_1, AN_EVENT_ID_2, AN_EVENT_ID_3) + val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity( + eventId = AN_EVENT_ID, + relatedEventIds = RealmList(*relatedEventIds.toTypedArray()), + ) + fakeRealm.givenWhere() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID) + .givenFindFirst(aggregatedSummaryEntity) + val redactedEvent = fakeLocalEchoEventFactory.givenCreateRedactEvent( + eventId = AN_EVENT_ID, + withLocalEcho = true + ) + fakeEventSenderProcessor.givenPostRedaction(event = redactedEvent, reason = A_REASON) + val redactedEvent1 = fakeLocalEchoEventFactory.givenCreateRedactEvent( + eventId = AN_EVENT_ID_1, + withLocalEcho = true + ) + fakeEventSenderProcessor.givenPostRedaction(event = redactedEvent1, reason = A_REASON) + val redactedEvent2 = fakeLocalEchoEventFactory.givenCreateRedactEvent( + eventId = AN_EVENT_ID_2, + withLocalEcho = true + ) + fakeEventSenderProcessor.givenPostRedaction(event = redactedEvent2, reason = A_REASON) + val redactedEvent3 = fakeLocalEchoEventFactory.givenCreateRedactEvent( + eventId = AN_EVENT_ID_3, + withLocalEcho = true + ) + fakeEventSenderProcessor.givenPostRedaction(event = redactedEvent3, reason = A_REASON) + + defaultRedactLiveLocationShareTask.execute(params) + + fakeLocalEchoEventFactory.verifyCreateRedactEvent( + roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, + reason = A_REASON + ) + fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactedEvent) + relatedEventIds.forEach { eventId -> + fakeLocalEchoEventFactory.verifyCreateRedactEvent( + roomId = A_ROOM_ID, + eventId = eventId, + reason = A_REASON + ) + } + fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactedEvent1) + fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactedEvent2) + fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactedEvent3) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt index fbdcf5bfd7..db04b8b8cb 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt @@ -27,4 +27,8 @@ internal class FakeEventSenderProcessor : EventSenderProcessor by mockk() { fun givenPostEventReturns(event: Event, cancelable: Cancelable) { every { postEvent(event) } returns cancelable } + + fun givenPostRedaction(event: Event, reason: String?) { + every { postRedaction(event, reason) } returns mockk() + } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt index 50ec85f14a..f484e32149 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt @@ -46,24 +46,6 @@ internal class FakeLocalEchoEventFactory { return event } - fun givenCreateLiveLocationEvent(withLocalEcho: Boolean): Event { - val event = Event() - every { - instance.createLiveLocationEvent( - beaconInfoEventId = any(), - roomId = any(), - latitude = any(), - longitude = any(), - uncertainty = any() - ) - } returns event - - if (withLocalEcho) { - every { instance.createLocalEcho(event) } just runs - } - return event - } - fun verifyCreateStaticLocationEvent( roomId: String, latitude: Double, @@ -82,6 +64,24 @@ internal class FakeLocalEchoEventFactory { } } + fun givenCreateLiveLocationEvent(withLocalEcho: Boolean): Event { + val event = Event() + every { + instance.createLiveLocationEvent( + beaconInfoEventId = any(), + roomId = any(), + latitude = any(), + longitude = any(), + uncertainty = any() + ) + } returns event + + if (withLocalEcho) { + every { instance.createLocalEcho(event) } just runs + } + return event + } + fun verifyCreateLiveLocationEvent( roomId: String, beaconInfoEventId: String, @@ -100,6 +100,36 @@ internal class FakeLocalEchoEventFactory { } } + fun givenCreateRedactEvent(eventId: String, withLocalEcho: Boolean): Event { + val event = Event() + every { + instance.createRedactEvent( + roomId = any(), + eventId = eventId, + reason = any() + ) + } returns event + + if (withLocalEcho) { + every { instance.createLocalEcho(event) } just runs + } + return event + } + + fun verifyCreateRedactEvent( + roomId: String, + eventId: String, + reason: String? + ) { + verify { + instance.createRedactEvent( + roomId = roomId, + eventId = eventId, + reason = reason + ) + } + } + fun verifyCreateLocalEcho(event: Event) { verify { instance.createLocalEcho(event) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt new file mode 100644 index 0000000000..15a9823c79 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.coEvery +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.realm.Realm +import io.realm.RealmConfiguration +import org.matrix.android.sdk.internal.database.awaitTransaction + +internal class FakeRealmConfiguration { + + init { + mockkStatic("org.matrix.android.sdk.internal.database.AsyncTransactionKt") + } + + val instance = mockk() + + fun givenAwaitTransaction(realm: Realm) { + val transaction = slot T>() + coEvery { awaitTransaction(instance, capture(transaction)) } coAnswers { + secondArg T>().invoke(realm) + } + } +} From 237a5a18f3c149853cd6083596097b90bad22d3a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 5 Jul 2022 16:03:18 +0200 Subject: [PATCH 06/19] Adding new method in location sharing service to redact a live location share --- .../room/location/LocationSharingService.kt | 7 +++++++ .../location/DefaultLocationSharingService.kt | 10 ++++++++++ .../DefaultLocationSharingServiceTest.kt | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt index a92542fcf5..50a87fbdc2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt @@ -59,6 +59,13 @@ interface LocationSharingService { */ suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult + /** + * Redact (delete) the live associated to the given beacon info event id. + * @param beaconInfoEventId event id of the initial beacon info state event + * @param reason Optional reason string + */ + suspend fun redactLiveLocationShare(beaconInfoEventId: String, reason: String?) + /** * Returns a LiveData on the list of current running live location shares. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt index a8a9691ce9..60312071d7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt @@ -42,6 +42,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor( private val startLiveLocationShareTask: StartLiveLocationShareTask, private val stopLiveLocationShareTask: StopLiveLocationShareTask, private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask, + private val redactLiveLocationShareTask: RedactLiveLocationShareTask, private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper, ) : LocationSharingService { @@ -102,6 +103,15 @@ internal class DefaultLocationSharingService @AssistedInject constructor( return stopLiveLocationShareTask.execute(params) } + override suspend fun redactLiveLocationShare(beaconInfoEventId: String, reason: String?) { + val params = RedactLiveLocationShareTask.Params( + roomId = roomId, + beaconInfoEventId = beaconInfoEventId, + reason = reason + ) + return redactLiveLocationShareTask.execute(params) + } + override fun getRunningLiveLocationShareSummaries(): LiveData> { return monarchy.findAllMappedWithChanges( { LiveLocationShareAggregatedSummaryEntity.findRunningLiveInRoom(it, roomId = roomId) }, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt index ef9bde2c49..a01f51604c 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt @@ -22,8 +22,10 @@ import androidx.lifecycle.Transformations import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every +import io.mockk.just import io.mockk.mockk import io.mockk.mockkStatic +import io.mockk.runs import io.mockk.slot import io.mockk.unmockkAll import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -52,6 +54,7 @@ private const val A_LONGITUDE = 40.0 private const val AN_UNCERTAINTY = 5.0 private const val A_TIMEOUT = 15_000L private const val A_DESCRIPTION = "description" +private const val A_REASON = "reason" @ExperimentalCoroutinesApi internal class DefaultLocationSharingServiceTest { @@ -62,6 +65,7 @@ internal class DefaultLocationSharingServiceTest { private val startLiveLocationShareTask = mockk() private val stopLiveLocationShareTask = mockk() private val checkIfExistingActiveLiveTask = mockk() + private val redactLiveLocationShareTask = mockk() private val fakeLiveLocationShareAggregatedSummaryMapper = mockk() private val defaultLocationSharingService = DefaultLocationSharingService( @@ -72,6 +76,7 @@ internal class DefaultLocationSharingServiceTest { startLiveLocationShareTask = startLiveLocationShareTask, stopLiveLocationShareTask = stopLiveLocationShareTask, checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask, + redactLiveLocationShareTask = redactLiveLocationShareTask, liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper ) @@ -209,6 +214,20 @@ internal class DefaultLocationSharingServiceTest { coVerify { stopLiveLocationShareTask.execute(expectedParams) } } + @Test + fun `live location share can be redacted`() = runTest { + coEvery { redactLiveLocationShareTask.execute(any()) } just runs + + defaultLocationSharingService.redactLiveLocationShare(beaconInfoEventId = AN_EVENT_ID, reason = A_REASON) + + val expectedParams = RedactLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + reason = A_REASON + ) + coVerify { redactLiveLocationShareTask.execute(expectedParams) } + } + @Test fun `livedata of live summaries is correctly computed`() { val entity = LiveLocationShareAggregatedSummaryEntity() From 391bf842b4b266f3fabd9fd4ea840e3957d1563e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 5 Jul 2022 16:28:44 +0200 Subject: [PATCH 07/19] Calling usecase to redact live location share event --- .../sdk/api/session/events/model/Event.kt | 2 + .../room/location/LocationSharingService.kt | 1 - .../session/room/timeline/TimelineEvent.kt | 5 ++ .../home/room/detail/TimelineViewModel.kt | 11 ++++- .../RedactLiveLocationShareEventUseCase.kt | 30 ++++++++++++ ...RedactLiveLocationShareEventUseCaseTest.kt | 49 +++++++++++++++++++ .../test/fakes/FakeLocationSharingService.kt | 11 +++++ 7 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCase.kt create mode 100644 vector/src/test/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCaseTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 554dc2ec9d..59dc6c434d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -371,6 +371,8 @@ fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClear fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER +fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO + fun Event.getRelationContent(): RelationDefaultContent? { return if (isEncrypted()) { content.toModel()?.relatesTo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt index 50a87fbdc2..cd8acbcccc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.room.location -import androidx.annotation.MainThread import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.util.Cancelable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 9d8c8a13bd..d391abf1e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.isEdition +import org.matrix.android.sdk.api.session.events.model.isLiveLocation import org.matrix.android.sdk.api.session.events.model.isPoll import org.matrix.android.sdk.api.session.events.model.isReply import org.matrix.android.sdk.api.session.events.model.isSticker @@ -165,6 +166,10 @@ fun TimelineEvent.isSticker(): Boolean { return root.isSticker() } +fun TimelineEvent.isLiveLocation(): Boolean { + return root.isLiveLocation() +} + /** * Returns whether or not the event is a root thread event. */ 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 e305ccbec1..1a68371222 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 @@ -48,6 +48,7 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.createdirect.DirectRoomHelper import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider +import im.vector.app.features.home.room.detail.location.RedactLiveLocationShareEventUseCase import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever @@ -105,6 +106,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.isLiveLocation import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState import org.matrix.android.sdk.api.session.threads.ThreadNotificationState @@ -135,6 +137,7 @@ class TimelineViewModel @AssistedInject constructor( private val notificationDrawerManager: NotificationDrawerManager, private val locationSharingServiceConnection: LocationSharingServiceConnection, private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase, + private val redactLiveLocationShareEventUseCase: RedactLiveLocationShareEventUseCase, timelineFactory: TimelineFactory, appStateHandler: AppStateHandler, ) : VectorViewModel(initialState), @@ -770,7 +773,13 @@ class TimelineViewModel @AssistedInject constructor( private fun handleRedactEvent(action: RoomDetailAction.RedactAction) { val event = room.getTimelineEvent(action.targetEventId) ?: return - room.sendService().redactEvent(event.root, action.reason) + if (event.isLiveLocation()) { + viewModelScope.launch { + redactLiveLocationShareEventUseCase.execute(event.root, room, action.reason) + } + } else { + room.sendService().redactEvent(event.root, action.reason) + } } private fun handleUndoReact(action: RoomDetailAction.UndoReaction) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCase.kt new file mode 100644 index 0000000000..ba91000b40 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCase.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.location + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.Room +import javax.inject.Inject + +class RedactLiveLocationShareEventUseCase @Inject constructor() { + + suspend fun execute(event: Event, room: Room, reason: String?) { + event.eventId + ?.takeUnless { it.isEmpty() } + ?.let { room.locationSharingService().redactLiveLocationShare(it, reason) } + } +} diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCaseTest.kt new file mode 100644 index 0000000000..89b619fe3c --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCaseTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.location + +import im.vector.app.test.fakes.FakeRoom +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event + +private const val AN_EVENT_ID = "event-id" +private const val A_REASON = "reason" + +class RedactLiveLocationShareEventUseCaseTest { + + private val fakeRoom = FakeRoom() + + private val redactLiveLocationShareEventUseCase = RedactLiveLocationShareEventUseCase() + + @Test + fun `given an event with valid id when calling use case then event is redacted in the room`() = runTest { + val event = Event(eventId = AN_EVENT_ID) + fakeRoom.locationSharingService().givenRedactLiveLocationShare(beaconInfoEventId = AN_EVENT_ID, reason = A_REASON) + + redactLiveLocationShareEventUseCase.execute(event = event, room = fakeRoom, reason = A_REASON) + + fakeRoom.locationSharingService().verifyRedactLiveLocationShare(beaconInfoEventId = AN_EVENT_ID, reason = A_REASON) + } + + @Test + fun `given an event with empty id when calling use case then nothing is done`() = runTest { + val event = Event(eventId = "") + + redactLiveLocationShareEventUseCase.execute(event = event, room = fakeRoom, reason = A_REASON) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt index cebd45b2bb..accb3be877 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt @@ -19,8 +19,11 @@ package im.vector.app.test.fakes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every +import io.mockk.just import io.mockk.mockk +import io.mockk.runs import org.matrix.android.sdk.api.session.room.location.LocationSharingService import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary @@ -48,4 +51,12 @@ class FakeLocationSharingService : LocationSharingService by mockk() { fun givenStopLiveLocationShareReturns(result: UpdateLiveLocationShareResult) { coEvery { stopLiveLocationShare() } returns result } + + fun givenRedactLiveLocationShare(beaconInfoEventId: String, reason: String?) { + coEvery { redactLiveLocationShare(beaconInfoEventId, reason) } just runs + } + + fun verifyRedactLiveLocationShare(beaconInfoEventId: String, reason: String?) { + coVerify { redactLiveLocationShare(beaconInfoEventId, reason) } + } } From d3ad8d8deb7cf97dccb905175b3b4cce6f204d47 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 5 Jul 2022 17:31:40 +0200 Subject: [PATCH 08/19] Prune event entity when redacted --- .../sdk/internal/session/room/prune/RedactionEventProcessor.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index e33fbb56b1..a0f227bb9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -74,6 +74,7 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr when (typeToPrune) { EventType.ENCRYPTED, EventType.MESSAGE, + in EventType.STATE_ROOM_BEACON_INFO, in EventType.POLL_START -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") val unsignedData = EventMapper.map(eventToPrune).unsignedData From f6415b0a5d4641be639db20800b1db9e0f411ab0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 6 Jul 2022 15:02:23 +0200 Subject: [PATCH 09/19] Deleting summaries related to a redacted live location sharing --- .../EventAnnotationsSummaryEntityQuery.kt | 9 ++ .../sdk/internal/session/SessionModule.kt | 5 + ...iveLocationShareRedactionEventProcessor.kt | 65 +++++++++++ ...ocationShareRedactionEventProcessorTest.kt | 106 ++++++++++++++++++ .../android/sdk/test/fakes/FakeRealm.kt | 10 ++ 5 files changed, 195 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt index 6caa832110..1c19c21de2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt @@ -23,6 +23,11 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery { + return realm.where() + .equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId) +} + internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery { return realm.where() .equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId) @@ -44,3 +49,7 @@ internal fun EventAnnotationsSummaryEntity.Companion.getOrCreate(realm: Realm, r return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId) } + +internal fun EventAnnotationsSummaryEntity.Companion.get(realm: Realm, eventId: String): EventAnnotationsSummaryEntity? { + return EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index f8a52f0b7e..b9f56cbc9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -88,6 +88,7 @@ import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationPro import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor +import org.matrix.android.sdk.internal.session.room.location.LiveLocationShareRedactionEventProcessor import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessorCoroutine @@ -321,6 +322,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventRedactionProcessor(processor: RedactionEventProcessor): EventInsertLiveProcessor + @Binds + @IntoSet + abstract fun bindLiveLocationShareRedactionEventProcessor(processor: LiveLocationShareRedactionEventProcessor): EventInsertLiveProcessor + @Binds @IntoSet abstract fun bindEventRelationsAggregationProcessor(processor: EventRelationsAggregationProcessor): EventInsertLiveProcessor diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt new file mode 100644 index 0000000000..fa3479ed3c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessor.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2022 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.location + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor +import timber.log.Timber +import javax.inject.Inject + +/** + * Listens to the database for the insertion of any redaction event. + * Delete specifically the aggregated summary related to a redacted live location share event. + */ +internal class LiveLocationShareRedactionEventProcessor @Inject constructor() : EventInsertLiveProcessor { + + override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { + return eventType == EventType.REDACTION && insertType != EventInsertType.LOCAL_ECHO + } + + override suspend fun process(realm: Realm, event: Event) { + if (event.redacts.isNullOrBlank() || LocalEcho.isLocalEchoId(event.eventId.orEmpty())) { + return + } + + val redactedEvent = EventEntity.where(realm, eventId = event.redacts).findFirst() + ?: return + + if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) { + val liveSummary = LiveLocationShareAggregatedSummaryEntity.get(realm, eventId = redactedEvent.eventId) + + if (liveSummary != null) { + Timber.d("deleting live summary with id: ${liveSummary.eventId}") + liveSummary.deleteFromRealm() + val annotationsSummary = EventAnnotationsSummaryEntity.get(realm, eventId = redactedEvent.eventId) + if (annotationsSummary != null) { + Timber.d("deleting annotation summary with id: ${annotationsSummary.eventId}") + annotationsSummary.deleteFromRealm() + } + } + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt new file mode 100644 index 0000000000..24d9c30039 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/LiveLocationShareRedactionEventProcessorTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 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.location + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.givenDelete +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst + +private const val AN_EVENT_ID = "event-id" +private const val A_REDACTED_EVENT_ID = "redacted-event-id" + +@ExperimentalCoroutinesApi +class LiveLocationShareRedactionEventProcessorTest { + + private val liveLocationShareRedactionEventProcessor = LiveLocationShareRedactionEventProcessor() + private val fakeRealm = FakeRealm() + + @Test + fun `given an event when checking if it should be processed then only event of type REDACTED is processed`() { + val eventId = AN_EVENT_ID + val eventType = EventType.REDACTION + val insertType = EventInsertType.INCREMENTAL_SYNC + + val result = liveLocationShareRedactionEventProcessor.shouldProcess( + eventId = eventId, + eventType = eventType, + insertType = insertType + ) + + result shouldBe true + } + + @Test + fun `given an event when checking if it should be processed then local echo is not processed`() { + val eventId = AN_EVENT_ID + val eventType = EventType.REDACTION + val insertType = EventInsertType.LOCAL_ECHO + + val result = liveLocationShareRedactionEventProcessor.shouldProcess( + eventId = eventId, + eventType = eventType, + insertType = insertType + ) + + result shouldBe false + } + + @Test + fun `given a redacted live location share event when processing it then related summaries are deleted from database`() = runTest { + val event = Event(eventId = AN_EVENT_ID, redacts = A_REDACTED_EVENT_ID) + val redactedEventEntity = EventEntity(eventId = A_REDACTED_EVENT_ID, type = EventType.STATE_ROOM_BEACON_INFO.first()) + fakeRealm.givenWhere() + .givenEqualTo(EventEntityFields.EVENT_ID, A_REDACTED_EVENT_ID) + .givenFindFirst(redactedEventEntity) + val liveSummary = mockk() + every { liveSummary.eventId } returns A_REDACTED_EVENT_ID + liveSummary.givenDelete() + fakeRealm.givenWhere() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, A_REDACTED_EVENT_ID) + .givenFindFirst(liveSummary) + val annotationsSummary = mockk() + every { annotationsSummary.eventId } returns A_REDACTED_EVENT_ID + annotationsSummary.givenDelete() + fakeRealm.givenWhere() + .givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_REDACTED_EVENT_ID) + .givenFindFirst(annotationsSummary) + + liveLocationShareRedactionEventProcessor.process(fakeRealm.instance, event = event) + + verify { + liveSummary.deleteFromRealm() + annotationsSummary.deleteFromRealm() + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index 0ebff87278..cb40889fb7 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -18,10 +18,13 @@ package org.matrix.android.sdk.test.fakes import io.mockk.MockKVerificationScope import io.mockk.every +import io.mockk.just import io.mockk.mockk +import io.mockk.runs import io.mockk.verify import io.realm.Realm import io.realm.RealmModel +import io.realm.RealmObject import io.realm.RealmQuery import io.realm.RealmResults import io.realm.kotlin.where @@ -97,3 +100,10 @@ inline fun RealmQuery.givenIsNotNull( every { isNotNull(fieldName) } returns this return this } + +/** + * Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked. + */ +fun RealmObject.givenDelete() { + every { deleteFromRealm() } just runs +} From 63626b79de3fdd40a40303925bff6c7ef6dbcacb Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Fri, 8 Jul 2022 16:39:50 +0200 Subject: [PATCH 10/19] Redact beacon info event first to refresh timeline faster --- .../room/location/RedactLiveLocationShareTask.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt index 9883d1204b..61a9a81c03 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt @@ -45,6 +45,11 @@ internal class DefaultRedactLiveLocationShareTask @Inject constructor( val relatedEventIds = getRelatedEventIdsOfLive(params.beaconInfoEventId) Timber.d("beacon with id ${params.beaconInfoEventId} has related event ids: ${relatedEventIds.joinToString(", ")}") + redactEvent( + eventId = params.beaconInfoEventId, + roomId = params.roomId, + reason = params.reason + ) relatedEventIds.forEach { eventId -> redactEvent( eventId = eventId, @@ -52,12 +57,6 @@ internal class DefaultRedactLiveLocationShareTask @Inject constructor( reason = params.reason ) } - - redactEvent( - eventId = params.beaconInfoEventId, - roomId = params.roomId, - reason = params.reason - ) } private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List { From 8fb402ab101ee674e3ddd7b20a0f77d07db84ee2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 11 Jul 2022 14:42:33 +0200 Subject: [PATCH 11/19] Stop sharing live location if live is redacted --- .../location/LocationSharingAndroidService.kt | 10 +++- ...eckIfLiveLocationShareIsRedactedUseCase.kt | 39 ++++++++++++ .../GetLiveLocationShareSummaryUseCase.kt | 6 +- ...fLiveLocationShareIsRedactedUseCaseTest.kt | 60 +++++++++++++++++++ .../GetLiveLocationShareSummaryUseCaseTest.kt | 15 ++++- .../vector/app/test/fakes/FakeEventService.kt | 29 +++++++++ .../test/fakes/FakeLocationSharingService.kt | 2 +- .../im/vector/app/test/fakes/FakeSession.kt | 2 + 8 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCase.kt create mode 100644 vector/src/test/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeEventService.kt diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt index 69ffc0e89e..b3c88ccd93 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt @@ -24,6 +24,7 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.services.VectorAndroidService +import im.vector.app.features.location.live.CheckIfLiveLocationShareIsRedactedUseCase import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.session.coroutineScope @@ -55,6 +56,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca @Inject lateinit var locationTracker: LocationTracker @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase + @Inject lateinit var checkIfLiveLocationShareIsRedactedUseCase: CheckIfLiveLocationShareIsRedactedUseCase private val binder = LocalBinder() @@ -203,14 +205,18 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca private fun listenForLiveSummaryChanges(roomId: String, beaconEventId: String) { launchWithActiveSession { session -> val job = getLiveLocationShareSummaryUseCase.execute(roomId, beaconEventId) - .distinctUntilChangedBy { it.isActive } - .filter { it.isActive == false } + .distinctUntilChangedBy { it?.isActive } + .filter { it?.isActive == false || (it == null && isLiveRedacted(roomId, beaconEventId)) } .onEach { stopSharingLocation(beaconEventId) } .launchIn(session.coroutineScope) jobs.add(job) } } + private suspend fun isLiveRedacted(roomId: String, beaconEventId: String): Boolean { + return checkIfLiveLocationShareIsRedactedUseCase.execute(roomId = roomId, eventId = beaconEventId) + } + private fun launchWithActiveSession(block: suspend CoroutineScope.(Session) -> Unit) = activeSessionHolder .getSafeActiveSession() diff --git a/vector/src/main/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCase.kt b/vector/src/main/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCase.kt new file mode 100644 index 0000000000..123e1be7a3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCase.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.location.live + +import org.matrix.android.sdk.api.session.Session +import timber.log.Timber +import javax.inject.Inject + +class CheckIfLiveLocationShareIsRedactedUseCase @Inject constructor( + private val session: Session, +) { + + suspend fun execute(roomId: String, eventId: String): Boolean { + Timber.d("checking if event is redacted for roomId=$roomId and eventId=$eventId") + return try { + session.eventService() + .getEvent(roomId, eventId) + .isRedacted() + .also { Timber.d("event isRedacted=$it") } + } catch (error: Exception) { + Timber.e(error, "error when getting event, it may not exist yet") + false + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCase.kt b/vector/src/main/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCase.kt index 0d8b70ccda..bc38889d7f 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCase.kt @@ -19,7 +19,7 @@ package im.vector.app.features.location.live import androidx.lifecycle.asFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom @@ -31,13 +31,13 @@ class GetLiveLocationShareSummaryUseCase @Inject constructor( private val session: Session, ) { - suspend fun execute(roomId: String, eventId: String): Flow = withContext(session.coroutineDispatchers.main) { + suspend fun execute(roomId: String, eventId: String): Flow = withContext(session.coroutineDispatchers.main) { Timber.d("getting flow for roomId=$roomId and eventId=$eventId") session.getRoom(roomId) ?.locationSharingService() ?.getLiveLocationShareSummary(eventId) ?.asFlow() - ?.mapNotNull { it.getOrNull() } + ?.map { it.getOrNull() } ?: emptyFlow() } } diff --git a/vector/src/test/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCaseTest.kt new file mode 100644 index 0000000000..0cb6a09ad5 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCaseTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.location.live + +import im.vector.app.test.fakes.FakeSession +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.UnsignedData + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" + +class CheckIfLiveLocationShareIsRedactedUseCaseTest { + + private val fakeSession = FakeSession() + + private val checkIfLiveLocationShareIsRedactedUseCase = CheckIfLiveLocationShareIsRedactedUseCase( + session = fakeSession + ) + + @Test + fun `given a room id and event id for redacted event when calling use case then true is returned`() = runTest { + val event = Event( + unsignedData = UnsignedData(age = 123, redactedEvent = Event()) + ) + fakeSession.eventService() + .givenGetEventReturns(event) + + val result = checkIfLiveLocationShareIsRedactedUseCase.execute(A_ROOM_ID, AN_EVENT_ID) + + result shouldBeEqualTo true + } + + @Test + fun `given a room id and event id for non redacted event when calling use case then false is returned`() = runTest { + val event = Event() + fakeSession.eventService() + .givenGetEventReturns(event) + + val result = checkIfLiveLocationShareIsRedactedUseCase.execute(A_ROOM_ID, AN_EVENT_ID) + + result shouldBeEqualTo false + } +} diff --git a/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt index fed825154c..ed1bcebf16 100644 --- a/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt @@ -53,7 +53,7 @@ class GetLiveLocationShareSummaryUseCaseTest { } @Test - fun `given a room id and event id when calling use case then live data on summary is returned`() = runTest { + fun `given a room id and event id when calling use case then flow on summary is returned`() = runTest { val summary = LiveLocationShareAggregatedSummary( userId = "userId", isActive = true, @@ -70,4 +70,17 @@ class GetLiveLocationShareSummaryUseCaseTest { result shouldBeEqualTo summary } + + @Test + fun `given a room id, event id and a null summary when calling use case then null is emitted in the flow`() = runTest { + fakeSession.roomService() + .getRoom(A_ROOM_ID) + .locationSharingService() + .givenLiveLocationShareSummaryReturns(AN_EVENT_ID, null) + .givenAsFlowReturns(Optional(null)) + + val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first() + + result shouldBeEqualTo null + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeEventService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeEventService.kt new file mode 100644 index 0000000000..167f1d624b --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeEventService.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import io.mockk.coEvery +import io.mockk.mockk +import org.matrix.android.sdk.api.session.events.EventService +import org.matrix.android.sdk.api.session.events.model.Event + +class FakeEventService : EventService by mockk() { + + fun givenGetEventReturns(event: Event) { + coEvery { getEvent(any(), any()) } returns event + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt index accb3be877..d85403a274 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt @@ -41,7 +41,7 @@ class FakeLocationSharingService : LocationSharingService by mockk() { fun givenLiveLocationShareSummaryReturns( eventId: String, - summary: LiveLocationShareAggregatedSummary + summary: LiveLocationShareAggregatedSummary? ): LiveData> { return MutableLiveData(Optional(summary)).also { every { getLiveLocationShareSummary(eventId) } returns it diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index 3af15a7e5c..65295af3dd 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -35,6 +35,7 @@ class FakeSession( val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), private val fakeRoomService: FakeRoomService = FakeRoomService(), + private val fakeEventService: FakeEventService = FakeEventService(), ) : Session by mockk(relaxed = true) { init { @@ -50,6 +51,7 @@ class FakeSession( override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = fakeHomeServerCapabilitiesService override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun roomService() = fakeRoomService + override fun eventService() = fakeEventService fun givenVectorStore(vectorSessionStore: VectorSessionStore) { coEvery { From 127737649689b2fa0913fb6ccdeea2e3a349de1a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 12 Jul 2022 16:58:02 +0200 Subject: [PATCH 12/19] Fix missing handling of redacted beacon location data events --- .../session/room/prune/RedactionEventProcessor.kt | 1 + .../detail/timeline/factory/TimelineItemFactory.kt | 10 ++++++++-- .../timeline/helper/TimelineDisplayableEvents.kt | 2 +- .../timeline/helper/TimelineEventVisibilityHelper.kt | 4 ++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index a0f227bb9f..cc86679cbc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -75,6 +75,7 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr EventType.ENCRYPTED, EventType.MESSAGE, in EventType.STATE_ROOM_BEACON_INFO, + in EventType.BEACON_LOCATION_DATA, in EventType.POLL_START -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") val unsignedData = EventMapper.map(eventToPrune).unsignedData diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 97ae3b634e..6c5a66d39d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -113,8 +113,14 @@ class TimelineItemFactory @Inject constructor( EventType.CALL_NEGOTIATE, EventType.REACTION, in EventType.POLL_RESPONSE, - in EventType.POLL_END, - in EventType.BEACON_LOCATION_DATA -> noticeItemFactory.create(params) + in EventType.POLL_END -> noticeItemFactory.create(params) + in EventType.BEACON_LOCATION_DATA -> { + if (event.root.isRedacted()) { + messageItemFactory.create(params) + } else { + noticeItemFactory.create(params) + } + } // Calls EventType.CALL_INVITE, EventType.CALL_HANGUP, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index e9f8e35dc9..23db2a721c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -51,7 +51,7 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_JOIN_RULES, EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL, - ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA } fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index 8af708fca1..e6765bf35a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -241,6 +241,10 @@ class TimelineEventVisibilityHelper @Inject constructor( } else root.eventId != rootThreadEventId } + if (root.getClearType() in EventType.BEACON_LOCATION_DATA) { + return !root.isRedacted() + } + return false } From 17227f1ae62db2b3a2ad162fae8d7ef7ee7d110a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 18 Jul 2022 10:14:47 +0200 Subject: [PATCH 13/19] DB migration --- .../database/RealmSessionStoreMigration.kt | 4 ++- .../database/migration/MigrateSessionTo033.kt | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt 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 b54aec26b2..9784412761 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 @@ -49,6 +49,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -57,7 +58,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 32L, + schemaVersion = 33L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -99,5 +100,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 30) MigrateSessionTo030(realm).perform() if (oldVersion < 31) MigrateSessionTo031(realm).perform() if (oldVersion < 32) MigrateSessionTo032(realm).perform() + if (oldVersion < 33) MigrateSessionTo033(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt new file mode 100644 index 0000000000..0e3a8599c5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo033.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 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.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * Migrating to: + * Live location sharing aggregated summary: adding new field relatedEventIds. + */ +internal class MigrateSessionTo033(realm: DynamicRealm) : RealmMigrator(realm, 33) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("LiveLocationShareAggregatedSummaryEntity") + ?.addRealmListField(LiveLocationShareAggregatedSummaryEntityFields.RELATED_EVENT_IDS.`$`, String::class.java) + } +} From 7b15193eff28b867567c26b674241e1f6de1e930 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 18 Jul 2022 10:56:34 +0200 Subject: [PATCH 14/19] Renaming a method to reflect its implementation --- .../session/room/location/RedactLiveLocationShareTask.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt index 61a9a81c03..ac855b81e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/RedactLiveLocationShareTask.kt @@ -45,13 +45,13 @@ internal class DefaultRedactLiveLocationShareTask @Inject constructor( val relatedEventIds = getRelatedEventIdsOfLive(params.beaconInfoEventId) Timber.d("beacon with id ${params.beaconInfoEventId} has related event ids: ${relatedEventIds.joinToString(", ")}") - redactEvent( + postRedactionWithLocalEcho( eventId = params.beaconInfoEventId, roomId = params.roomId, reason = params.reason ) relatedEventIds.forEach { eventId -> - redactEvent( + postRedactionWithLocalEcho( eventId = eventId, roomId = params.roomId, reason = params.reason @@ -69,8 +69,8 @@ internal class DefaultRedactLiveLocationShareTask @Inject constructor( } } - private fun redactEvent(eventId: String, roomId: String, reason: String?) { - Timber.d("redacting event of id $eventId") + private fun postRedactionWithLocalEcho(eventId: String, roomId: String, reason: String?) { + Timber.d("posting redaction for event of id $eventId") val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, eventId, reason) localEchoEventFactory.createLocalEcho(redactionEcho) eventSenderProcessor.postRedaction(redactionEcho, reason) From 667b30f145d1eeef9d5f953d11f6d09c185c3817 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 18 Jul 2022 12:00:09 +0200 Subject: [PATCH 15/19] Refactoring redact task unit tests --- .../DefaultRedactLiveLocationShareTaskTest.kt | 94 ++++++++++--------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt index 66428ac064..b8618d1a79 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultRedactLiveLocationShareTaskTest.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields import org.matrix.android.sdk.test.fakes.FakeEventSenderProcessor @@ -58,59 +59,68 @@ class DefaultRedactLiveLocationShareTaskTest { } @Test - fun `given parameters when calling the task then it is correctly executed`() = runTest { - val params = RedactLiveLocationShareTask.Params( - roomId = A_ROOM_ID, - beaconInfoEventId = AN_EVENT_ID, - reason = A_REASON - ) - fakeRealmConfiguration.givenAwaitTransaction>(fakeRealm.instance) + fun `given parameters when redacting then post redact events and related and creates redact local echos`() = runTest { + val params = createParams() val relatedEventIds = listOf(AN_EVENT_ID_1, AN_EVENT_ID_2, AN_EVENT_ID_3) - val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity( - eventId = AN_EVENT_ID, - relatedEventIds = RealmList(*relatedEventIds.toTypedArray()), - ) - fakeRealm.givenWhere() - .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID) - .givenFindFirst(aggregatedSummaryEntity) - val redactedEvent = fakeLocalEchoEventFactory.givenCreateRedactEvent( - eventId = AN_EVENT_ID, - withLocalEcho = true - ) - fakeEventSenderProcessor.givenPostRedaction(event = redactedEvent, reason = A_REASON) - val redactedEvent1 = fakeLocalEchoEventFactory.givenCreateRedactEvent( - eventId = AN_EVENT_ID_1, - withLocalEcho = true - ) - fakeEventSenderProcessor.givenPostRedaction(event = redactedEvent1, reason = A_REASON) - val redactedEvent2 = fakeLocalEchoEventFactory.givenCreateRedactEvent( - eventId = AN_EVENT_ID_2, - withLocalEcho = true - ) - fakeEventSenderProcessor.givenPostRedaction(event = redactedEvent2, reason = A_REASON) - val redactedEvent3 = fakeLocalEchoEventFactory.givenCreateRedactEvent( - eventId = AN_EVENT_ID_3, - withLocalEcho = true - ) - fakeEventSenderProcessor.givenPostRedaction(event = redactedEvent3, reason = A_REASON) + val aggregatedSummaryEntity = createSummary(relatedEventIds) + givenSummaryForId(AN_EVENT_ID, aggregatedSummaryEntity) + fakeRealmConfiguration.givenAwaitTransaction>(fakeRealm.instance) + val redactEvents = givenCreateRedactEventWithLocalEcho(relatedEventIds + AN_EVENT_ID) + givenPostRedaction(redactEvents) defaultRedactLiveLocationShareTask.execute(params) - fakeLocalEchoEventFactory.verifyCreateRedactEvent( - roomId = A_ROOM_ID, + verifyCreateRedactEventForEventIds(relatedEventIds + AN_EVENT_ID) + verifyCreateLocalEchoForEvents(redactEvents) + } + + private fun createParams() = RedactLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + reason = A_REASON + ) + + private fun createSummary(relatedEventIds: List): LiveLocationShareAggregatedSummaryEntity { + return LiveLocationShareAggregatedSummaryEntity( eventId = AN_EVENT_ID, - reason = A_REASON + relatedEventIds = RealmList(*relatedEventIds.toTypedArray()), ) - fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactedEvent) - relatedEventIds.forEach { eventId -> + } + + private fun givenSummaryForId(eventId: String, aggregatedSummaryEntity: LiveLocationShareAggregatedSummaryEntity) { + fakeRealm.givenWhere() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId) + .givenFindFirst(aggregatedSummaryEntity) + } + + private fun givenCreateRedactEventWithLocalEcho(eventIds: List): List { + return eventIds.map { eventId -> + fakeLocalEchoEventFactory.givenCreateRedactEvent( + eventId = eventId, + withLocalEcho = true + ) + } + } + + private fun givenPostRedaction(redactEvents: List) { + redactEvents.forEach { + fakeEventSenderProcessor.givenPostRedaction(event = it, reason = A_REASON) + } + } + + private fun verifyCreateRedactEventForEventIds(eventIds: List) { + eventIds.forEach { eventId -> fakeLocalEchoEventFactory.verifyCreateRedactEvent( roomId = A_ROOM_ID, eventId = eventId, reason = A_REASON ) } - fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactedEvent1) - fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactedEvent2) - fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactedEvent3) + } + + private fun verifyCreateLocalEchoForEvents(events: List) { + events.forEach { redactionEvent -> + fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactionEvent) + } } } From 99fc4b4a212c38d3300922e826d16302ad6f917f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 18 Jul 2022 14:43:42 +0200 Subject: [PATCH 16/19] Simplify logic of the use case to check if event can be redacted --- .../timeline/action/CheckIfCanRedactEventUseCase.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt index 222df8f914..3bc3a5e351 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt @@ -29,10 +29,9 @@ class CheckIfCanRedactEventUseCase @Inject constructor( // Only some event types are supported for the moment val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO - if (event.root.getClearType() !in canRedactEventTypes) return false - // Message sent by the current user can always be redacted - if (event.root.senderId == activeSessionHolder.getActiveSession().myUserId) return true - // Check permission for messages sent by other users - return actionPermissions.canRedact + + return event.root.getClearType() in canRedactEventTypes && + // Message sent by the current user can always be redacted, else check permission for messages sent by other users + (event.root.senderId == activeSessionHolder.getActiveSession().myUserId || actionPermissions.canRedact) } } From 2121ec5739d6ce448af49c89f921dd5cd0945c76 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 18 Jul 2022 14:53:44 +0200 Subject: [PATCH 17/19] Moving use case to check if event is redacted into a dedicated package --- .../features/location/LocationSharingAndroidService.kt | 6 +++--- .../CheckIfEventIsRedactedUseCase.kt} | 4 ++-- .../CheckIfEventIsRedactedUseCaseTest.kt} | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) rename vector/src/main/java/im/vector/app/features/{location/live/CheckIfLiveLocationShareIsRedactedUseCase.kt => redaction/CheckIfEventIsRedactedUseCase.kt} (91%) rename vector/src/test/java/im/vector/app/features/{location/live/CheckIfLiveLocationShareIsRedactedUseCaseTest.kt => redaction/CheckIfEventIsRedactedUseCaseTest.kt} (81%) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt index b3c88ccd93..bf022e0088 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt @@ -24,7 +24,7 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.services.VectorAndroidService -import im.vector.app.features.location.live.CheckIfLiveLocationShareIsRedactedUseCase +import im.vector.app.features.redaction.CheckIfEventIsRedactedUseCase import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.session.coroutineScope @@ -56,7 +56,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca @Inject lateinit var locationTracker: LocationTracker @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase - @Inject lateinit var checkIfLiveLocationShareIsRedactedUseCase: CheckIfLiveLocationShareIsRedactedUseCase + @Inject lateinit var checkIfEventIsRedactedUseCase: CheckIfEventIsRedactedUseCase private val binder = LocalBinder() @@ -214,7 +214,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca } private suspend fun isLiveRedacted(roomId: String, beaconEventId: String): Boolean { - return checkIfLiveLocationShareIsRedactedUseCase.execute(roomId = roomId, eventId = beaconEventId) + return checkIfEventIsRedactedUseCase.execute(roomId = roomId, eventId = beaconEventId) } private fun launchWithActiveSession(block: suspend CoroutineScope.(Session) -> Unit) = diff --git a/vector/src/main/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCase.kt b/vector/src/main/java/im/vector/app/features/redaction/CheckIfEventIsRedactedUseCase.kt similarity index 91% rename from vector/src/main/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCase.kt rename to vector/src/main/java/im/vector/app/features/redaction/CheckIfEventIsRedactedUseCase.kt index 123e1be7a3..ac77455d66 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/redaction/CheckIfEventIsRedactedUseCase.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package im.vector.app.features.location.live +package im.vector.app.features.redaction import org.matrix.android.sdk.api.session.Session import timber.log.Timber import javax.inject.Inject -class CheckIfLiveLocationShareIsRedactedUseCase @Inject constructor( +class CheckIfEventIsRedactedUseCase @Inject constructor( private val session: Session, ) { diff --git a/vector/src/test/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/redaction/CheckIfEventIsRedactedUseCaseTest.kt similarity index 81% rename from vector/src/test/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCaseTest.kt rename to vector/src/test/java/im/vector/app/features/redaction/CheckIfEventIsRedactedUseCaseTest.kt index 0cb6a09ad5..7dffd78516 100644 --- a/vector/src/test/java/im/vector/app/features/location/live/CheckIfLiveLocationShareIsRedactedUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/redaction/CheckIfEventIsRedactedUseCaseTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.location.live +package im.vector.app.features.redaction import im.vector.app.test.fakes.FakeSession import kotlinx.coroutines.test.runTest @@ -26,11 +26,11 @@ import org.matrix.android.sdk.api.session.events.model.UnsignedData private const val A_ROOM_ID = "room_id" private const val AN_EVENT_ID = "event_id" -class CheckIfLiveLocationShareIsRedactedUseCaseTest { +class CheckIfEventIsRedactedUseCaseTest { private val fakeSession = FakeSession() - private val checkIfLiveLocationShareIsRedactedUseCase = CheckIfLiveLocationShareIsRedactedUseCase( + private val checkIfEventIsRedactedUseCase = CheckIfEventIsRedactedUseCase( session = fakeSession ) @@ -42,7 +42,7 @@ class CheckIfLiveLocationShareIsRedactedUseCaseTest { fakeSession.eventService() .givenGetEventReturns(event) - val result = checkIfLiveLocationShareIsRedactedUseCase.execute(A_ROOM_ID, AN_EVENT_ID) + val result = checkIfEventIsRedactedUseCase.execute(A_ROOM_ID, AN_EVENT_ID) result shouldBeEqualTo true } @@ -53,7 +53,7 @@ class CheckIfLiveLocationShareIsRedactedUseCaseTest { fakeSession.eventService() .givenGetEventReturns(event) - val result = checkIfLiveLocationShareIsRedactedUseCase.execute(A_ROOM_ID, AN_EVENT_ID) + val result = checkIfEventIsRedactedUseCase.execute(A_ROOM_ID, AN_EVENT_ID) result shouldBeEqualTo false } From 79615258690ac371041454dfaf3cd2d2928fbb45 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 18 Jul 2022 15:02:30 +0200 Subject: [PATCH 18/19] Unit tests: adding verification that redaction does not happen when event id is empty --- .../location/RedactLiveLocationShareEventUseCaseTest.kt | 2 ++ .../vector/app/test/fakes/FakeLocationSharingService.kt | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCaseTest.kt index 89b619fe3c..2ca285ef50 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/location/RedactLiveLocationShareEventUseCaseTest.kt @@ -45,5 +45,7 @@ class RedactLiveLocationShareEventUseCaseTest { val event = Event(eventId = "") redactLiveLocationShareEventUseCase.execute(event = event, room = fakeRoom, reason = A_REASON) + + fakeRoom.locationSharingService().verifyRedactLiveLocationShare(inverse = true, beaconInfoEventId = "", reason = A_REASON) } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt index d85403a274..ce498a715a 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt @@ -56,7 +56,12 @@ class FakeLocationSharingService : LocationSharingService by mockk() { coEvery { redactLiveLocationShare(beaconInfoEventId, reason) } just runs } - fun verifyRedactLiveLocationShare(beaconInfoEventId: String, reason: String?) { - coVerify { redactLiveLocationShare(beaconInfoEventId, reason) } + /** + * @param inverse when true it will check redaction of the live did not happen + * @param beaconInfoEventId event id of the beacon related to the live + * @param reason reason explaining the redaction + */ + fun verifyRedactLiveLocationShare(inverse: Boolean = false, beaconInfoEventId: String, reason: String?) { + coVerify(inverse = inverse) { redactLiveLocationShare(beaconInfoEventId, reason) } } } From b2d7ef9fbf18f894ac1af1e7a46bf3fc8e0b7c6e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Mon, 18 Jul 2022 15:04:57 +0200 Subject: [PATCH 19/19] Re-arranging imports --- .../app/features/location/LocationSharingAndroidService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt index bf022e0088..fb749c2581 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAndroidService.kt @@ -24,9 +24,9 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.services.VectorAndroidService -import im.vector.app.features.redaction.CheckIfEventIsRedactedUseCase import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase import im.vector.app.features.notifications.NotificationUtils +import im.vector.app.features.redaction.CheckIfEventIsRedactedUseCase import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job