diff --git a/library/external/textdrawable/build.gradle b/library/external/textdrawable/build.gradle
index 5eb27bf6aa3..dcaf2d76ccd 100644
--- a/library/external/textdrawable/build.gradle
+++ b/library/external/textdrawable/build.gradle
@@ -1,7 +1,5 @@
apply plugin: 'com.android.library'
-apply plugin: 'com.android.library'
-
android {
namespace "com.amulyakhare.textdrawable"
diff --git a/library/ui-strings/src/main/res/values-ru/strings_sc.xml b/library/ui-strings/src/main/res/values-ru/strings_sc.xml
index d14411c18f5..4e510848b74 100644
--- a/library/ui-strings/src/main/res/values-ru/strings_sc.xml
+++ b/library/ui-strings/src/main/res/values-ru/strings_sc.xml
@@ -180,4 +180,4 @@
Очистить выделение при прокрутке
Разрешить резервный сервер звонков
⚠️ Эта настройка по умолчанию (если не изменена конфигурацией Вашего домашнего сервера) включает доступ к \"Scalar\", менеджеру интеграций от Element. К сожалению, он является проприетарным, т.е. его исходый код не открытый и не может быть проверен пользователями или разработчиками SchildiChat.
-
\ No newline at end of file
+
diff --git a/library/ui-strings/src/main/res/values/strings_sc.xml b/library/ui-strings/src/main/res/values/strings_sc.xml
index 148d267d8cf..9adfff72804 100644
--- a/library/ui-strings/src/main/res/values/strings_sc.xml
+++ b/library/ui-strings/src/main/res/values/strings_sc.xml
@@ -239,6 +239,8 @@
Will use %s as assist when your homeserver does not offer one (your IP address will be seen by the stun server during a call
)
+ Ring for group calls
+ Open App
⚠️ This setting by default (unless overridden by your homeserver\'s configuration) enables access to \"scalar\", Element\'s integration manager which is unfortunately proprietary, i.e. its source code is not open and can not be checked by the public or the SchildiChat developers.
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 196b419598f..053af048151 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
@@ -23,7 +23,9 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
+import org.matrix.android.sdk.api.session.events.model.EventType.IS_JITSI_CALL
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
+import org.matrix.android.sdk.api.session.room.model.JitsiEventContent
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.MessageContent
@@ -498,6 +500,9 @@ fun Event.getPollContent(): MessagePollContent? {
return getClearContent().toModel()
}
+fun Event.isJitsiEvent() = content?.toModel()?.type?.lowercase() == IS_JITSI_CALL &&
+ content.toModel()?.name?.lowercase() == IS_JITSI_CALL
+
fun Event.supportsNotification() =
this.getClearType() in EventType.MESSAGE + EventType.POLL_START.values + EventType.POLL_END.values + 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 170254078f7..94e336679ac 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
@@ -108,6 +108,9 @@ object EventType {
// Relation Events
const val REACTION = "m.reaction"
+ // Jitsi call
+ const val IS_JITSI_CALL = "jitsi"
+
// Poll
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")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/JitsiEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/JitsiEventContent.kt
new file mode 100644
index 00000000000..956c343ac96
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/JitsiEventContent.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 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
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Class representing the Jitsi call state event.
+ */
+@JsonClass(generateAdapter = true)
+data class JitsiEventContent(
+ @Json(name = "type") val type: String? = null,
+ @Json(name = "name") val name: String? = null,
+)
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 d000d709a92..f9f701548f5 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
@@ -64,6 +64,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
EventType.MESSAGE,
EventType.REDACTION,
EventType.ENCRYPTED,
+ EventType.STATE_ROOM_WIDGET_LEGACY,
EventType.STATE_ROOM_MEMBER -> true
else -> false
}
diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
index ffaf462244d..c71c16c052e 100644
--- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
+++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
@@ -79,6 +79,9 @@ class DebugVectorFeatures(
override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled)
?: vectorFeatures.isVoiceBroadcastEnabled()
+ override fun isJitsiCallNotificationEnabled(): Boolean = read(DebugFeatureKeys.jitsiCallNotificationsEnabled)
+ ?: vectorFeatures.isJitsiCallNotificationEnabled()
+
override fun isUnverifiedSessionsAlertEnabled(): Boolean = read(DebugFeatureKeys.unverifiedSessionsAlertEnabled)
?: vectorFeatures.isUnverifiedSessionsAlertEnabled()
@@ -143,4 +146,5 @@ object DebugFeatureKeys {
val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled")
val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled")
val unverifiedSessionsAlertEnabled = booleanPreferencesKey("unverified-sessions-alert-enabled")
+ val jitsiCallNotificationsEnabled = booleanPreferencesKey("jitsi-call-notifications-enabled")
}
diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml
index c12af1f568d..6d83f32a047 100755
--- a/vector-config/src/main/res/values/config-settings.xml
+++ b/vector-config/src/main/res/values/config-settings.xml
@@ -51,6 +51,7 @@
false
true
false
+ false
diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt b/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt
index acf22504498..a99523017c3 100644
--- a/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt
+++ b/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt
@@ -25,6 +25,7 @@ import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.navigation.Navigator
+import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.pin.PinLocker
import im.vector.app.features.rageshake.BugReporter
import im.vector.app.features.session.SessionListener
@@ -41,6 +42,8 @@ interface SingletonEntryPoint {
fun avatarRenderer(): AvatarRenderer
+ fun notificationUtils(): NotificationUtils
+
fun activeSessionHolder(): ActiveSessionHolder
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt
index 1fa2a68941c..e785f6b5ef1 100644
--- a/vector/src/main/java/im/vector/app/features/MainActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt
@@ -42,6 +42,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.threads.ThreadsActivity
import im.vector.app.features.location.live.map.LiveLocationMapViewActivity
import im.vector.app.features.notifications.NotificationDrawerManager
+import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.pin.UnlockedActivity
import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository
import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper
@@ -88,7 +89,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
private const val EXTRA_NEXT_INTENT = "EXTRA_NEXT_INTENT"
private const val EXTRA_INIT_SESSION = "EXTRA_INIT_SESSION"
private const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID"
+ private const val EXTRA_CALL_ID = "EXTRA_CALL_ID"
private const val ACTION_ROOM_DETAILS_FROM_SHORTCUT = "ROOM_DETAILS_FROM_SHORTCUT"
+ private const val ACTION_ROOM_DETAILS_JITSI_CALL = "ROOM_DETAILS_JITSI_CALL"
// Special action to clear cache and/or clear credentials
fun restartApp(activity: Activity, args: MainActivityArgs) {
@@ -119,6 +122,20 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
}
}
+ fun jitsiCallIntent(
+ context: Context,
+ roomId: String,
+ callId: String,
+ ): Intent {
+ return Intent(context, MainActivity::class.java).apply {
+ action = ACTION_ROOM_DETAILS_JITSI_CALL
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+
+ putExtra(EXTRA_ROOM_ID, roomId)
+ putExtra(EXTRA_CALL_ID, callId)
+ }
+ }
+
val allowList = listOf(
HomeActivity::class.java.name,
MainActivity::class.java.name,
@@ -137,6 +154,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
private lateinit var args: MainActivityArgs
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
+ @Inject lateinit var notificationUtils: NotificationUtils
@Inject lateinit var uiStateRepository: UiStateRepository
@Inject lateinit var shortcutsHandler: ShortcutsHandler
@Inject lateinit var pinCodeHelper: PinCodeHelper
@@ -204,6 +222,17 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
setResult(RESULT_OK)
finish()
} else if (intent.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) {
+ startSyncing()
+ val roomId = intent.getStringExtra(EXTRA_ROOM_ID)
+ if (roomId?.isNotEmpty() == true) {
+ navigator.openRoom(this, roomId, trigger = ViewRoom.Trigger.Shortcut)
+ }
+ finish()
+ } else if (intent.action == ACTION_ROOM_DETAILS_JITSI_CALL) {
+ val callId = intent.getStringExtra(EXTRA_CALL_ID).orEmpty()
+
+ notificationUtils.cancelNotificationMessage(callId, NotificationDrawerManager.JITSI_CALL_NOTIFICATION_ID)
+
startSyncing()
val roomId = intent.getStringExtra(EXTRA_ROOM_ID)
if (roomId?.isNotEmpty() == true) {
diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
index 65c1d036554..f1b9c4007dc 100644
--- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
+++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
@@ -41,6 +41,7 @@ interface VectorFeatures {
*/
fun isNewAppLayoutFeatureEnabled(): Boolean
fun isVoiceBroadcastEnabled(): Boolean
+ fun isJitsiCallNotificationEnabled(): Boolean
fun isUnverifiedSessionsAlertEnabled(): Boolean
}
@@ -58,5 +59,6 @@ class DefaultVectorFeatures : VectorFeatures {
override fun forceUsageOfOpusEncoder(): Boolean = false
override fun isNewAppLayoutFeatureEnabled(): Boolean = true
override fun isVoiceBroadcastEnabled(): Boolean = true
+ override fun isJitsiCallNotificationEnabled(): Boolean = true
override fun isUnverifiedSessionsAlertEnabled(): Boolean = true
}
diff --git a/vector/src/main/java/im/vector/app/features/call/service/CallHeadsUpActionReceiver.kt b/vector/src/main/java/im/vector/app/features/call/service/CallHeadsUpActionReceiver.kt
index 161aa33d1df..ef28c258a53 100644
--- a/vector/src/main/java/im/vector/app/features/call/service/CallHeadsUpActionReceiver.kt
+++ b/vector/src/main/java/im/vector/app/features/call/service/CallHeadsUpActionReceiver.kt
@@ -20,7 +20,9 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import im.vector.app.core.extensions.singletonEntryPoint
+import im.vector.app.core.services.CallAndroidService
import im.vector.app.features.call.webrtc.WebRtcCallManager
+import im.vector.app.features.notifications.NotificationDrawerManager
import timber.log.Timber
class CallHeadsUpActionReceiver : BroadcastReceiver() {
@@ -33,9 +35,11 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
val webRtcCallManager = context.singletonEntryPoint().webRtcCallManager()
+ val notificationUtils = context.singletonEntryPoint().notificationUtils()
when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) {
CALL_ACTION_REJECT -> {
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
+ notificationUtils.cancelNotificationMessage(callId, NotificationDrawerManager.JITSI_CALL_NOTIFICATION_ID)
onCallRejectClicked(webRtcCallManager, callId)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventProcessor.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventProcessor.kt
index 81b9844e36e..b63ec903a20 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventProcessor.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventProcessor.kt
@@ -41,17 +41,29 @@ class NotifiableEventProcessor @Inject constructor(
.also { Timber.d("notification message removed due to being read") }
else -> KEEP
}
+ is NotifiableJitsiEvent -> {
+ if (it.isReceived != true) {
+ KEEP
+ } else {
+ REMOVE
+ }
+ }
is SimpleNotifiableEvent -> when (it.type) {
EventType.REDACTION -> REMOVE
else -> KEEP
}
}
- ProcessedEvent(type, it)
+
+ val updatedEvent = if (it is NotifiableJitsiEvent) it.updateReceivedStatus() else it
+ ProcessedEvent(type, updatedEvent)
}
val removedEventsDiff = renderedEvents.filter { renderedEvent ->
queuedEvents.none { it.eventId == renderedEvent.event.eventId }
- }.map { ProcessedEvent(REMOVE, it.event) }
+ }.map {
+ val updatedEvent = if (it.event is NotifiableJitsiEvent) it.event.updateReceivedStatus() else it.event
+ ProcessedEvent(REMOVE, updatedEvent)
+ }
return removedEventsDiff + processedEvents
}
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 70ab94e8d55..e93411bc233 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
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
import org.matrix.android.sdk.api.session.events.model.isEdition
import org.matrix.android.sdk.api.session.events.model.isImageMessage
+import org.matrix.android.sdk.api.session.events.model.isJitsiEvent
import org.matrix.android.sdk.api.session.events.model.supportsNotification
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
@@ -80,6 +81,9 @@ class NotifiableEventResolver @Inject constructor(
event.supportsNotification() || event.type == EventType.ENCRYPTED -> {
resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
}
+ event.isJitsiEvent() -> {
+ resolveJitsiEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
+ }
else -> {
// If the event can be displayed, display it as is
Timber.w("NotifiableEventResolver Received an unsupported event matching a bing rule")
@@ -133,6 +137,51 @@ class NotifiableEventResolver @Inject constructor(
}
}
+ private fun resolveJitsiEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableJitsiEvent?{
+ // The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...)
+ val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/)
+
+ return if (room != null) {
+ val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString()
+ val roomName = room.roomSummary()?.displayName.orEmpty()
+ val senderDisplayName = event.senderInfo.disambiguatedDisplayName
+
+ NotifiableJitsiEvent(
+ eventId = event.root.eventId.orEmpty(),
+ editedEventId = event.getEditedEventId(),
+ canBeReplaced = canBeReplaced,
+ timestamp = event.root.originServerTs ?: 0,
+ noisy = isNoisy,
+ senderName = senderDisplayName,
+ senderId = event.root.senderId,
+ body = body,
+ roomId = event.root.roomId!!,
+ threadId = event.root.getRootThreadEventId(),
+ roomName = roomName,
+ roomIsDirect = room.roomSummary()?.isDirect ?: false,
+ roomAvatarPath = session.contentUrlResolver()
+ .resolveThumbnail(
+ room.roomSummary()?.avatarUrl,
+ 250,
+ 250,
+ ContentUrlResolver.ThumbnailMethod.SCALE
+ ),
+ senderAvatarPath = session.contentUrlResolver()
+ .resolveThumbnail(
+ event.senderInfo.avatarUrl,
+ 250,
+ 250,
+ ContentUrlResolver.ThumbnailMethod.SCALE
+ ),
+ matrixID = session.myUserId,
+ soundName = null,
+ isReceived = null,
+ )
+ } else {
+ null
+ }
+ }
+
fun TimelineEvent.getCaption(): String? = (getLastMessageContent() as? MessageWithAttachmentContent)?.getCaption()
fun TimelineEvent.getFilename(): String? = (getLastMessageContent() as? MessageWithAttachmentContent)?.getFileName()
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableJitsiEvent.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableJitsiEvent.kt
new file mode 100644
index 00000000000..ca43d31f319
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableJitsiEvent.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 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.notifications
+
+import org.matrix.android.sdk.api.session.events.model.EventType
+
+data class NotifiableJitsiEvent(
+ override val eventId: String,
+ override val editedEventId: String?,
+ override val canBeReplaced: Boolean,
+ val noisy: Boolean,
+ val timestamp: Long,
+ val senderName: String?,
+ val senderId: String?,
+ val body: String?,
+ val roomId: String,
+ val threadId: String?,
+ val roomName: String?,
+ val roomIsDirect: Boolean = false,
+ val roomAvatarPath: String? = null,
+ val senderAvatarPath: String? = null,
+ val matrixID: String? = null,
+ val soundName: String? = null,
+ // This is used for >N notification, as the result of a smart reply
+ val outGoingMessage: Boolean = false,
+ val outGoingMessageFailed: Boolean = false,
+ var isReceived: Boolean? = null,
+ override val isRedacted: Boolean = false,
+ override val isUpdated: Boolean = false
+) : NotifiableEvent {
+
+ val type: String = EventType.MESSAGE
+ val description: String = body ?: ""
+ val title: String = senderName ?: ""
+
+ fun updateReceivedStatus() = this.copy(
+ isReceived = when (isReceived) {
+ null -> false
+ false -> true
+ true -> true
+ }
+ )
+}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt
index 247674dbe48..f37cfb05e81 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt
@@ -240,5 +240,6 @@ class NotificationDrawerManager @Inject constructor(
const val ROOM_MESSAGES_NOTIFICATION_ID = 1
const val ROOM_EVENT_NOTIFICATION_ID = 2
const val ROOM_INVITATION_NOTIFICATION_ID = 3
+ const val JITSI_CALL_NOTIFICATION_ID = 4
}
}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationEventQueue.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationEventQueue.kt
index 8aff9c3bf26..4478a523379 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationEventQueue.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationEventQueue.kt
@@ -36,6 +36,7 @@ data class NotificationEventQueue(
is InviteNotifiableEvent -> it.copy(isRedacted = true)
is NotifiableMessageEvent -> it.copy(isRedacted = true)
is SimpleNotifiableEvent -> it.copy(isRedacted = true)
+ is NotifiableJitsiEvent -> it.copy(isRedacted = true)
}
}
}
@@ -117,6 +118,7 @@ data class NotificationEventQueue(
is InviteNotifiableEvent -> with.copy(isUpdated = true)
is NotifiableMessageEvent -> with.copy(isUpdated = true)
is SimpleNotifiableEvent -> with.copy(isUpdated = true)
+ is NotifiableJitsiEvent -> with.copy(isUpdated = true)
}
)
}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt
index 7292f650091..5bef05bf75d 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationFactory.kt
@@ -20,6 +20,7 @@ import android.app.Notification
import javax.inject.Inject
private typealias ProcessedMessageEvents = List>
+private typealias ProcessedJitsiEvents = List>
class NotificationFactory @Inject constructor(
private val notificationUtils: NotificationUtils,
@@ -27,6 +28,28 @@ class NotificationFactory @Inject constructor(
private val summaryGroupMessageCreator: SummaryGroupMessageCreator
) {
+ fun Map.toNotifications(): List {
+ return map { (roomId, events) ->
+ if (events.all { it.event.isReceived == true }) {
+ return emptyList()
+ }
+
+ val eventToShow = events.first { it.event.isReceived == false }
+
+ JitsiNotification.IncomingCall(
+ roomId = roomId,
+ eventId = eventToShow.event.eventId,
+ roomName = eventToShow.event.roomName.orEmpty(),
+ notification = notificationUtils.buildIncomingJitsiCallNotification(
+ callId = eventToShow.event.eventId.ifEmpty { roomId },
+ signalingRoomId = roomId,
+ title = eventToShow.event.roomName.orEmpty(),
+ fromBg = true,
+ )
+ )
+ }
+ }
+
fun Map.toNotifications(myUserDisplayName: String, myUserAvatarUrl: String?): List {
return map { (roomId, events) ->
when {
@@ -117,6 +140,15 @@ sealed interface RoomNotification {
}
}
+sealed interface JitsiNotification {
+ data class IncomingCall(
+ val roomId: String,
+ val eventId: String,
+ val roomName: String,
+ val notification: Notification,
+ ) : JitsiNotification
+}
+
sealed interface OneShotNotification {
data class Removed(val key: String) : OneShotNotification
data class Append(val notification: Notification, val meta: Meta) : OneShotNotification {
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt
index ab59b2e6d80..098d173413f 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt
@@ -15,19 +15,20 @@
*/
package im.vector.app.features.notifications
-import android.content.Context
import androidx.annotation.WorkerThread
+import im.vector.app.features.notifications.NotificationDrawerManager.Companion.JITSI_CALL_NOTIFICATION_ID
import im.vector.app.features.notifications.NotificationDrawerManager.Companion.ROOM_EVENT_NOTIFICATION_ID
import im.vector.app.features.notifications.NotificationDrawerManager.Companion.ROOM_INVITATION_NOTIFICATION_ID
import im.vector.app.features.notifications.NotificationDrawerManager.Companion.ROOM_MESSAGES_NOTIFICATION_ID
import im.vector.app.features.notifications.NotificationDrawerManager.Companion.SUMMARY_NOTIFICATION_ID
+import im.vector.app.features.settings.VectorPreferences
import timber.log.Timber
import javax.inject.Inject
class NotificationRenderer @Inject constructor(
private val notificationDisplayer: NotificationDisplayer,
private val notificationFactory: NotificationFactory,
- private val appContext: Context
+ private val vectorPreferences: VectorPreferences,
) {
@WorkerThread
@@ -38,9 +39,10 @@ class NotificationRenderer @Inject constructor(
useCompleteNotificationFormat: Boolean,
eventsToProcess: List>
) {
- val (roomEvents, simpleEvents, invitationEvents) = eventsToProcess.groupByType()
+ val (roomEvents, simpleEvents, invitationEvents, jitsiEvents) = eventsToProcess.groupByType()
with(notificationFactory) {
val roomNotifications = roomEvents.toNotifications(myUserDisplayName, myUserAvatarUrl)
+ val jitsiNotifications = jitsiEvents.toNotifications()
val invitationNotifications = invitationEvents.toNotifications(myUserId)
val simpleNotifications = simpleEvents.toNotifications(myUserId)
val summaryNotification = createSummaryNotification(
@@ -69,6 +71,21 @@ class NotificationRenderer @Inject constructor(
}
}
+ Timber.d("Jitsi call notifications count = ${jitsiNotifications.size}")
+ if (vectorPreferences.isJitsiCallNotificationEnabled()) {
+ jitsiNotifications.forEach { wrapper ->
+ when (wrapper) {
+ is JitsiNotification.IncomingCall -> {
+ Timber.d("Updating jitsi call notification ${wrapper.eventId} for room ${wrapper.roomName}")
+ if (wrapper.eventId.isNotEmpty() || wrapper.roomId.isNotEmpty()) {
+ val tag = wrapper.eventId.ifEmpty { wrapper.roomId }
+ notificationDisplayer.showNotificationMessage(tag, JITSI_CALL_NOTIFICATION_ID, wrapper.notification)
+ }
+ }
+ }
+ }
+ }
+
invitationNotifications.forEach { wrapper ->
when (wrapper) {
is OneShotNotification.Removed -> {
@@ -108,6 +125,7 @@ private fun List>.groupByType(): GroupedNotifica
val roomIdToEventMap: MutableMap>> = LinkedHashMap()
val simpleEvents: MutableList> = ArrayList()
val invitationEvents: MutableList> = ArrayList()
+ val roomIdToJitsiEventMap: MutableMap>> = LinkedHashMap()
forEach {
when (val event = it.event) {
is InviteNotifiableEvent -> invitationEvents.add(it.castedToEventType())
@@ -115,10 +133,14 @@ private fun List>.groupByType(): GroupedNotifica
val roomEvents = roomIdToEventMap.getOrPut(event.roomId) { ArrayList() }
roomEvents.add(it.castedToEventType())
}
+ is NotifiableJitsiEvent -> {
+ val jitsiEvents = roomIdToJitsiEventMap.getOrPut(event.roomId) { ArrayList() }
+ jitsiEvents.add(it.castedToEventType())
+ }
is SimpleNotifiableEvent -> simpleEvents.add(it.castedToEventType())
}
}
- return GroupedNotificationEvents(roomIdToEventMap, simpleEvents, invitationEvents)
+ return GroupedNotificationEvents(roomIdToEventMap, simpleEvents, invitationEvents, roomIdToJitsiEventMap)
}
@Suppress("UNCHECKED_CAST")
@@ -127,5 +149,6 @@ private fun ProcessedEvent.castedToEventT
data class GroupedNotificationEvents(
val roomEvents: Map>>,
val simpleEvents: List>,
- val invitationEvents: List>
+ val invitationEvents: List>,
+ val jitsiEvents: Map>>,
)
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
index d04571835c7..564ee90b69c 100755
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
@@ -366,6 +366,79 @@ class NotificationUtils @Inject constructor(
return builder.build()
}
+ /**
+ * Build an incoming jitsi call notification.
+ * This notification starts the VectorHomeActivity which is in charge of centralizing the incoming call flow.
+ *
+ * @param callId id of the jitsi call
+ * @param signalingRoomId id of the room
+ * @param title title of the notification
+ * @param fromBg true if the app is in background when posting the notification
+ * @return the call notification.
+ */
+ fun buildIncomingJitsiCallNotification(
+ callId: String,
+ signalingRoomId: String,
+ title: String,
+ fromBg: Boolean,
+ ): Notification {
+ val accentColor = ContextCompat.getColor(context, im.vector.lib.ui.styles.R.color.notification_accent_color)
+ val notificationChannel = if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
+ val builder = NotificationCompat.Builder(context, notificationChannel)
+ .setContentTitle(ensureTitleNotEmpty(title))
+ .apply {
+ setContentText(stringProvider.getString(CommonStrings.incoming_video_call))
+ setSmallIcon(R.drawable.ic_call_answer_video)
+ }
+ .setCategory(NotificationCompat.CATEGORY_CALL)
+ .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary))
+ .setLights(accentColor, 500, 500)
+ .setOngoing(true)
+
+ val contentIntent = MainActivity.jitsiCallIntent(
+ context = context,
+ roomId = signalingRoomId,
+ callId = callId,
+ )
+
+ val contentPendingIntent = PendingIntent.getActivity(
+ context,
+ clock.epochMillis().toInt(),
+ contentIntent,
+ PendingIntentCompat.FLAG_IMMUTABLE
+ )
+
+ val answerCallPendingIntent = TaskStackBuilder.create(context)
+ .addNextIntentWithParentStack(HomeActivity.newIntent(context, firstStartMainActivity = false))
+ .addNextIntent(contentIntent)
+ .getPendingIntent(clock.epochMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
+
+ val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
+
+ builder.addAction(
+ NotificationCompat.Action(
+ IconCompat.createWithResource(context, R.drawable.ic_call_hangup)
+ .setTint(ThemeUtils.getColor(context, android.R.attr.colorError)),
+ getActionText(CommonStrings.call_notification_reject, android.R.attr.colorError),
+ rejectCallPendingIntent
+ )
+ )
+
+ builder.addAction(
+ NotificationCompat.Action(
+ R.drawable.ic_call_answer,
+ getActionText(CommonStrings.call_notification_open_app_action, android.R.attr.colorPrimary),
+ answerCallPendingIntent
+ )
+ )
+ if (fromBg) {
+ // Compat: Display the incoming call notification on the lock screen
+ builder.priority = NotificationCompat.PRIORITY_HIGH
+ builder.setFullScreenIntent(contentPendingIntent, true)
+ }
+ return builder.build()
+ }
+
fun buildOutgoingRingingCallNotification(
call: WebRtcCall,
title: String
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 627c0fd403c..5149bedd02f 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -85,6 +85,7 @@ class VectorPreferences @Inject constructor(
const val SETTINGS_LABS_NEW_SESSION_MANAGER_KEY = "SETTINGS_LABS_NEW_SESSION_MANAGER_KEY"
const val SETTINGS_LABS_CLIENT_INFO_RECORDING_KEY = "SETTINGS_LABS_CLIENT_INFO_RECORDING_KEY"
const val SETTINGS_LABS_VOICE_BROADCAST_KEY = "SETTINGS_LABS_VOICE_BROADCAST_KEY"
+ const val SETTINGS_LABS_JITSI_CALL_NOTIFICATION_KEY = "SETTINGS_LABS_JITSI_CALL_NOTIFICATION_KEY"
const val SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
@@ -1597,6 +1598,11 @@ class VectorPreferences @Inject constructor(
)
}
+ fun isJitsiCallNotificationEnabled(): Boolean {
+ return vectorFeatures.isJitsiCallNotificationEnabled() &&
+ defaultPrefs.getBoolean(SETTINGS_LABS_JITSI_CALL_NOTIFICATION_KEY, getDefault(im.vector.app.config.R.bool.settings_labs_enable_jitsi_call_notifications_default))
+ }
+
fun showIpAddressInSessionManagerScreens(): Boolean {
return defaultPrefs.getBoolean(
SETTINGS_SESSION_MANAGER_SHOW_IP_ADDRESS,
diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml
index 0b2c6f67c7e..752a41aa376 100644
--- a/vector/src/main/res/xml/vector_settings_labs.xml
+++ b/vector/src/main/res/xml/vector_settings_labs.xml
@@ -202,5 +202,11 @@
android:title="@string/labs_enable_voice_broadcast_title"
app:isPreferenceVisible="@bool/settings_labs_enable_voice_broadcast_visible" />
+
+