Merge pull request #7611 from vector-im/feature/fre/voice_broadcast_deletion

Be less restritive when pruning redacted events
This commit is contained in:
Florian Renaud 2022-11-28 13:57:40 +01:00 committed by GitHub
commit 49199bd5e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 175 additions and 138 deletions

View file

@ -66,7 +66,7 @@ class PollAggregationTest : InstrumentedTest {
val aliceEventsListener = object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
snapshot.firstOrNull { it.root.getClearType() in EventType.POLL_START }?.let { pollEvent ->
snapshot.firstOrNull { it.root.getClearType() in EventType.POLL_START.values }?.let { pollEvent ->
val pollEventId = pollEvent.eventId
val pollContent = pollEvent.root.content?.toModel<MessagePollContent>()
val pollSummary = pollEvent.annotations?.pollResponseSummary

View file

@ -26,10 +26,8 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
@ -390,24 +388,18 @@ fun Event.isLocationMessage(): Boolean {
}
}
fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClearType() in EventType.POLL_END
fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START.values || getClearType() in EventType.POLL_END.values
fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO
fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO.values
fun Event.getRelationContent(): RelationDefaultContent? {
return if (isEncrypted()) {
content.toModel<EncryptedEventContent>()?.relatesTo
} else {
content.toModel<MessageContent>()?.relatesTo ?: run {
// Special cases when there is only a local msgtype for some event types
when (getClearType()) {
EventType.STICKER -> getClearContent().toModel<MessageStickerContent>()?.relatesTo
in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel<MessageBeaconLocationDataContent>()?.relatesTo
else -> getClearContent()?.get("m.relates_to")?.toContent().toModel()
}
}
content.toModel<MessageContent>()?.relatesTo
?: getClearContent()?.get("m.relates_to")?.toContent().toModel() // Special cases when there is only a local msgtype for some event types
}
}
@ -451,7 +443,7 @@ fun Event.getPollContent(): MessagePollContent? {
}
fun Event.supportsNotification() =
this.getClearType() in EventType.MESSAGE + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
this.getClearType() in EventType.MESSAGE + EventType.POLL_START.values + EventType.STATE_ROOM_BEACON_INFO.values
fun Event.isContentReportable() =
this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO
this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO.values

View file

@ -49,11 +49,10 @@ object EventType {
const val STATE_ROOM_JOIN_RULES = "m.room.join_rules"
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
val STATE_ROOM_BEACON_INFO = listOf("org.matrix.msc3672.beacon_info", "m.beacon_info")
val BEACON_LOCATION_DATA = listOf("org.matrix.msc3672.beacon", "m.beacon")
val STATE_ROOM_BEACON_INFO = StableUnstableId(stable = "m.beacon_info", unstable = "org.matrix.msc3672.beacon_info")
val BEACON_LOCATION_DATA = StableUnstableId(stable = "m.beacon", unstable = "org.matrix.msc3672.beacon")
const val STATE_SPACE_CHILD = "m.space.child"
const val STATE_SPACE_PARENT = "m.space.parent"
/**
@ -81,8 +80,7 @@ object EventType {
const val CALL_NEGOTIATE = "m.call.negotiate"
const val CALL_REJECT = "m.call.reject"
const val CALL_HANGUP = "m.call.hangup"
const val CALL_ASSERTED_IDENTITY = "m.call.asserted_identity"
const val CALL_ASSERTED_IDENTITY_PREFIX = "org.matrix.call.asserted_identity"
val CALL_ASSERTED_IDENTITY = StableUnstableId(stable = "m.call.asserted_identity", unstable = "org.matrix.call.asserted_identity")
// This type is not processed by the client, just sent to the server
const val CALL_REPLACES = "m.call.replaces"
@ -90,10 +88,7 @@ object EventType {
// Key share events
const val ROOM_KEY_REQUEST = "m.room_key_request"
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
val ROOM_KEY_WITHHELD = StableUnstableId(
stable = "m.room_key.withheld",
unstable = "org.matrix.room_key.withheld"
)
val ROOM_KEY_WITHHELD = StableUnstableId(stable = "m.room_key.withheld", unstable = "org.matrix.room_key.withheld")
const val REQUEST_SECRET = "m.secret.request"
const val SEND_SECRET = "m.secret.send"
@ -111,9 +106,9 @@ object EventType {
const val REACTION = "m.reaction"
// Poll
val POLL_START = listOf("org.matrix.msc3381.poll.start", "m.poll.start")
val POLL_RESPONSE = listOf("org.matrix.msc3381.poll.response", "m.poll.response")
val POLL_END = listOf("org.matrix.msc3381.poll.end", "m.poll.end")
val POLL_START = StableUnstableId(stable = "m.poll.start", unstable = "org.matrix.msc3381.poll.start")
val POLL_RESPONSE = StableUnstableId(stable = "m.poll.response", unstable = "org.matrix.msc3381.poll.response")
val POLL_END = StableUnstableId(stable = "m.poll.end", unstable = "org.matrix.msc3381.poll.end")
// Unwedging
internal const val DUMMY = "m.dummy"

View file

@ -34,7 +34,7 @@ data class MessageStickerContent(
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
* or some kind of content description for accessibility e.g. 'image attachment'.
*/
@Json(name = "body") override val body: String,
@Json(name = "body") override val body: String = "",
/**
* Metadata about the image referred to in url.

View file

@ -33,5 +33,7 @@ object RoomSummaryConstants {
EventType.ENCRYPTED,
EventType.STICKER,
EventType.REACTION
) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
) +
EventType.POLL_START.values +
EventType.STATE_ROOM_BEACON_INFO.values
}

View file

@ -147,9 +147,9 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
// Polls/Beacon are not message contents like others as there is no msgtype subtype to discriminate moshi parsing
// so toModel<MessageContent> won't parse them correctly
// It's discriminated on event type instead. Maybe it shouldn't be MessageContent at all to avoid confusion?
in EventType.POLL_START -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessagePollContent>()
in EventType.STATE_ROOM_BEACON_INFO -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageBeaconInfoContent>()
in EventType.BEACON_LOCATION_DATA -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageBeaconLocationDataContent>()
in EventType.POLL_START.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessagePollContent>()
in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageBeaconInfoContent>()
in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageBeaconLocationDataContent>()
else -> (getLastEditNewContent() ?: root.getClearContent()).toModel()
}
}

View file

@ -41,9 +41,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.ENCRYPTED,
EventType.CALL_ASSERTED_IDENTITY,
EventType.CALL_ASSERTED_IDENTITY_PREFIX
)
) +
EventType.CALL_ASSERTED_IDENTITY.values
private val eventsToPostProcess = mutableListOf<Event>()

View file

@ -84,8 +84,7 @@ internal class CallSignalingHandler @Inject constructor(
EventType.CALL_NEGOTIATE -> {
handleCallNegotiateEvent(event)
}
EventType.CALL_ASSERTED_IDENTITY,
EventType.CALL_ASSERTED_IDENTITY_PREFIX -> {
in EventType.CALL_ASSERTED_IDENTITY.values -> {
handleCallAssertedIdentityEvent(event)
}
}

View file

@ -56,8 +56,8 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
when (event.type) {
in EventType.POLL_START,
in EventType.STATE_ROOM_BEACON_INFO,
in EventType.POLL_START.values,
in EventType.STATE_ROOM_BEACON_INFO.values,
EventType.MESSAGE,
EventType.REDACTION,
EventType.ENCRYPTED,

View file

@ -91,7 +91,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY,
EventType.ENCRYPTED
) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA
) +
EventType.POLL_START.values +
EventType.POLL_RESPONSE.values +
EventType.POLL_END.values +
EventType.STATE_ROOM_BEACON_INFO.values +
EventType.BEACON_LOCATION_DATA.values
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
return allowedTypes.contains(eventType)
@ -208,7 +213,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
}
}
in EventType.POLL_START -> {
in EventType.POLL_START.values -> {
val content: MessagePollContent? = event.content.toModel()
if (content?.relatesTo?.type == RelationType.REPLACE) {
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
@ -216,26 +221,26 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
handleReplace(realm, event, roomId, isLocalEcho, content.relatesTo.eventId)
}
}
in EventType.POLL_RESPONSE -> {
in EventType.POLL_RESPONSE.values -> {
event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
pollAggregationProcessor.handlePollResponseEvent(session, realm, event)
}
}
}
in EventType.POLL_END -> {
in EventType.POLL_END.values -> {
sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
getPowerLevelsHelper(event.roomId)?.let {
pollAggregationProcessor.handlePollEndEvent(session, it, realm, event)
}
}
}
in EventType.STATE_ROOM_BEACON_INFO -> {
in EventType.STATE_ROOM_BEACON_INFO.values -> {
event.content.toModel<MessageBeaconInfoContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho)
}
}
in EventType.BEACON_LOCATION_DATA -> {
in EventType.BEACON_LOCATION_DATA.values -> {
handleBeaconLocationData(event, realm, roomId, isLocalEcho)
}
else -> Timber.v("UnHandled event ${event.eventId}")
@ -324,7 +329,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
}
if (event.getClearType() in EventType.POLL_START) {
if (event.getClearType() in EventType.POLL_START.values) {
pollAggregationProcessor.handlePollStartEvent(realm, event)
}

View file

@ -39,7 +39,7 @@ internal class DefaultGetActiveBeaconInfoForUserTask @Inject constructor(
) : GetActiveBeaconInfoForUserTask {
override suspend fun execute(params: GetActiveBeaconInfoForUserTask.Params): Event? {
return EventType.STATE_ROOM_BEACON_INFO
return EventType.STATE_ROOM_BEACON_INFO.values
.mapNotNull {
stateEventDataSource.getStateEvent(
roomId = params.roomId,

View file

@ -48,7 +48,7 @@ internal class LiveLocationShareRedactionEventProcessor @Inject constructor() :
val redactedEvent = EventEntity.where(realm, eventId = event.redacts).findFirst()
?: return
if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO) {
if (redactedEvent.type in EventType.STATE_ROOM_BEACON_INFO.values) {
val liveSummary = LiveLocationShareAggregatedSummaryEntity.get(realm, eventId = redactedEvent.eventId)
if (liveSummary != null) {

View file

@ -46,7 +46,7 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor(
isLive = true,
unstableTimestampMillis = clock.epochMillis()
).toContent()
val eventType = EventType.STATE_ROOM_BEACON_INFO.first()
val eventType = EventType.STATE_ROOM_BEACON_INFO.stable
val sendStateTaskParams = SendStateTask.Params(
roomId = params.roomId,
stateKey = userId,

View file

@ -45,7 +45,7 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor(
val sendStateTaskParams = SendStateTask.Params(
roomId = params.roomId,
stateKey = stateKey,
eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
eventType = EventType.STATE_ROOM_BEACON_INFO.stable,
body = updatedContent
)
return try {

View file

@ -61,45 +61,36 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
val isLocalEcho = LocalEcho.isLocalEchoId(redactionEvent.eventId ?: "")
Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho")
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
?: return
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() ?: return
val typeToPrune = eventToPrune.type
val stateKey = eventToPrune.stateKey
val allowedKeys = computeAllowedKeys(typeToPrune)
if (allowedKeys.isNotEmpty()) {
val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) }
eventToPrune.content = ContentMapper.map(prunedContent)
} else {
when (typeToPrune) {
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
?: UnsignedData(null, null)
when {
allowedKeys.isNotEmpty() -> {
val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) }
eventToPrune.content = ContentMapper.map(prunedContent)
}
canPruneEventType(typeToPrune) -> {
Timber.d("REDACTION for message ${eventToPrune.eventId}")
val unsignedData = EventMapper.map(eventToPrune).unsignedData ?: UnsignedData(null, null)
// was this event a m.replace
// was this event a m.replace
// val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
// if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) {
// eventRelationsAggregationUpdater.handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm)
// }
val modified = unsignedData.copy(redactedEvent = redactionEvent)
// Deleting the content of a thread message will result to delete the thread relation, however threads are now dynamic
// so there is not much of a problem
eventToPrune.content = ContentMapper.map(emptyMap())
eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified)
eventToPrune.decryptionResultJson = null
eventToPrune.decryptionErrorCode = null
val modified = unsignedData.copy(redactedEvent = redactionEvent)
eventToPrune.content = ContentMapper.map(emptyMap())
eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified)
eventToPrune.decryptionResultJson = null
eventToPrune.decryptionErrorCode = null
handleTimelineThreadSummaryIfNeeded(realm, eventToPrune, isLocalEcho)
}
// EventType.REACTION -> {
// eventRelationsAggregationUpdater.handleReactionRedact(eventToPrune, realm, userId)
// }
handleTimelineThreadSummaryIfNeeded(realm, eventToPrune, isLocalEcho)
}
else -> {
Timber.w("Not pruning event (type $typeToPrune)")
}
}
if (typeToPrune == EventType.STATE_ROOM_MEMBER && stateKey != null) {
@ -167,4 +158,28 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
else -> emptyList()
}
}
private fun canPruneEventType(eventType: String): Boolean {
return when {
EventType.isCallEvent(eventType) -> false
EventType.isVerificationEvent(eventType) -> false
eventType == EventType.STATE_ROOM_WIDGET_LEGACY ||
eventType == EventType.STATE_ROOM_WIDGET ||
eventType == EventType.STATE_ROOM_NAME ||
eventType == EventType.STATE_ROOM_TOPIC ||
eventType == EventType.STATE_ROOM_AVATAR ||
eventType == EventType.STATE_ROOM_THIRD_PARTY_INVITE ||
eventType == EventType.STATE_ROOM_GUEST_ACCESS ||
eventType == EventType.STATE_SPACE_CHILD ||
eventType == EventType.STATE_SPACE_PARENT ||
eventType == EventType.STATE_ROOM_TOMBSTONE ||
eventType == EventType.STATE_ROOM_HISTORY_VISIBILITY ||
eventType == EventType.STATE_ROOM_RELATED_GROUPS ||
eventType == EventType.STATE_ROOM_PINNED_EVENT ||
eventType == EventType.STATE_ROOM_ENCRYPTION ||
eventType == EventType.STATE_ROOM_SERVER_ACL ||
eventType == EventType.REACTION -> false
else -> true
}
}
}

View file

@ -181,7 +181,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.POLL_START.first(),
type = EventType.POLL_START.stable,
content = newContent.toContent().plus(additionalContent.orEmpty())
)
}
@ -206,7 +206,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.POLL_RESPONSE.first(),
type = EventType.POLL_RESPONSE.stable,
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
@ -226,7 +226,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.POLL_START.first(),
type = EventType.POLL_START.stable,
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
@ -249,7 +249,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.POLL_END.first(),
type = EventType.POLL_END.stable,
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
@ -300,7 +300,7 @@ internal class LocalEchoEventFactory @Inject constructor(
originServerTs = dummyOriginServerTs(),
senderId = userId,
eventId = localId,
type = EventType.BEACON_LOCATION_DATA.first(),
type = EventType.BEACON_LOCATION_DATA.stable,
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)

View file

@ -87,7 +87,7 @@ object PollEventsTestData {
)
internal val A_POLL_START_EVENT = Event(
type = EventType.POLL_START.first(),
type = EventType.POLL_START.stable,
eventId = AN_EVENT_ID,
originServerTs = 1652435922563,
senderId = A_USER_ID_1,
@ -96,7 +96,7 @@ object PollEventsTestData {
)
internal val A_POLL_RESPONSE_EVENT = Event(
type = EventType.POLL_RESPONSE.first(),
type = EventType.POLL_RESPONSE.stable,
eventId = AN_EVENT_ID,
originServerTs = 1652435922563,
senderId = A_USER_ID_1,
@ -105,7 +105,7 @@ object PollEventsTestData {
)
internal val A_POLL_END_EVENT = Event(
type = EventType.POLL_END.first(),
type = EventType.POLL_END.stable,
eventId = AN_EVENT_ID,
originServerTs = 1652435922563,
senderId = A_USER_ID_1,

View file

@ -69,7 +69,7 @@ class DefaultGetActiveBeaconInfoForUserTaskTest {
result shouldBeEqualTo currentStateEvent
fakeStateEventDataSource.verifyGetStateEvent(
roomId = params.roomId,
eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
eventType = EventType.STATE_ROOM_BEACON_INFO.stable,
stateKey = QueryStringValue.Equals(A_USER_ID)
)
}

View file

@ -75,7 +75,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
val expectedParams = SendStateTask.Params(
roomId = params.roomId,
stateKey = A_USER_ID,
eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
eventType = EventType.STATE_ROOM_BEACON_INFO.stable,
body = expectedBeaconContent
)
fakeSendStateTask.verifyExecuteRetry(

View file

@ -79,7 +79,7 @@ class DefaultStopLiveLocationShareTaskTest {
val expectedSendParams = SendStateTask.Params(
roomId = params.roomId,
stateKey = A_USER_ID,
eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
eventType = EventType.STATE_ROOM_BEACON_INFO.stable,
body = expectedBeaconContent
)
fakeSendStateTask.verifyExecuteRetry(

View file

@ -79,7 +79,7 @@ class LiveLocationShareRedactionEventProcessorTest {
@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())
val redactedEventEntity = EventEntity(eventId = A_REDACTED_EVENT_ID, type = EventType.STATE_ROOM_BEACON_INFO.stable)
fakeRealm.givenWhere<EventEntity>()
.givenEqualTo(EventEntityFields.EVENT_ID, A_REDACTED_EVENT_ID)
.givenFindFirst(redactedEventEntity)

View file

@ -19,7 +19,6 @@ package im.vector.app.core.extensions
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.send.SendState
@ -28,7 +27,7 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
fun TimelineEvent.canReact(): Boolean {
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START &&
return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values &&
root.sendState == SendState.SYNCED &&
!root.isRedacted()
}
@ -41,7 +40,7 @@ fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
// Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method
return when (root.getClearType()) {
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
(annotations?.editSummary?.latestEdit?.getClearContent()?.toModel<MessageContent>().toContent().toModel<MessageVoiceBroadcastInfoContent>()
(annotations?.editSummary?.latestEdit?.getClearContent()?.toModel<MessageVoiceBroadcastInfoContent>()
?: root.getClearContent().toModel<MessageVoiceBroadcastInfoContent>())
}
else -> getLastMessageContent()

View file

@ -1774,7 +1774,7 @@ class TimelineFragment :
timelineViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
}
is EventSharedAction.Edit -> {
if (action.eventType in EventType.POLL_START) {
if (action.eventType in EventType.POLL_START.values) {
navigator.openCreatePoll(requireContext(), timelineArgs.roomId, action.eventId, PollMode.EDIT)
} else if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId))

View file

@ -33,8 +33,8 @@ class CheckIfCanRedactEventUseCase @Inject constructor(
EventType.STICKER,
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
) +
EventType.POLL_START +
EventType.STATE_ROOM_BEACON_INFO
EventType.POLL_START.values +
EventType.STATE_ROOM_BEACON_INFO.values
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

View file

@ -26,7 +26,7 @@ class CheckIfCanReplyEventUseCase @Inject constructor() {
fun execute(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
// Only EventType.MESSAGE, EventType.POLL_START and EventType.STATE_ROOM_BEACON_INFO event types are supported for the moment
if (event.root.getClearType() !in EventType.STATE_ROOM_BEACON_INFO + EventType.POLL_START + EventType.MESSAGE) return false
if (event.root.getClearType() !in EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.MESSAGE) return false
if (!actionPermissions.canSendMessage) return false
return when (messageContent?.msgType) {
MessageType.MSGTYPE_TEXT,

View file

@ -215,7 +215,7 @@ class MessageActionsViewModel @AssistedInject constructor(
EventType.CALL_ANSWER -> {
noticeEventFormatter.format(timelineEvent, room?.roomSummary()?.isDirect.orFalse())
}
in EventType.POLL_START -> {
in EventType.POLL_START.values -> {
timelineEvent.root.getClearContent().toModel<MessagePollContent>(catchError = true)
?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: ""
}
@ -383,7 +383,7 @@ class MessageActionsViewModel @AssistedInject constructor(
}
if (canRedact(timelineEvent, actionPermissions)) {
if (timelineEvent.root.getClearType() in EventType.POLL_START) {
if (timelineEvent.root.getClearType() in EventType.POLL_START.values) {
add(
EventSharedAction.Redact(
eventId,
@ -530,13 +530,13 @@ class MessageActionsViewModel @AssistedInject constructor(
private fun canViewReactions(event: TimelineEvent): 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
if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values) return false
return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
}
private fun canEdit(event: TimelineEvent, myUserId: String, actionPermissions: ActionPermissions): Boolean {
// Only event of type EventType.MESSAGE and EventType.POLL_START are supported for the moment
if (event.root.getClearType() !in listOf(EventType.MESSAGE) + EventType.POLL_START) return false
if (event.root.getClearType() !in listOf(EventType.MESSAGE) + EventType.POLL_START.values) return false
if (!actionPermissions.canSendMessage) return false
// TODO if user is admin or moderator
val messageContent = event.root.getClearContent().toModel<MessageContent>()
@ -582,14 +582,14 @@ class MessageActionsViewModel @AssistedInject constructor(
}
private fun canEndPoll(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
return event.root.getClearType() in EventType.POLL_START &&
return event.root.getClearType() in EventType.POLL_START.values &&
canRedact(event, actionPermissions) &&
event.annotations?.pollResponseSummary?.closedTime == null
}
private fun canEditPoll(event: TimelineEvent): Boolean {
return event.root.getClearType() in EventType.POLL_START &&
return event.root.getClearType() in EventType.POLL_START.values &&
event.annotations?.pollResponseSummary?.closedTime == null &&
event.annotations?.pollResponseSummary?.aggregatedContent?.totalVotes ?: 0 == 0
(event.annotations?.pollResponseSummary?.aggregatedContent?.totalVotes ?: 0) == 0
}
}

View file

@ -45,6 +45,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio
import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem
import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem
import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
@ -324,9 +325,11 @@ class MessageItemFactory @Inject constructor(
informationData: MessageInformationData,
highlight: Boolean,
attributes: AbsMessageItem.Attributes
): MessageVoiceItem? {
): BaseEventItem<*>? {
// Do not display voice broadcast messages
if (params.event.root.asMessageAudioEvent().isVoiceBroadcast()) return null
if (params.event.root.asMessageAudioEvent().isVoiceBroadcast()) {
return noticeItemFactory.create(params)
}
val fileUrl = getAudioFileUrl(messageContent, informationData)
val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)

View file

@ -88,7 +88,7 @@ class TimelineItemFactory @Inject constructor(
EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(params)
// State room create
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params)
in EventType.STATE_ROOM_BEACON_INFO.values -> messageItemFactory.create(params)
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params)
// Unhandled state event types
else -> {
@ -101,7 +101,7 @@ class TimelineItemFactory @Inject constructor(
when (event.root.getClearType()) {
// Message itemsX
EventType.STICKER,
in EventType.POLL_START,
in EventType.POLL_START.values,
EventType.MESSAGE -> messageItemFactory.create(params)
EventType.REDACTION,
EventType.KEY_VERIFICATION_ACCEPT,
@ -114,9 +114,9 @@ class TimelineItemFactory @Inject constructor(
EventType.CALL_SELECT_ANSWER,
EventType.CALL_NEGOTIATE,
EventType.REACTION,
in EventType.POLL_RESPONSE,
in EventType.POLL_END -> noticeItemFactory.create(params)
in EventType.BEACON_LOCATION_DATA -> {
in EventType.POLL_RESPONSE.values,
in EventType.POLL_END.values -> noticeItemFactory.create(params)
in EventType.BEACON_LOCATION_DATA.values -> {
if (event.root.isRedacted()) {
messageItemFactory.create(params)
} else {

View file

@ -23,6 +23,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvide
import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageVoiceBroadcastItem
import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastListeningItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastRecordingItem
@ -47,6 +48,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
private val voiceBroadcastPlayer: VoiceBroadcastPlayer,
private val playbackTracker: AudioMessagePlaybackTracker,
private val noticeItemFactory: NoticeItemFactory,
) {
fun create(
@ -54,9 +56,11 @@ class VoiceBroadcastItemFactory @Inject constructor(
messageContent: MessageVoiceBroadcastInfoContent,
highlight: Boolean,
attributes: AbsMessageItem.Attributes,
): AbsMessageVoiceBroadcastItem<*>? {
): BaseEventItem<*>? {
// Only display item of the initial event with updated data
if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null
if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) {
return noticeItemFactory.create(params)
}
val voiceBroadcastEventsGroup = params.eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null
val voiceBroadcastEvent = voiceBroadcastEventsGroup.getLastDisplayableEvent().root.asVoiceBroadcastEvent() ?: return null

View file

@ -122,17 +122,17 @@ class DisplayableEventFormatter @Inject constructor(
EventType.CALL_CANDIDATES -> {
span { }
}
in EventType.POLL_START -> {
in EventType.POLL_START.values -> {
timelineEvent.root.getClearContent().toModel<MessagePollContent>(catchError = true)?.getBestPollCreationInfo()?.question?.getBestQuestion()
?: stringProvider.getString(R.string.sent_a_poll)
}
in EventType.POLL_RESPONSE -> {
in EventType.POLL_RESPONSE.values -> {
stringProvider.getString(R.string.poll_response_room_list_preview)
}
in EventType.POLL_END -> {
in EventType.POLL_END.values -> {
stringProvider.getString(R.string.poll_end_room_list_preview)
}
in EventType.STATE_ROOM_BEACON_INFO -> {
in EventType.STATE_ROOM_BEACON_INFO.values -> {
simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor)
}
else -> {
@ -220,17 +220,17 @@ class DisplayableEventFormatter @Inject constructor(
emojiSpanify.spanify(stringProvider.getString(R.string.sent_a_reaction, it.key))
} ?: span { }
}
in EventType.POLL_START -> {
in EventType.POLL_START.values -> {
event.getClearContent().toModel<MessagePollContent>(catchError = true)?.pollCreationInfo?.question?.question
?: stringProvider.getString(R.string.sent_a_poll)
}
in EventType.POLL_RESPONSE -> {
in EventType.POLL_RESPONSE.values -> {
stringProvider.getString(R.string.poll_response_room_list_preview)
}
in EventType.POLL_END -> {
in EventType.POLL_END.values -> {
stringProvider.getString(R.string.poll_end_room_list_preview)
}
in EventType.STATE_ROOM_BEACON_INFO -> {
in EventType.STATE_ROOM_BEACON_INFO.values -> {
stringProvider.getString(R.string.sent_live_location)
}
else -> {

View file

@ -21,6 +21,7 @@ import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.roomprofile.permissions.RoleFormatter
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.extensions.appendNl
import org.matrix.android.sdk.api.extensions.orFalse
@ -106,9 +107,10 @@ class NoticeEventFormatter @Inject constructor(
EventType.STATE_SPACE_PARENT,
EventType.REDACTION,
EventType.STICKER,
in EventType.POLL_RESPONSE,
in EventType.POLL_END,
in EventType.BEACON_LOCATION_DATA -> formatDebug(timelineEvent.root)
in EventType.POLL_RESPONSE.values,
in EventType.POLL_END.values,
in EventType.BEACON_LOCATION_DATA.values,
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatDebug(timelineEvent.root)
else -> {
Timber.v("Type $type not handled by this formatter")
null

View file

@ -54,9 +54,9 @@ object TimelineDisplayableEvents {
EventType.KEY_VERIFICATION_CANCEL,
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
) +
EventType.POLL_START +
EventType.STATE_ROOM_BEACON_INFO +
EventType.BEACON_LOCATION_DATA
EventType.POLL_START.values +
EventType.STATE_ROOM_BEACON_INFO.values +
EventType.BEACON_LOCATION_DATA.values
}
fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {

View file

@ -18,6 +18,11 @@ package im.vector.app.features.home.room.detail.timeline.helper
import im.vector.app.core.extensions.localDateTime
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import org.matrix.android.sdk.api.extensions.orFalse
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.RelationType
@ -28,6 +33,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import javax.inject.Inject
@ -242,10 +248,19 @@ class TimelineEventVisibilityHelper @Inject constructor(
} else root.eventId != rootThreadEventId
}
if (root.getClearType() in EventType.BEACON_LOCATION_DATA) {
if (root.getClearType() in EventType.BEACON_LOCATION_DATA.values) {
return !root.isRedacted()
}
if (root.getClearType() == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO &&
root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState != VoiceBroadcastState.STARTED) {
return true
}
if (root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse()) {
return true
}
return false
}

View file

@ -47,8 +47,10 @@ class TimelineMessageLayoutFactory @Inject constructor(
private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
EventType.MESSAGE,
EventType.ENCRYPTED,
EventType.STICKER
) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
EventType.STICKER,
) +
EventType.POLL_START.values +
EventType.STATE_ROOM_BEACON_INFO.values
// Can't be rendered in bubbles, so get back to default layout
private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(

View file

@ -83,7 +83,7 @@ class LocationSharingViewModel @AssistedInject constructor(
.distinctUntilChanged()
.setOnEach {
val powerLevelsHelper = PowerLevelsHelper(it)
val canShareLiveLocation = EventType.STATE_ROOM_BEACON_INFO
val canShareLiveLocation = EventType.STATE_ROOM_BEACON_INFO.values
.all { beaconInfoType ->
powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, beaconInfoType)
}

View file

@ -66,7 +66,7 @@ class NotifiableEventResolver @Inject constructor(
) {
private val nonEncryptedNotifiableEventTypes: List<String> =
listOf(EventType.MESSAGE) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
listOf(EventType.MESSAGE) + EventType.POLL_START.values + EventType.STATE_ROOM_BEACON_INFO.values
suspend fun resolveEvent(event: Event, session: Session, isNoisy: Boolean): NotifiableEvent? {
val roomID = event.roomId ?: return null

View file

@ -35,8 +35,13 @@ class CheckIfCanRedactEventUseCaseTest {
@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, VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) +
EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
val canRedactEventTypes = listOf(
EventType.MESSAGE,
EventType.STICKER,
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
) +
EventType.POLL_START.values +
EventType.STATE_ROOM_BEACON_INFO.values
canRedactEventTypes.forEach { eventType ->
val event = givenAnEvent(

View file

@ -43,7 +43,7 @@ class CheckIfCanReplyEventUseCaseTest {
@Test
fun `given reply is allowed for the event type when use case is executed then result is true`() {
val eventTypes = EventType.STATE_ROOM_BEACON_INFO + EventType.POLL_START + EventType.MESSAGE
val eventTypes = EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.MESSAGE
eventTypes.forEach { eventType ->
val event = givenAnEvent(eventType)

View file

@ -63,7 +63,7 @@ object FakeCreatePollViewStates {
)
private val A_POLL_START_EVENT = Event(
type = EventType.POLL_START.first(),
type = EventType.POLL_START.stable,
eventId = A_FAKE_EVENT_ID,
originServerTs = 1652435922563,
senderId = A_FAKE_USER_ID,
@ -80,8 +80,8 @@ object FakeCreatePollViewStates {
)
val initialCreatePollViewState = CreatePollViewState(createPollArgs).copy(
canCreatePoll = false,
canAddMoreOptions = true
canCreatePoll = false,
canAddMoreOptions = true
)
val pollViewStateWithOnlyQuestion = initialCreatePollViewState.copy(