Skip to content

Commit

Permalink
Fixes #3042 - Cancel ElementCall ringing as soon as the call ends
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanceriu committed Jul 17, 2024
1 parent 2bfc825 commit 8c1f4a7
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 18 deletions.
8 changes: 6 additions & 2 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@
386720B603F87D156DB01FB2 /* VoiceMessageMediaManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40076C770A5FB83325252973 /* VoiceMessageMediaManager.swift */; };
38896D54D6D675534E606195 /* RoomTimelineControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */; };
388D39ED9FE1122EA6D76BF2 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BC84BA0AF11C2128D58ABD /* Common.swift */; };
3895969759E68FAB90C63EF7 /* ElementCallServiceConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */; };
3982C505960006B341CFD0C6 /* UserDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D0EA07BD545CC9F234DB8D /* UserDetailsEditScreenModels.swift */; };
3982E60F9C126437D5E488A3 /* PillContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A6314FDC51DA25712D9A81 /* PillContextTests.swift */; };
39A987B3E41B976D1DF944C6 /* CallScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A63A59BFDDC494B1C20119 /* CallScreenViewModel.swift */; };
Expand Down Expand Up @@ -453,7 +454,6 @@
6AECC84BE14A13440120FED8 /* NSESettingsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB4F169D653296023ED65E6 /* NSESettingsProtocol.swift */; };
6B05AA5D9BBCD6D8D63B80EB /* TimelineItemAccessibilityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C6F3DAD167F972702C8893 /* TimelineItemAccessibilityModifier.swift */; };
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */; };
6B67AC7AA41136FC9804C136 /* ElementCallServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */; };
6BAD956B909A6E29F6CC6E7C /* ButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC23C63849452BC86EA2852 /* ButtonStyle.swift */; };
6BB6944443C421C722ED1E7D /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */; };
6C34237AFB808E38FC8776B9 /* RoomStateEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */; };
Expand Down Expand Up @@ -676,6 +676,7 @@
9DE801D278AC34737467F937 /* VoiceMessageMediaManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */; };
9E838A62918E47BC72D6640D /* UserIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB54B4F94686CCF0289B72F /* UserIndicatorPresenter.swift */; };
9EBDC79CAC9B63A0D626E333 /* LegalInformationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EB2CAA266B921D128C35710 /* LegalInformationScreenCoordinator.swift */; };
9F11B9F347F9E2D236799FB3 /* ElementCallServiceConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */; };
9F11E743EA01482E78A438B0 /* GlobalSearchScreenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22DB19219E6CC4D002E15D48 /* GlobalSearchScreenCell.swift */; };
9F19096BFA629C0AC282B1E4 /* CreateRoomScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEB4634C0DD7779C4AB504 /* CreateRoomScreenUITests.swift */; };
9FAF6DA7E8E85C9699757764 /* CollapsibleRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2656184491C505700D2405 /* CollapsibleRoomTimelineView.swift */; };
Expand Down Expand Up @@ -1412,6 +1413,7 @@
3FFDA99C98BE05F43A92343B /* test_pdf.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = test_pdf.pdf; sourceTree = "<group>"; };
40076C770A5FB83325252973 /* VoiceMessageMediaManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManager.swift; sourceTree = "<group>"; };
40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModelTests.swift; sourceTree = "<group>"; };
406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallServiceConstants.swift; sourceTree = "<group>"; };
40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
4100DDE6BF3C566AB66B80CC /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = "<group>"; };
4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreen.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4097,6 +4099,7 @@
isa = PBXGroup;
children = (
33AE897D86784CCA5E4E9227 /* ElementCallService.swift */,
406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */,
6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */,
309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */,
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */,
Expand Down Expand Up @@ -5720,7 +5723,7 @@
B5618E3C948584E5C1F67033 /* DTHTMLElement+AttributedStringBuilder.swift in Sources */,
DFCA89C4EC2A5332ED6B441F /* DataProtectionManager.swift in Sources */,
24A75F72EEB7561B82D726FD /* Date.swift in Sources */,
6B67AC7AA41136FC9804C136 /* ElementCallServiceProtocol.swift in Sources */,
9F11B9F347F9E2D236799FB3 /* ElementCallServiceConstants.swift in Sources */,
CFEC53440C572CEEABC4A6A0 /* ElementXAttributeScope.swift in Sources */,
A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */,
59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */,
Expand Down Expand Up @@ -6082,6 +6085,7 @@
AE1160076F663BF14E0E893A /* EffectsView.swift in Sources */,
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */,
5732395A4F71F51F9C754C5A /* ElementCallService.swift in Sources */,
3895969759E68FAB90C63EF7 /* ElementCallServiceConstants.swift in Sources */,
8E7A902CA16E24928F83646C /* ElementCallServiceMock.swift in Sources */,
48416BBEB8DDF3E4DED0EDB6 /* ElementCallServiceProtocol.swift in Sources */,
07CC13C5729C24255348CBBD /* ElementCallWidgetDriver.swift in Sources */,
Expand Down
9 changes: 9 additions & 0 deletions ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
didSet {
userSessionObserver?.cancel()
if userSession != nil {
configureElementCallService()
configureNotificationManager()
observeUserSessionChanges()
startSync()
Expand Down Expand Up @@ -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)
Expand Down
76 changes: 76 additions & 0 deletions ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
58 changes: 57 additions & 1 deletion ElementX/Sources/Services/ElementCall/ElementCallService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
18 changes: 11 additions & 7 deletions ElementX/Sources/Services/Room/RoomProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 }
Expand Down
2 changes: 2 additions & 0 deletions ElementX/Sources/Services/Room/RoomProxyProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion NSE/SupportingFiles/target.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,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/Services/ElementCall/ElementCallServiceConstants.swift
- path: ../../ElementX/Sources/Application/AppSettings.swift

0 comments on commit 8c1f4a7

Please sign in to comment.