diff --git a/changelog.d/8938.misc b/changelog.d/8938.misc new file mode 100644 index 0000000000..b6bead2e09 --- /dev/null +++ b/changelog.d/8938.misc @@ -0,0 +1 @@ +Indicate when calls are unsupported in the timeline/notifications diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 7c0c7bda7a..a5daeddc7a 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2939,6 +2939,9 @@ Slide to end the call + Unsupported call + Unsupported call. The new Element X app is needed to join this call. + Re-Authentication Needed ${app_name} requires you to enter your credentials to perform this action. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 196b419598..9a96b4b7e8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -499,7 +499,11 @@ fun Event.getPollContent(): MessagePollContent? { } fun Event.supportsNotification() = - this.getClearType() in EventType.MESSAGE + EventType.POLL_START.values + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values + this.getClearType() in EventType.MESSAGE + + EventType.POLL_START.values + + EventType.POLL_END.values + + EventType.STATE_ROOM_BEACON_INFO.values + + EventType.ELEMENT_CALL_NOTIFY.values fun Event.isContentReportable() = this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO.values diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 9228f76db2..78bbc7cd6b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -87,6 +87,9 @@ object EventType { // This type is not processed by the client, just sent to the server const val CALL_REPLACES = "m.call.replaces" + // Element Call + val ELEMENT_CALL_NOTIFY = StableUnstableId(stable = "m.call.notify", unstable = "org.matrix.msc4075.call.notify") + // Key share events const val ROOM_KEY_REQUEST = "m.room_key_request" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ElementCallNotifyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ElementCallNotifyContent.kt new file mode 100644 index 0000000000..4f8de7f8f7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ElementCallNotifyContent.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2024 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.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ElementCallNotifyContent( + @Json(name = "application") val application: String? = null, + @Json(name = "call_id") val callId: String? = null, + @Json(name = "m.mentions") val mentions: Mentions? = null, + @Json(name = "notify_type") val notifyType: String? = null, +) + +@JsonClass(generateAdapter = true) +data class Mentions( + @Json(name = "room") val room: Boolean? = null, + @Json(name = "user_ids") val userIds: List? = null, +) + +fun ElementCallNotifyContent.isUserMentioned(userId: String): Boolean { + return mentions?.room == true || + mentions?.userIds?.contains(userId) == true +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt index d000d709a9..434512e2d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt @@ -61,6 +61,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor( in EventType.POLL_START.values, in EventType.POLL_END.values, in EventType.STATE_ROOM_BEACON_INFO.values, + in EventType.ELEMENT_CALL_NOTIFY.values, EventType.MESSAGE, EventType.REDACTION, EventType.ENCRYPTED, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 761f7d03ac..b9d2f1e779 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -214,6 +214,9 @@ class MessageActionsViewModel @AssistedInject constructor( in EventType.POLL_END.values -> { stringProvider.getString(CommonStrings.message_reply_to_ended_poll_preview) } + in EventType.ELEMENT_CALL_NOTIFY.values -> { + stringProvider.getString(CommonStrings.call_unsupported) + } else -> null } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ElementCallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ElementCallItemFactory.kt new file mode 100644 index 0000000000..2c0b49eeab --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ElementCallItemFactory.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ +package im.vector.app.features.home.room.detail.timeline.factory + +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.resources.UserPreferencesProvider +import im.vector.app.features.home.room.detail.timeline.MessageColorProvider +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory +import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.app.features.home.room.detail.timeline.item.ElementCallTileTimelineItem +import im.vector.app.features.home.room.detail.timeline.item.ElementCallTileTimelineItem_ +import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData +import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.message.ElementCallNotifyContent +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class ElementCallItemFactory @Inject constructor( + private val session: Session, + private val userPreferencesProvider: UserPreferencesProvider, + private val messageColorProvider: MessageColorProvider, + private val messageInformationDataFactory: MessageInformationDataFactory, + private val messageItemAttributesFactory: MessageItemAttributesFactory, + private val avatarSizeProvider: AvatarSizeProvider, + private val noticeItemFactory: NoticeItemFactory +) { + + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event + if (event.root.eventId == null) return null + val showHiddenEvents = userPreferencesProvider.shouldShowHiddenEvents() + val roomSummary = params.partialState.roomSummary ?: return null + val informationData = messageInformationDataFactory.create(params) + val callItem = when (event.root.getClearType()) { + in EventType.ELEMENT_CALL_NOTIFY.values -> { + val notifyContent: ElementCallNotifyContent = event.root.content.toModel() ?: return null + createElementCallTileTimelineItem( + roomSummary = roomSummary, + callId = notifyContent.callId.orEmpty(), + callStatus = ElementCallTileTimelineItem.CallStatus.INVITED, + callKind = ElementCallTileTimelineItem.CallKind.VIDEO, + callback = params.callback, + highlight = params.isHighlighted, + informationData = informationData, + reactionsSummaryEvents = params.reactionsSummaryEvents + ) + } + else -> null + } + return if (callItem == null && showHiddenEvents) { + // Fallback to notice item for showing hidden events + noticeItemFactory.create(params) + } else { + callItem + } + } + + private fun createElementCallTileTimelineItem( + roomSummary: RoomSummary, + callId: String, + callKind: ElementCallTileTimelineItem.CallKind, + callStatus: ElementCallTileTimelineItem.CallStatus, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + reactionsSummaryEvents: ReactionsSummaryEvents? + ): ElementCallTileTimelineItem? { + val userOfInterest = roomSummary.toMatrixItem() + val attributes = messageItemAttributesFactory.create(null, informationData, callback, reactionsSummaryEvents).let { + ElementCallTileTimelineItem.Attributes( + callId = callId, + callKind = callKind, + callStatus = callStatus, + informationData = informationData, + avatarRenderer = it.avatarRenderer, + messageColorProvider = messageColorProvider, + itemClickListener = it.itemClickListener, + itemLongClickListener = it.itemLongClickListener, + reactionPillCallback = it.reactionPillCallback, + readReceiptsCallback = it.readReceiptsCallback, + userOfInterest = userOfInterest, + callback = callback, + reactionsSummaryEvents = reactionsSummaryEvents + ) + } + return ElementCallTileTimelineItem_() + .attributes(attributes) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index ce3694c637..51a08b07f6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -32,6 +32,7 @@ class TimelineItemFactory @Inject constructor( private val widgetItemFactory: WidgetItemFactory, private val verificationConclusionItemFactory: VerificationItemFactory, private val callItemFactory: CallItemFactory, + private val elementCallItemFactory: ElementCallItemFactory, private val decryptionFailureTracker: DecryptionFailureTracker, private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper, private val session: Session, @@ -119,6 +120,8 @@ class TimelineItemFactory @Inject constructor( noticeItemFactory.create(params) } } + // Element Call + in EventType.ELEMENT_CALL_NOTIFY.values -> elementCallItemFactory.create(params) // Calls EventType.CALL_INVITE, EventType.CALL_HANGUP, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index d33c198f0d..8a6847f62d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -142,6 +142,9 @@ class DisplayableEventFormatter @Inject constructor( in EventType.STATE_ROOM_BEACON_INFO.values -> { simpleFormat(senderName, stringProvider.getString(CommonStrings.sent_live_location), appendAuthor) } + in EventType.ELEMENT_CALL_NOTIFY.values -> { + simpleFormat(senderName, stringProvider.getString(CommonStrings.call_unsupported), appendAuthor) + } VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> { formatVoiceBroadcastEvent(timelineEvent.root, isDm, senderName) } @@ -243,6 +246,9 @@ class DisplayableEventFormatter @Inject constructor( in EventType.STATE_ROOM_BEACON_INFO.values -> { stringProvider.getString(CommonStrings.sent_live_location) } + in EventType.ELEMENT_CALL_NOTIFY.values -> { + stringProvider.getString(CommonStrings.call_unsupported) + } else -> { span { } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 67f03571c9..8e3a50d882 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -47,6 +47,7 @@ object TimelineDisplayableEvents { ) + EventType.POLL_START.values + EventType.POLL_END.values + + EventType.ELEMENT_CALL_NOTIFY.values + EventType.STATE_ROOM_BEACON_INFO.values + EventType.BEACON_LOCATION_DATA.values } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ElementCallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ElementCallTileTimelineItem.kt new file mode 100644 index 0000000000..33d5e33661 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ElementCallTileTimelineItem.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ +package im.vector.app.features.home.room.detail.timeline.item + +import android.content.res.Resources +import android.view.View +import android.widget.ImageView +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.features.displayname.getBestName +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.MessageColorProvider +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.lib.strings.CommonStrings +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class ElementCallTileTimelineItem : AbsBaseMessageItem(R.layout.item_timeline_event_base_state) { + + override val baseAttributes: AbsBaseMessageItem.Attributes + get() = attributes + + override fun isCacheable() = false + + @EpoxyAttribute + lateinit var attributes: Attributes + + override fun getViewStubId() = STUB_ID + + override fun bind(holder: Holder) { + super.bind(holder) + holder.endGuideline.updateLayoutParams { + this.marginEnd = leftGuideline + } + holder.creatorNameView.text = attributes.userOfInterest.getBestName() + attributes.avatarRenderer.render(attributes.userOfInterest, holder.creatorAvatarView) + renderSendState(holder.view, null, holder.failedToSendIndicator) + } + + class Holder : AbsBaseMessageItem.Holder(STUB_ID) { + val creatorAvatarView by bind(R.id.itemCallCreatorAvatar) + val creatorNameView by bind(R.id.itemCallCreatorNameTextView) + val endGuideline by bind(R.id.messageEndGuideline) + val failedToSendIndicator by bind(R.id.messageFailToSendIndicator) + + val resources: Resources + get() = view.context.resources + } + + companion object { + private val STUB_ID = R.id.messageElementCallStub + } + + data class Attributes( + val callId: String, + val callKind: CallKind, + val callStatus: CallStatus, + val userOfInterest: MatrixItem, + val callback: TimelineEventController.Callback? = null, + override val informationData: MessageInformationData, + override val avatarRenderer: AvatarRenderer, + override val messageColorProvider: MessageColorProvider, + override val itemLongClickListener: View.OnLongClickListener? = null, + override val itemClickListener: ClickListener? = null, + override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, + override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, + override val reactionsSummaryEvents: ReactionsSummaryEvents? = null + ) : AbsBaseMessageItem.Attributes + + enum class CallKind(@DrawableRes val icon: Int, @StringRes val title: Int) { + VIDEO(R.drawable.ic_call_video_small, CommonStrings.action_video_call), + } + + enum class CallStatus { + INVITED, + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 271f6bca77..9979b71a44 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -34,7 +34,9 @@ import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.room.getTimelineEvent 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.ElementCallNotifyContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent +import org.matrix.android.sdk.api.session.room.model.message.isUserMentioned import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId @@ -149,9 +151,11 @@ class NotifiableEventResolver @Inject constructor( ) } else { event.attemptToDecryptIfNeeded(session) - // only convert encrypted messages to NotifiableMessageEvents + // For incoming Element Call, check that the user is mentioned + val isIncomingElementCall = event.root.getClearType() in EventType.ELEMENT_CALL_NOTIFY.values && + event.root.getClearContent()?.toModel()?.isUserMentioned(session.myUserId) == true when { - event.root.supportsNotification() -> { + isIncomingElementCall || event.root.supportsNotification() -> { val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString() val roomName = room.roomSummary()?.displayName ?: "" val senderDisplayName = event.senderInfo.disambiguatedDisplayName diff --git a/vector/src/main/res/layout/item_timeline_event_base_state.xml b/vector/src/main/res/layout/item_timeline_event_base_state.xml index 927abbfd16..f94cb8801b 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_state.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_state.xml @@ -52,21 +52,28 @@ android:id="@+id/messageVerificationRequestStub" style="@style/TimelineContentStubBaseParams" android:layout="@layout/item_timeline_event_verification_stub" - tools:layout_marginTop="250dp" + tools:layout_marginTop="200dp" tools:visibility="visible" /> + + @@ -122,4 +129,4 @@ - \ No newline at end of file + diff --git a/vector/src/main/res/layout/item_timeline_event_element_call_tile_stub.xml b/vector/src/main/res/layout/item_timeline_event_element_call_tile_stub.xml new file mode 100644 index 0000000000..5f0a924ff9 --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_element_call_tile_stub.xml @@ -0,0 +1,48 @@ + + + + + + + + + +