Fixes #3042 - Cancel ElementCall ringing as soon as the call ends

This commit is contained in:
Stefan Ceriu
2024-07-16 17:09:16 +03:00
committed by Stefan Ceriu
parent d16ba9f3ef
commit 239afeb662
9 changed files with 188 additions and 18 deletions
@@ -42,6 +42,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
didSet {
userSessionObserver?.cancel()
if userSession != nil {
configureElementCallService()
configureNotificationManager()
observeUserSessionChanges()
startSync()
@@ -637,6 +638,14 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
}
}
}
private func configureElementCallService() {
guard let userSession else {
fatalError("User session not setup")
}
elementCallService.setClientProxy(userSession.clientProxy)
}
private func configureNotificationManager() {
notificationManager.setUserSession(userSession)
@@ -4826,6 +4826,47 @@ class ElementCallServiceMock: ElementCallServiceProtocol {
}
var underlyingActions: AnyPublisher<ElementCallServiceAction, Never>!
//MARK: - setClientProxy
var setClientProxyUnderlyingCallsCount = 0
var setClientProxyCallsCount: Int {
get {
if Thread.isMainThread {
return setClientProxyUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = setClientProxyUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
setClientProxyUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
setClientProxyUnderlyingCallsCount = newValue
}
}
}
}
var setClientProxyCalled: Bool {
return setClientProxyCallsCount > 0
}
var setClientProxyReceivedClientProxy: ClientProxyProtocol?
var setClientProxyReceivedInvocations: [ClientProxyProtocol] = []
var setClientProxyClosure: ((ClientProxyProtocol) -> Void)?
func setClientProxy(_ clientProxy: ClientProxyProtocol) {
setClientProxyCallsCount += 1
setClientProxyReceivedClientProxy = clientProxy
DispatchQueue.main.async {
self.setClientProxyReceivedInvocations.append(clientProxy)
}
setClientProxyClosure?(clientProxy)
}
//MARK: - setupCallSession
var setupCallSessionRoomIDRoomDisplayNameUnderlyingCallsCount = 0
@@ -8300,6 +8341,41 @@ class RoomProxyMock: RoomProxyProtocol {
subscribeForUpdatesCallsCount += 1
await subscribeForUpdatesClosure?()
}
//MARK: - subscribeToRoomInfoUpdates
var subscribeToRoomInfoUpdatesUnderlyingCallsCount = 0
var subscribeToRoomInfoUpdatesCallsCount: Int {
get {
if Thread.isMainThread {
return subscribeToRoomInfoUpdatesUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = subscribeToRoomInfoUpdatesUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
subscribeToRoomInfoUpdatesUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
subscribeToRoomInfoUpdatesUnderlyingCallsCount = newValue
}
}
}
}
var subscribeToRoomInfoUpdatesCalled: Bool {
return subscribeToRoomInfoUpdatesCallsCount > 0
}
var subscribeToRoomInfoUpdatesClosure: (() -> Void)?
func subscribeToRoomInfoUpdates() {
subscribeToRoomInfoUpdatesCallsCount += 1
subscribeToRoomInfoUpdatesClosure?()
}
//MARK: - timelineFocusedOnEvent
var timelineFocusedOnEventEventIDNumberOfEventsUnderlyingCallsCount = 0
@@ -44,7 +44,17 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
return CXProvider(configuration: configuration)
}()
private var incomingCallID: CallID?
private weak var clientProxy: ClientProxyProtocol?
private var cancellables = Set<AnyCancellable>()
private var incomingCallID: CallID? {
didSet {
Task {
await observeIncomingCallRoomStateUpdates()
}
}
}
private var endUnansweredCallTask: Task<Void, Never>?
private var ongoingCallID: CallID?
@@ -65,6 +75,10 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
callProvider.setDelegate(self, queue: nil)
}
func setClientProxy(_ clientProxy: any ClientProxyProtocol) {
self.clientProxy = clientProxy
}
func setupCallSession(roomID: String, roomDisplayName: String) async {
// Drop any ongoing calls when starting a new one
if ongoingCallID != nil {
@@ -221,4 +235,46 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
ongoingCallID = nil
}
func observeIncomingCallRoomStateUpdates() async {
cancellables.removeAll()
guard let clientProxy, let incomingCallID else {
return
}
guard let roomProxy = await clientProxy.roomForIdentifier(incomingCallID.roomID) else {
return
}
roomProxy.subscribeToRoomInfoUpdates()
// There's no incoming event for call cancellations so try to infer
// it from what we have. If the call is running before subscribing then wait
// for it to change to `false` otherwise wait for it to turn `true` before
// changing to `false`
let isCallOngoing = roomProxy.hasOngoingCall
roomProxy
.actionsPublisher
.map { action in
switch action {
case .roomInfoUpdate:
return roomProxy.hasOngoingCall
}
}
.removeDuplicates()
.dropFirst(isCallOngoing ? 0 : 1)
.sink { [weak self] hasOngoingCall in
guard let self else { return }
if !hasOngoingCall {
MXLog.info("Call has been cancelled")
cancellables.removeAll()
endUnansweredCallTask?.cancel()
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .remoteEnded)
}
}
.store(in: &cancellables)
}
}
@@ -0,0 +1,24 @@
//
// Copyright 2024 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.
//
import Foundation
enum ElementCallServiceNotificationKey: String {
case roomID
case roomDisplayName
}
let ElementCallServiceNotificationDiscardDelta = 10.0
@@ -22,17 +22,12 @@ enum ElementCallServiceAction {
case setCallMuted(_ muted: Bool, roomID: String)
}
enum ElementCallServiceNotificationKey: String {
case roomID
case roomDisplayName
}
let ElementCallServiceNotificationDiscardDelta = 10.0
// sourcery: AutoMockable
protocol ElementCallServiceProtocol {
var actions: AnyPublisher<ElementCallServiceAction, Never> { get }
func setClientProxy(_ clientProxy: ClientProxyProtocol)
func setupCallSession(roomID: String, roomDisplayName: String) async
func tearDownCallSession()
+11 -7
View File
@@ -155,6 +155,17 @@ class RoomProxy: RoomProxyProtocol {
subscribeToTypingNotifications()
}
func subscribeToRoomInfoUpdates() {
guard roomInfoObservationToken == nil else {
return
}
roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: RoomInfoUpdateListener { [weak self] in
MXLog.info("Received room info update")
self?.actionsSubject.send(.roomInfoUpdate)
})
}
func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result<TimelineProxyProtocol, RoomProxyError> {
do {
let timeline = try await room.timelineFocusedOnEvent(eventId: eventID, numContextEvents: numberOfEvents, internalIdPrefix: UUID().uuidString)
@@ -596,13 +607,6 @@ class RoomProxy: RoomProxyProtocol {
// MARK: - Private
private func subscribeToRoomInfoUpdates() {
roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: RoomInfoUpdateListener { [weak self] in
MXLog.info("Received room info update")
self?.actionsSubject.send(.roomInfoUpdate)
})
}
private func subscribeToTypingNotifications() {
typingNotificationObservationToken = room.subscribeToTypingNotifications(listener: RoomTypingNotificationUpdateListener { [weak self] typingUserIDs in
guard let self else { return }
@@ -66,6 +66,8 @@ protocol RoomProxyProtocol {
func subscribeForUpdates() async
func subscribeToRoomInfoUpdates()
func timelineFocusedOnEvent(eventID: String, numberOfEvents: UInt16) async -> Result<TimelineProxyProtocol, RoomProxyError>
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>