From f6c476eeca703778e9c8676c97a709b6144b6aa9 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 27 May 2024 12:49:24 +0300 Subject: [PATCH] Show ElementCalls in the system Recents list and allow deep linking back into a call from there --- .../Sources/Application/AppCoordinator.swift | 15 +++++++++++++++ .../Application/AppCoordinatorProtocol.swift | 3 +++ ElementX/Sources/Application/Application.swift | 5 +++++ .../ElementCall/ElementCallService.swift | 16 +++++++--------- .../ElementCall/ElementCallServiceProtocol.swift | 2 -- .../Sources/UITests/UITestsAppCoordinator.swift | 4 ++++ .../UnitTests/UnitTestsAppCoordinator.swift | 4 ++++ ElementX/SupportingFiles/Info.plist | 1 + ElementX/SupportingFiles/target.yml | 3 ++- NSE/Sources/NotificationServiceExtension.swift | 8 ++------ 10 files changed, 43 insertions(+), 18 deletions(-) diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index f245797c7..ab3acaa92 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -17,6 +17,7 @@ import AnalyticsEvents import BackgroundTasks import Combine +import Intents import MatrixRustSDK import SwiftUI import Version @@ -236,6 +237,20 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg return false } + func handleUserActivity(_ userActivity: NSUserActivity) { + // `INStartVideoCallIntent` is to be replaced with `INStartCallIntent` + // but calls from Recents still send it ¯\_(ツ)_/¯ + guard let intent = userActivity.interaction?.intent as? INStartVideoCallIntent, + let contact = intent.contacts?.first, + let roomIdentifier = contact.personHandle?.value else { + MXLog.error("Failed retrieving information from userActivity: \(userActivity)") + return + } + + MXLog.info("Starting call in room: \(roomIdentifier)") + handleAppRoute(AppRoute.call(roomID: roomIdentifier)) + } + // MARK: - AuthenticationFlowCoordinatorDelegate func authenticationFlowCoordinator(didLoginWithSession userSession: UserSessionProtocol) { diff --git a/ElementX/Sources/Application/AppCoordinatorProtocol.swift b/ElementX/Sources/Application/AppCoordinatorProtocol.swift index 2419526da..7ac7d1d68 100644 --- a/ElementX/Sources/Application/AppCoordinatorProtocol.swift +++ b/ElementX/Sources/Application/AppCoordinatorProtocol.swift @@ -18,5 +18,8 @@ import Foundation protocol AppCoordinatorProtocol: CoordinatorProtocol { var windowManager: SecureWindowManagerProtocol { get } + @discardableResult func handleDeepLink(_ url: URL, isExternalURL: Bool) -> Bool + + func handleUserActivity(_ userActivity: NSUserActivity) } diff --git a/ElementX/Sources/Application/Application.swift b/ElementX/Sources/Application/Application.swift index 434e265bf..efef47f74 100644 --- a/ElementX/Sources/Application/Application.swift +++ b/ElementX/Sources/Application/Application.swift @@ -51,6 +51,11 @@ struct Application: App { openURLInSystemBrowser($0) } } + .onContinueUserActivity("INStartVideoCallIntent", perform: { userActivity in + // `INStartVideoCallIntent` is to be replaced with `INStartCallIntent` + // but calls from Recents still send it ¯\_(ツ)_/¯ + appCoordinator.handleUserActivity(userActivity) + }) .task { appCoordinator.start() } diff --git a/ElementX/Sources/Services/ElementCall/ElementCallService.swift b/ElementX/Sources/Services/ElementCall/ElementCallService.swift index 28bec8d46..5c9b90690 100644 --- a/ElementX/Sources/Services/ElementCall/ElementCallService.swift +++ b/ElementX/Sources/Services/ElementCall/ElementCallService.swift @@ -91,7 +91,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { guard let roomID = payload.dictionaryPayload[ElementCallServiceNotificationKey.roomID.rawValue] as? String else { - MXLog.error("Somethnig went wrong, missing room identifier for incoming voip call: \(payload)") + MXLog.error("Something went wrong, missing room identifier for incoming voip call: \(payload)") return } @@ -102,20 +102,18 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe let configuration = CXProviderConfiguration() configuration.supportsVideo = true - configuration.includesCallsInRecents = false + configuration.includesCallsInRecents = true + + // https://stackoverflow.com/a/46077628/730924 + configuration.supportedHandleTypes = [.generic] let update = CXCallUpdate() update.hasVideo = true update.localizedCallerName = payload.dictionaryPayload[ElementCallServiceNotificationKey.roomDisplayName.rawValue] as? String - if let senderDisplayName = payload.dictionaryPayload[ElementCallServiceNotificationKey.senderDisplayName.rawValue] as? String { - update.remoteHandle = .init(type: .generic, value: senderDisplayName) - } else if let senderID = payload.dictionaryPayload[ElementCallServiceNotificationKey.senderID.rawValue] as? String { - update.remoteHandle = .init(type: .generic, value: senderID) - } else { - MXLog.error("Something went wrong, both the user display name and ID are nil") - } + // https://stackoverflow.com/a/41230020/730924 + update.remoteHandle = .init(type: .generic, value: roomID) let callProvider = CXProvider(configuration: configuration) callProvider.setDelegate(self, queue: nil) diff --git a/ElementX/Sources/Services/ElementCall/ElementCallServiceProtocol.swift b/ElementX/Sources/Services/ElementCall/ElementCallServiceProtocol.swift index 51af47edb..c05d593f1 100644 --- a/ElementX/Sources/Services/ElementCall/ElementCallServiceProtocol.swift +++ b/ElementX/Sources/Services/ElementCall/ElementCallServiceProtocol.swift @@ -24,8 +24,6 @@ enum ElementCallServiceAction { enum ElementCallServiceNotificationKey: String { case roomID case roomDisplayName - case senderID - case senderDisplayName } let ElementCallServiceNotificationDiscardDelta = 10.0 diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index acdb99907..3dfa3e439 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -70,6 +70,10 @@ class UITestsAppCoordinator: AppCoordinatorProtocol, SecureWindowManagerDelegate fatalError("Not implemented.") } + func handleUserActivity(_ activity: NSUserActivity) { + fatalError("Not implemented.") + } + func windowManagerDidConfigureWindows(_ windowManager: SecureWindowManagerProtocol) { ServiceLocator.shared.userIndicatorController.window = windowManager.overlayWindow diff --git a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift index b866605ef..6c669d8a2 100644 --- a/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift +++ b/ElementX/Sources/UnitTests/UnitTestsAppCoordinator.swift @@ -41,4 +41,8 @@ class UnitTestsAppCoordinator: AppCoordinatorProtocol { func handleDeepLink(_ url: URL, isExternalURL: Bool) -> Bool { fatalError("Not implemented.") } + + func handleUserActivity(_ activity: NSUserActivity) { + fatalError("Not implemented.") + } } diff --git a/ElementX/SupportingFiles/Info.plist b/ElementX/SupportingFiles/Info.plist index f154a5033..fb51c555a 100644 --- a/ElementX/SupportingFiles/Info.plist +++ b/ElementX/SupportingFiles/Info.plist @@ -77,6 +77,7 @@ NSUserActivityTypes INSendMessageIntent + INStartCallIntent UIBackgroundModes diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index 98e3820f0..601e96c4d 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -82,7 +82,8 @@ targets: productionAppName: $(PRODUCTION_APP_NAME) ITSAppUsesNonExemptEncryption: false NSUserActivityTypes: [ - INSendMessageIntent + INSendMessageIntent, + INStartCallIntent, ] NSCameraUsageDescription: To take pictures or videos and send them as a message $(APP_DISPLAY_NAME) needs access to the camera. NSMicrophoneUsageDescription: To record and send messages with audio, $(APP_DISPLAY_NAME) needs to access the microphone. diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index 802fc3200..baac46305 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -206,12 +206,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension { 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 - } + let payload = [ElementCallServiceNotificationKey.roomID.rawValue: itemProxy.roomID, + ElementCallServiceNotificationKey.roomDisplayName.rawValue: itemProxy.roomDisplayName] do { try await CXProvider.reportNewIncomingVoIPPushPayload(payload)