Add support for showing the native OS incoming call screen when starting new Element Calls

This commit is contained in:
Stefan Ceriu
2024-05-17 15:13:57 +03:00
committed by Stefan Ceriu
parent 5860317fea
commit 8bfd802793
19 changed files with 460 additions and 66 deletions
@@ -46,6 +46,8 @@ struct NotificationContentBuilder {
return try await processPollStartEvent(notificationItem: notificationItem, pollQuestion: question, mediaProvider: mediaProvider)
case .callInvite:
return try await processCallInviteEvent(notificationItem: notificationItem, mediaProvider: mediaProvider)
case .callNotify:
return try await processCallNotifyEvent(notificationItem: notificationItem, mediaProvider: mediaProvider)
default:
return processEmpty(notificationItem: notificationItem)
}
@@ -136,6 +138,12 @@ struct NotificationContentBuilder {
notification.body = L10n.commonCallInvite
return notification
}
private func processCallNotifyEvent(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
notification.body = L10n.commonCallStarted
return notification
}
private func processCommonRoomMessage(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
var notification = baseMutableContent(for: notificationItem)
@@ -14,6 +14,7 @@
// limitations under the License.
//
import CallKit
import Intents
import MatrixRustSDK
import UserNotifications
@@ -104,6 +105,10 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
return discard(unreadCount: unreadCount)
}
guard await shouldHandleCallNotification(itemProxy) else {
return discard(unreadCount: unreadCount)
}
// After the first processing, update the modified content
modifiedContent = try await notificationContentBuilder.content(for: itemProxy, mediaProvider: nil)
@@ -173,4 +178,48 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
NSELogger.logMemory(with: tag)
MXLog.info("\(tag) deinit")
}
private func shouldHandleCallNotification(_ itemProxy: NotificationItemProxyProtocol) async -> Bool {
// Handle incoming VoIP calls, show the native OS call screen
// https://developer.apple.com/documentation/callkit/sending-end-to-end-encrypted-voip-calls
//
// The way this works is the following:
// - the NSE receives the notification and decrypts it
// - checks if it's still time relevant (max 10 seconds old) and whether it should ring
// - otherwise it goes on to show it as a normal notification
// - if it should ring then it discards the notification but invokes `reportNewIncomingVoIPPushPayload`
// so that the main app can handle it
// - the main app picks this up in `PKPushRegistry.didReceiveIncomingPushWith` and
// `CXProvider.reportNewIncomingCall` to show the system UI and handle actions on it.
// N.B. this flow works properly only when background processing capabilities are enabled
guard case let .timeline(event) = itemProxy.event,
case let .messageLike(content) = try? event.eventType(),
case let .callNotify(notificationType) = content,
notificationType == .ring else {
return true
}
let timestamp = Date(timeIntervalSince1970: TimeInterval(event.timestamp() / 1000))
guard abs(timestamp.timeIntervalSinceNow) < ElementCallServiceNotificationDiscardDelta else {
MXLog.info("Call notification is too old, handling as push notification")
return true
}
var payload = [ElementCallServiceNotificationKey.roomID.rawValue: itemProxy.roomID,
ElementCallServiceNotificationKey.roomDisplayName.rawValue: itemProxy.roomDisplayName,
ElementCallServiceNotificationKey.senderID.rawValue: itemProxy.senderID]
if let senderDisplayName = itemProxy.senderDisplayName {
payload[ElementCallServiceNotificationKey.senderDisplayName.rawValue] = senderDisplayName
}
do {
try await CXProvider.reportNewIncomingVoIPPushPayload(payload)
} catch {
MXLog.error("Failed reporting voip call with error: \(error). Handling as push notification")
return true
}
return false
}
}
+1
View File
@@ -104,4 +104,5 @@ targets:
- path: ../../ElementX/Sources/Services/Notification/Proxy
- path: ../../ElementX/Sources/Services/Room/RoomSummary/RoomMessageEventStringBuilder.swift
- path: ../../ElementX/Sources/Services/UserSession/RestorationToken.swift
- path: ../../ElementX/Sources/Services/ElementCall/ElementCallServiceProtocol.swift
- path: ../../ElementX/Sources/Application/AppSettings.swift