Merge pull request #6486 from vector-im/feature/mna/delete-lls

[Location sharing] - Delete action on a live message (PSG-523)
This commit is contained in:
Maxime NATUREL 2022-07-19 16:43:41 +02:00 committed by GitHub
commit c3105c8bb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1061 additions and 43 deletions

1
changelog.d/6437.feature Normal file
View File

@ -0,0 +1 @@
[Location sharing] - Delete action on a live message

View File

@ -371,6 +371,8 @@ fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClear
fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO
fun Event.getRelationContent(): RelationDefaultContent? { fun Event.getRelationContent(): RelationDefaultContent? {
return if (isEncrypted()) { return if (isEncrypted()) {
content.toModel<EncryptedEventContent>()?.relatesTo content.toModel<EncryptedEventContent>()?.relatesTo

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.room.location package org.matrix.android.sdk.api.session.room.location
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
@ -59,16 +58,21 @@ interface LocationSharingService {
*/ */
suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult 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. * Returns a LiveData on the list of current running live location shares.
*/ */
@MainThread
fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>>
/** /**
* Returns a LiveData on the live location share summary with the given eventId. * Returns a LiveData on the live location share summary with the given eventId.
* @param beaconInfoEventId event id of the initial beacon info state event * @param beaconInfoEventId event id of the initial beacon info state event
*/ */
@MainThread
fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>> fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>>
} }

View File

@ -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.RelationType
import org.matrix.android.sdk.api.session.events.model.getRelationContent 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.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.isPoll
import org.matrix.android.sdk.api.session.events.model.isReply import org.matrix.android.sdk.api.session.events.model.isReply
import org.matrix.android.sdk.api.session.events.model.isSticker import org.matrix.android.sdk.api.session.events.model.isSticker
@ -165,6 +166,10 @@ fun TimelineEvent.isSticker(): Boolean {
return root.isSticker() return root.isSticker()
} }
fun TimelineEvent.isLiveLocation(): Boolean {
return root.isLiveLocation()
}
/** /**
* Returns whether or not the event is a root thread event. * Returns whether or not the event is a root thread event.
*/ */

View File

@ -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.MigrateSessionTo030
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 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.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.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject import javax.inject.Inject
@ -57,7 +58,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer private val normalizer: Normalizer
) : MatrixRealmMigration( ) : MatrixRealmMigration(
dbName = "Session", dbName = "Session",
schemaVersion = 32L, schemaVersion = 33L,
) { ) {
/** /**
* Forces all RealmSessionStoreMigration instances to be equal. * Forces all RealmSessionStoreMigration instances to be equal.
@ -99,5 +100,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 30) MigrateSessionTo030(realm).perform() if (oldVersion < 30) MigrateSessionTo030(realm).perform()
if (oldVersion < 31) MigrateSessionTo031(realm).perform() if (oldVersion < 31) MigrateSessionTo031(realm).perform()
if (oldVersion < 32) MigrateSessionTo032(realm).perform() if (oldVersion < 32) MigrateSessionTo032(realm).perform()
if (oldVersion < 33) MigrateSessionTo033(realm).perform()
} }
} }

View File

@ -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)
}
}

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.database.model.livelocation package org.matrix.android.sdk.internal.database.model.livelocation
import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
@ -29,6 +30,11 @@ internal open class LiveLocationShareAggregatedSummaryEntity(
@PrimaryKey @PrimaryKey
var eventId: String = "", var eventId: String = "",
/**
* List of event ids used to compute the aggregated summary data.
*/
var relatedEventIds: RealmList<String> = RealmList(),
var roomId: String = "", var roomId: String = "",
var userId: String = "", var userId: String = "",

View File

@ -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.EventAnnotationsSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> {
return realm.where<EventAnnotationsSummaryEntity>()
.equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId)
}
internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> { internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> {
return realm.where<EventAnnotationsSummaryEntity>() return realm.where<EventAnnotationsSummaryEntity>()
.equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId) .equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId)
@ -44,3 +49,7 @@ internal fun EventAnnotationsSummaryEntity.Companion.getOrCreate(realm: Realm, r
return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId) ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
} }
internal fun EventAnnotationsSummaryEntity.Companion.get(realm: Realm, eventId: String): EventAnnotationsSummaryEntity? {
return EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
}

View File

@ -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.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
realm: Realm,
eventId: String,
): RealmQuery<LiveLocationShareAggregatedSummaryEntity> {
return realm.where<LiveLocationShareAggregatedSummaryEntity>()
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
}
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where( internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
realm: Realm, realm: Realm,
roomId: String, roomId: String,
@ -72,6 +80,13 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get(
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst() 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( internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveInRoomForUser(
realm: Realm, realm: Realm,
roomId: String, roomId: String,

View File

@ -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.DefaultPollAggregationProcessor
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor 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.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.prune.RedactionEventProcessor
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessorCoroutine import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessorCoroutine
@ -321,6 +322,10 @@ internal abstract class SessionModule {
@IntoSet @IntoSet
abstract fun bindEventRedactionProcessor(processor: RedactionEventProcessor): EventInsertLiveProcessor abstract fun bindEventRedactionProcessor(processor: RedactionEventProcessor): EventInsertLiveProcessor
@Binds
@IntoSet
abstract fun bindLiveLocationShareRedactionEventProcessor(processor: LiveLocationShareRedactionEventProcessor): EventInsertLiveProcessor
@Binds @Binds
@IntoSet @IntoSet
abstract fun bindEventRelationsAggregationProcessor(processor: EventRelationsAggregationProcessor): EventInsertLiveProcessor abstract fun bindEventRelationsAggregationProcessor(processor: EventRelationsAggregationProcessor): EventInsertLiveProcessor

View File

@ -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.CheckIfExistingActiveLiveTask
import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask 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.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.DefaultSendLiveLocationTask
import org.matrix.android.sdk.internal.session.room.location.DefaultSendStaticLocationTask 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.DefaultStartLiveLocationShareTask
import org.matrix.android.sdk.internal.session.room.location.DefaultStopLiveLocationShareTask 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.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.SendLiveLocationTask
import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask
import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask
@ -339,4 +341,7 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask
@Binds
abstract fun bindRedactLiveLocationShareTask(task: DefaultRedactLiveLocationShareTask): RedactLiveLocationShareTask
} }

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import io.realm.Realm import io.realm.Realm
import io.realm.RealmList
import org.matrix.android.sdk.api.extensions.orTrue 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.Event
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
@ -73,6 +74,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
eventId = targetEventId 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 // remote event can stay with isLive == true while the local summary is no more active
val isActive = aggregatedSummary.isActive.orTrue() && isLive val isActive = aggregatedSummary.isActive.orTrue() && isLive
val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) } val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
@ -144,6 +150,11 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
roomId = roomId, roomId = roomId,
eventId = relatedEventId eventId = relatedEventId
) )
if (!event.eventId.isNullOrEmpty()) {
addRelatedEventId(event.eventId, aggregatedSummary)
}
val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0 val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0
val currentLocationTimestamp = ContentMapper val currentLocationTimestamp = ContentMapper
.map(aggregatedSummary.lastLocationContent) .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) { private fun deactivateAllPreviousBeacons(realm: Realm, roomId: String, userId: String, currentEventId: String) {
LiveLocationShareAggregatedSummaryEntity LiveLocationShareAggregatedSummaryEntity
.findActiveLiveInRoomForUser( .findActiveLiveInRoomForUser(

View File

@ -42,6 +42,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
private val startLiveLocationShareTask: StartLiveLocationShareTask, private val startLiveLocationShareTask: StartLiveLocationShareTask,
private val stopLiveLocationShareTask: StopLiveLocationShareTask, private val stopLiveLocationShareTask: StopLiveLocationShareTask,
private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask, private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask,
private val redactLiveLocationShareTask: RedactLiveLocationShareTask,
private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper, private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper,
) : LocationSharingService { ) : LocationSharingService {
@ -102,6 +103,15 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
return stopLiveLocationShareTask.execute(params) 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<List<LiveLocationShareAggregatedSummary>> { override fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> {
return monarchy.findAllMappedWithChanges( return monarchy.findAllMappedWithChanges(
{ LiveLocationShareAggregatedSummaryEntity.findRunningLiveInRoom(it, roomId = roomId) }, { LiveLocationShareAggregatedSummaryEntity.findRunningLiveInRoom(it, roomId = roomId) },

View File

@ -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()
}
}
}
}
}

View File

@ -0,0 +1,78 @@
/*
* 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<RedactLiveLocationShareTask.Params, Unit> {
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(", ")}")
postRedactionWithLocalEcho(
eventId = params.beaconInfoEventId,
roomId = params.roomId,
reason = params.reason
)
relatedEventIds.forEach { eventId ->
postRedactionWithLocalEcho(
eventId = eventId,
roomId = params.roomId,
reason = params.reason
)
}
}
private suspend fun getRelatedEventIdsOfLive(beaconInfoEventId: String): List<String> {
return awaitTransaction(realmConfiguration) { realm ->
val aggregatedSummaryEntity = LiveLocationShareAggregatedSummaryEntity.get(
realm = realm,
eventId = beaconInfoEventId
)
aggregatedSummaryEntity?.relatedEventIds?.toList() ?: emptyList()
}
}
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)
}
}

View File

@ -74,6 +74,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
when (typeToPrune) { when (typeToPrune) {
EventType.ENCRYPTED, EventType.ENCRYPTED,
EventType.MESSAGE, EventType.MESSAGE,
in EventType.STATE_ROOM_BEACON_INFO,
in EventType.BEACON_LOCATION_DATA,
in EventType.POLL_START -> { in EventType.POLL_START -> {
Timber.d("REDACTION for message ${eventToPrune.eventId}") Timber.d("REDACTION for message ${eventToPrune.eventId}")
val unsignedData = EventMapper.map(eventToPrune).unsignedData val unsignedData = EventMapper.map(eventToPrune).unsignedData

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldContain
import org.junit.Test import org.junit.Test
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.UnsignedData import org.matrix.android.sdk.api.session.events.model.UnsignedData
@ -199,9 +200,10 @@ internal class LiveLocationAggregationProcessorTest {
age = 123, age = 123,
replacesState = AN_EVENT_ID replacesState = AN_EVENT_ID
) )
val stateEventId = "state-event-id"
val event = Event( val event = Event(
senderId = A_SENDER_ID, senderId = A_SENDER_ID,
eventId = "", eventId = stateEventId,
unsignedData = unsignedData unsignedData = unsignedData
) )
val beaconInfo = MessageBeaconInfoContent( val beaconInfo = MessageBeaconInfoContent(
@ -237,6 +239,7 @@ internal class LiveLocationAggregationProcessorTest {
aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID
aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID
aggregatedEntity.isActive shouldBeEqualTo false aggregatedEntity.isActive shouldBeEqualTo false
aggregatedEntity.relatedEventIds shouldContain stateEventId
aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS
aggregatedEntity.lastLocationContent shouldBeEqualTo null aggregatedEntity.lastLocationContent shouldBeEqualTo null
previousEntities.forEach { entity -> previousEntities.forEach { entity ->
@ -324,7 +327,7 @@ internal class LiveLocationAggregationProcessorTest {
val lastBeaconLocationContent = MessageBeaconLocationDataContent( val lastBeaconLocationContent = MessageBeaconLocationDataContent(
unstableTimestampMillis = A_TIMESTAMP unstableTimestampMillis = A_TIMESTAMP
) )
givenLastSummaryQueryReturns( val aggregatedEntity = givenLastSummaryQueryReturns(
eventId = AN_EVENT_ID, eventId = AN_EVENT_ID,
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
beaconLocationContent = lastBeaconLocationContent beaconLocationContent = lastBeaconLocationContent
@ -340,6 +343,7 @@ internal class LiveLocationAggregationProcessorTest {
) )
result shouldBeEqualTo false result shouldBeEqualTo false
aggregatedEntity.relatedEventIds shouldContain AN_EVENT_ID
} }
@Test @Test
@ -353,7 +357,7 @@ internal class LiveLocationAggregationProcessorTest {
val lastBeaconLocationContent = MessageBeaconLocationDataContent( val lastBeaconLocationContent = MessageBeaconLocationDataContent(
unstableTimestampMillis = A_TIMESTAMP - 60_000 unstableTimestampMillis = A_TIMESTAMP - 60_000
) )
val entity = givenLastSummaryQueryReturns( val aggregatedEntity = givenLastSummaryQueryReturns(
eventId = AN_EVENT_ID, eventId = AN_EVENT_ID,
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
beaconLocationContent = lastBeaconLocationContent beaconLocationContent = lastBeaconLocationContent
@ -369,7 +373,8 @@ internal class LiveLocationAggregationProcessorTest {
) )
result shouldBeEqualTo true result shouldBeEqualTo true
val savedLocationData = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>() aggregatedEntity.relatedEventIds shouldContain AN_EVENT_ID
val savedLocationData = ContentMapper.map(aggregatedEntity.lastLocationContent).toModel<MessageBeaconLocationDataContent>()
savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP
savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI
} }

View File

@ -22,8 +22,10 @@ import androidx.lifecycle.Transformations
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.coVerify import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.slot import io.mockk.slot
import io.mockk.unmockkAll import io.mockk.unmockkAll
import kotlinx.coroutines.ExperimentalCoroutinesApi 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 AN_UNCERTAINTY = 5.0
private const val A_TIMEOUT = 15_000L private const val A_TIMEOUT = 15_000L
private const val A_DESCRIPTION = "description" private const val A_DESCRIPTION = "description"
private const val A_REASON = "reason"
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
internal class DefaultLocationSharingServiceTest { internal class DefaultLocationSharingServiceTest {
@ -62,6 +65,7 @@ internal class DefaultLocationSharingServiceTest {
private val startLiveLocationShareTask = mockk<StartLiveLocationShareTask>() private val startLiveLocationShareTask = mockk<StartLiveLocationShareTask>()
private val stopLiveLocationShareTask = mockk<StopLiveLocationShareTask>() private val stopLiveLocationShareTask = mockk<StopLiveLocationShareTask>()
private val checkIfExistingActiveLiveTask = mockk<CheckIfExistingActiveLiveTask>() private val checkIfExistingActiveLiveTask = mockk<CheckIfExistingActiveLiveTask>()
private val redactLiveLocationShareTask = mockk<RedactLiveLocationShareTask>()
private val fakeLiveLocationShareAggregatedSummaryMapper = mockk<LiveLocationShareAggregatedSummaryMapper>() private val fakeLiveLocationShareAggregatedSummaryMapper = mockk<LiveLocationShareAggregatedSummaryMapper>()
private val defaultLocationSharingService = DefaultLocationSharingService( private val defaultLocationSharingService = DefaultLocationSharingService(
@ -72,6 +76,7 @@ internal class DefaultLocationSharingServiceTest {
startLiveLocationShareTask = startLiveLocationShareTask, startLiveLocationShareTask = startLiveLocationShareTask,
stopLiveLocationShareTask = stopLiveLocationShareTask, stopLiveLocationShareTask = stopLiveLocationShareTask,
checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask, checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask,
redactLiveLocationShareTask = redactLiveLocationShareTask,
liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper
) )
@ -209,6 +214,20 @@ internal class DefaultLocationSharingServiceTest {
coVerify { stopLiveLocationShareTask.execute(expectedParams) } 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 @Test
fun `livedata of live summaries is correctly computed`() { fun `livedata of live summaries is correctly computed`() {
val entity = LiveLocationShareAggregatedSummaryEntity() val entity = LiveLocationShareAggregatedSummaryEntity()

View File

@ -0,0 +1,126 @@
/*
* 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.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
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 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 = createSummary(relatedEventIds)
givenSummaryForId(AN_EVENT_ID, aggregatedSummaryEntity)
fakeRealmConfiguration.givenAwaitTransaction<List<String>>(fakeRealm.instance)
val redactEvents = givenCreateRedactEventWithLocalEcho(relatedEventIds + AN_EVENT_ID)
givenPostRedaction(redactEvents)
defaultRedactLiveLocationShareTask.execute(params)
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<String>): LiveLocationShareAggregatedSummaryEntity {
return LiveLocationShareAggregatedSummaryEntity(
eventId = AN_EVENT_ID,
relatedEventIds = RealmList(*relatedEventIds.toTypedArray()),
)
}
private fun givenSummaryForId(eventId: String, aggregatedSummaryEntity: LiveLocationShareAggregatedSummaryEntity) {
fakeRealm.givenWhere<LiveLocationShareAggregatedSummaryEntity>()
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
.givenFindFirst(aggregatedSummaryEntity)
}
private fun givenCreateRedactEventWithLocalEcho(eventIds: List<String>): List<Event> {
return eventIds.map { eventId ->
fakeLocalEchoEventFactory.givenCreateRedactEvent(
eventId = eventId,
withLocalEcho = true
)
}
}
private fun givenPostRedaction(redactEvents: List<Event>) {
redactEvents.forEach {
fakeEventSenderProcessor.givenPostRedaction(event = it, reason = A_REASON)
}
}
private fun verifyCreateRedactEventForEventIds(eventIds: List<String>) {
eventIds.forEach { eventId ->
fakeLocalEchoEventFactory.verifyCreateRedactEvent(
roomId = A_ROOM_ID,
eventId = eventId,
reason = A_REASON
)
}
}
private fun verifyCreateLocalEchoForEvents(events: List<Event>) {
events.forEach { redactionEvent ->
fakeLocalEchoEventFactory.verifyCreateLocalEcho(redactionEvent)
}
}
}

View File

@ -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<EventEntity>()
.givenEqualTo(EventEntityFields.EVENT_ID, A_REDACTED_EVENT_ID)
.givenFindFirst(redactedEventEntity)
val liveSummary = mockk<LiveLocationShareAggregatedSummaryEntity>()
every { liveSummary.eventId } returns A_REDACTED_EVENT_ID
liveSummary.givenDelete()
fakeRealm.givenWhere<LiveLocationShareAggregatedSummaryEntity>()
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, A_REDACTED_EVENT_ID)
.givenFindFirst(liveSummary)
val annotationsSummary = mockk<EventAnnotationsSummaryEntity>()
every { annotationsSummary.eventId } returns A_REDACTED_EVENT_ID
annotationsSummary.givenDelete()
fakeRealm.givenWhere<EventAnnotationsSummaryEntity>()
.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_REDACTED_EVENT_ID)
.givenFindFirst(annotationsSummary)
liveLocationShareRedactionEventProcessor.process(fakeRealm.instance, event = event)
verify {
liveSummary.deleteFromRealm()
annotationsSummary.deleteFromRealm()
}
}
}

View File

@ -27,4 +27,8 @@ internal class FakeEventSenderProcessor : EventSenderProcessor by mockk() {
fun givenPostEventReturns(event: Event, cancelable: Cancelable) { fun givenPostEventReturns(event: Event, cancelable: Cancelable) {
every { postEvent(event) } returns cancelable every { postEvent(event) } returns cancelable
} }
fun givenPostRedaction(event: Event, reason: String?) {
every { postRedaction(event, reason) } returns mockk()
}
} }

View File

@ -46,24 +46,6 @@ internal class FakeLocalEchoEventFactory {
return event 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( fun verifyCreateStaticLocationEvent(
roomId: String, roomId: String,
latitude: Double, 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( fun verifyCreateLiveLocationEvent(
roomId: String, roomId: String,
beaconInfoEventId: 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) { fun verifyCreateLocalEcho(event: Event) {
verify { instance.createLocalEcho(event) } verify { instance.createLocalEcho(event) }
} }

View File

@ -18,10 +18,13 @@ package org.matrix.android.sdk.test.fakes
import io.mockk.MockKVerificationScope import io.mockk.MockKVerificationScope
import io.mockk.every import io.mockk.every
import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify import io.mockk.verify
import io.realm.Realm import io.realm.Realm
import io.realm.RealmModel import io.realm.RealmModel
import io.realm.RealmObject
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.kotlin.where import io.realm.kotlin.where
@ -97,3 +100,10 @@ inline fun <reified T : RealmModel> RealmQuery<T>.givenIsNotNull(
every { isNotNull(fieldName) } returns this every { isNotNull(fieldName) } returns this
return 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
}

View File

@ -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<RealmConfiguration>()
fun <T> givenAwaitTransaction(realm: Realm) {
val transaction = slot<suspend (Realm) -> T>()
coEvery { awaitTransaction(instance, capture(transaction)) } coAnswers {
secondArg<suspend (Realm) -> T>().invoke(realm)
}
}
}

View File

@ -48,6 +48,7 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper import im.vector.app.features.createdirect.DirectRoomHelper
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider 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.sticker.StickerPickerActionHandler
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever 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.read.ReadService
import org.matrix.android.sdk.api.session.room.timeline.Timeline 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.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.sync.SyncRequestState
import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
@ -135,6 +137,7 @@ class TimelineViewModel @AssistedInject constructor(
private val notificationDrawerManager: NotificationDrawerManager, private val notificationDrawerManager: NotificationDrawerManager,
private val locationSharingServiceConnection: LocationSharingServiceConnection, private val locationSharingServiceConnection: LocationSharingServiceConnection,
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase, private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
private val redactLiveLocationShareEventUseCase: RedactLiveLocationShareEventUseCase,
timelineFactory: TimelineFactory, timelineFactory: TimelineFactory,
appStateHandler: AppStateHandler, appStateHandler: AppStateHandler,
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
@ -770,7 +773,13 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleRedactEvent(action: RoomDetailAction.RedactAction) { private fun handleRedactEvent(action: RoomDetailAction.RedactAction) {
val event = room.getTimelineEvent(action.targetEventId) ?: return 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) { private fun handleUndoReact(action: RoomDetailAction.UndoReaction) {

View File

@ -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) }
}
}

View File

@ -0,0 +1,37 @@
/*
* 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
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)
}
}

View File

@ -82,6 +82,7 @@ class MessageActionsViewModel @AssistedInject constructor(
private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val checkIfCanReplyEventUseCase: CheckIfCanReplyEventUseCase, private val checkIfCanReplyEventUseCase: CheckIfCanReplyEventUseCase,
private val checkIfCanRedactEventUseCase: CheckIfCanRedactEventUseCase,
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) { ) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
private val informationData = initialState.informationData private val informationData = initialState.informationData
@ -518,12 +519,7 @@ class MessageActionsViewModel @AssistedInject constructor(
} }
private fun canRedact(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean { 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 return checkIfCanRedactEventUseCase.execute(event, actionPermissions)
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
} }
private fun canRetry(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean { private fun canRetry(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {

View File

@ -113,8 +113,14 @@ class TimelineItemFactory @Inject constructor(
EventType.CALL_NEGOTIATE, EventType.CALL_NEGOTIATE,
EventType.REACTION, EventType.REACTION,
in EventType.POLL_RESPONSE, in EventType.POLL_RESPONSE,
in EventType.POLL_END, in EventType.POLL_END -> noticeItemFactory.create(params)
in EventType.BEACON_LOCATION_DATA -> noticeItemFactory.create(params) in EventType.BEACON_LOCATION_DATA -> {
if (event.root.isRedacted()) {
messageItemFactory.create(params)
} else {
noticeItemFactory.create(params)
}
}
// Calls // Calls
EventType.CALL_INVITE, EventType.CALL_INVITE,
EventType.CALL_HANGUP, EventType.CALL_HANGUP,

View File

@ -51,7 +51,7 @@ object TimelineDisplayableEvents {
EventType.STATE_ROOM_JOIN_RULES, EventType.STATE_ROOM_JOIN_RULES,
EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_DONE,
EventType.KEY_VERIFICATION_CANCEL, 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 { fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {

View File

@ -241,6 +241,10 @@ class TimelineEventVisibilityHelper @Inject constructor(
} else root.eventId != rootThreadEventId } else root.eventId != rootThreadEventId
} }
if (root.getClearType() in EventType.BEACON_LOCATION_DATA) {
return !root.isRedacted()
}
return false return false
} }

View File

@ -26,6 +26,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.services.VectorAndroidService import im.vector.app.core.services.VectorAndroidService
import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.redaction.CheckIfEventIsRedactedUseCase
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -55,6 +56,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
@Inject lateinit var locationTracker: LocationTracker @Inject lateinit var locationTracker: LocationTracker
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase @Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase
@Inject lateinit var checkIfEventIsRedactedUseCase: CheckIfEventIsRedactedUseCase
private val binder = LocalBinder() private val binder = LocalBinder()
@ -203,14 +205,18 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
private fun listenForLiveSummaryChanges(roomId: String, beaconEventId: String) { private fun listenForLiveSummaryChanges(roomId: String, beaconEventId: String) {
launchWithActiveSession { session -> launchWithActiveSession { session ->
val job = getLiveLocationShareSummaryUseCase.execute(roomId, beaconEventId) val job = getLiveLocationShareSummaryUseCase.execute(roomId, beaconEventId)
.distinctUntilChangedBy { it.isActive } .distinctUntilChangedBy { it?.isActive }
.filter { it.isActive == false } .filter { it?.isActive == false || (it == null && isLiveRedacted(roomId, beaconEventId)) }
.onEach { stopSharingLocation(beaconEventId) } .onEach { stopSharingLocation(beaconEventId) }
.launchIn(session.coroutineScope) .launchIn(session.coroutineScope)
jobs.add(job) jobs.add(job)
} }
} }
private suspend fun isLiveRedacted(roomId: String, beaconEventId: String): Boolean {
return checkIfEventIsRedactedUseCase.execute(roomId = roomId, eventId = beaconEventId)
}
private fun launchWithActiveSession(block: suspend CoroutineScope.(Session) -> Unit) = private fun launchWithActiveSession(block: suspend CoroutineScope.(Session) -> Unit) =
activeSessionHolder activeSessionHolder
.getSafeActiveSession() .getSafeActiveSession()

View File

@ -19,7 +19,7 @@ package im.vector.app.features.location.live
import androidx.lifecycle.asFlow import androidx.lifecycle.asFlow
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
@ -31,13 +31,13 @@ class GetLiveLocationShareSummaryUseCase @Inject constructor(
private val session: Session, private val session: Session,
) { ) {
suspend fun execute(roomId: String, eventId: String): Flow<LiveLocationShareAggregatedSummary> = withContext(session.coroutineDispatchers.main) { suspend fun execute(roomId: String, eventId: String): Flow<LiveLocationShareAggregatedSummary?> = withContext(session.coroutineDispatchers.main) {
Timber.d("getting flow for roomId=$roomId and eventId=$eventId") Timber.d("getting flow for roomId=$roomId and eventId=$eventId")
session.getRoom(roomId) session.getRoom(roomId)
?.locationSharingService() ?.locationSharingService()
?.getLiveLocationShareSummary(eventId) ?.getLiveLocationShareSummary(eventId)
?.asFlow() ?.asFlow()
?.mapNotNull { it.getOrNull() } ?.map { it.getOrNull() }
?: emptyFlow() ?: emptyFlow()
} }
} }

View File

@ -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.redaction
import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
import javax.inject.Inject
class CheckIfEventIsRedactedUseCase @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
}
}
}

View File

@ -0,0 +1,51 @@
/*
* 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)
fakeRoom.locationSharingService().verifyRedactLiveLocationShare(inverse = true, beaconInfoEventId = "", reason = A_REASON)
}
}

View File

@ -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)
}
}

View File

@ -53,7 +53,7 @@ class GetLiveLocationShareSummaryUseCaseTest {
} }
@Test @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( val summary = LiveLocationShareAggregatedSummary(
userId = "userId", userId = "userId",
isActive = true, isActive = true,
@ -70,4 +70,17 @@ class GetLiveLocationShareSummaryUseCaseTest {
result shouldBeEqualTo summary 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
}
} }

View File

@ -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.redaction
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 CheckIfEventIsRedactedUseCaseTest {
private val fakeSession = FakeSession()
private val checkIfEventIsRedactedUseCase = CheckIfEventIsRedactedUseCase(
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 = checkIfEventIsRedactedUseCase.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 = checkIfEventIsRedactedUseCase.execute(A_ROOM_ID, AN_EVENT_ID)
result shouldBeEqualTo false
}
}

View File

@ -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
}
}

View File

@ -19,8 +19,11 @@ package im.vector.app.test.fakes
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.just
import io.mockk.mockk 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.LocationSharingService
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
@ -38,7 +41,7 @@ class FakeLocationSharingService : LocationSharingService by mockk() {
fun givenLiveLocationShareSummaryReturns( fun givenLiveLocationShareSummaryReturns(
eventId: String, eventId: String,
summary: LiveLocationShareAggregatedSummary summary: LiveLocationShareAggregatedSummary?
): LiveData<Optional<LiveLocationShareAggregatedSummary>> { ): LiveData<Optional<LiveLocationShareAggregatedSummary>> {
return MutableLiveData(Optional(summary)).also { return MutableLiveData(Optional(summary)).also {
every { getLiveLocationShareSummary(eventId) } returns it every { getLiveLocationShareSummary(eventId) } returns it
@ -48,4 +51,17 @@ class FakeLocationSharingService : LocationSharingService by mockk() {
fun givenStopLiveLocationShareReturns(result: UpdateLiveLocationShareResult) { fun givenStopLiveLocationShareReturns(result: UpdateLiveLocationShareResult) {
coEvery { stopLiveLocationShare() } returns result coEvery { stopLiveLocationShare() } returns result
} }
fun givenRedactLiveLocationShare(beaconInfoEventId: String, reason: String?) {
coEvery { redactLiveLocationShare(beaconInfoEventId, reason) } just runs
}
/**
* @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) }
}
} }

View File

@ -35,6 +35,7 @@ class FakeSession(
val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(), val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(),
val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(),
private val fakeRoomService: FakeRoomService = FakeRoomService(), private val fakeRoomService: FakeRoomService = FakeRoomService(),
private val fakeEventService: FakeEventService = FakeEventService(),
) : Session by mockk(relaxed = true) { ) : Session by mockk(relaxed = true) {
init { init {
@ -50,6 +51,7 @@ class FakeSession(
override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = fakeHomeServerCapabilitiesService override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = fakeHomeServerCapabilitiesService
override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun sharedSecretStorageService() = fakeSharedSecretStorageService
override fun roomService() = fakeRoomService override fun roomService() = fakeRoomService
override fun eventService() = fakeEventService
fun givenVectorStore(vectorSessionStore: VectorSessionStore) { fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
coEvery { coEvery {