diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 7b84ba567..73535a7cc 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -128,6 +128,7 @@ 7B0EFDF2275449AA00FFAAE7 /* TSInfoMessage+Calls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */; }; 7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */; }; 7B0EFDF62755CC5400FFAAE7 /* CallMissedTipsModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0EFDF52755CC5400FFAAE7 /* CallMissedTipsModal.swift */; }; + 7B13E1E92810F01300BD4F64 /* SessionCallManager+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B13E1E82810F01300BD4F64 /* SessionCallManager+Action.swift */; }; 7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; }; 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; }; 7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; }; @@ -1115,6 +1116,7 @@ 7B0EFDF1275449AA00FFAAE7 /* TSInfoMessage+Calls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+Calls.swift"; sourceTree = ""; }; 7B0EFDF3275490EA00FFAAE7 /* ringing.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringing.mp3; sourceTree = ""; }; 7B0EFDF52755CC5400FFAAE7 /* CallMissedTipsModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMissedTipsModal.swift; sourceTree = ""; }; + 7B13E1E82810F01300BD4F64 /* SessionCallManager+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+Action.swift"; sourceTree = ""; }; 7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = ""; }; 7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = ""; }; 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = ""; }; @@ -2098,6 +2100,7 @@ 7BC707F127290ACB002817AD /* SessionCallManager.swift */, 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */, 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */, + 7B13E1E82810F01300BD4F64 /* SessionCallManager+Action.swift */, ); path = "Call Management"; sourceTree = ""; @@ -4952,6 +4955,7 @@ 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */, B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */, B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */, + 7B13E1E92810F01300BD4F64 /* SessionCallManager+Action.swift in Sources */, C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */, 7B1581E827210ECC00848B49 /* RenderView.swift in Sources */, 7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCallManager+Action.swift b/Session/Calls/Call Management/SessionCallManager+Action.swift new file mode 100644 index 000000000..66e9814ea --- /dev/null +++ b/Session/Calls/Call Management/SessionCallManager+Action.swift @@ -0,0 +1,47 @@ +extension SessionCallManager { + @discardableResult + public func startCallAction() -> Bool { + guard let call = self.currentCall else { return false } + call.startSessionCall() + return true + } + + @discardableResult + public func answerCallAction() -> Bool { + guard let call = self.currentCall else { return false } + if let _ = CurrentAppContext().frontmostViewController() as? CallVC { + call.answerSessionCall() + } else { + guard let presentingVC = CurrentAppContext().frontmostViewController() else { return false } // FIXME: Handle more gracefully + let callVC = CallVC(for: self.currentCall!) + if let conversationVC = presentingVC as? ConversationVC { + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + } + presentingVC.present(callVC, animated: true) { + call.answerSessionCall() + } + } + return true + } + + @discardableResult + public func endCallAction() -> Bool { + guard let call = self.currentCall else { return false } + call.endSessionCall() + if call.didTimeout { + reportCurrentCallEnded(reason: .unanswered) + } else { + reportCurrentCallEnded(reason: nil) + } + return true + } + + @discardableResult + public func setMutedCallAction(isMuted: Bool) -> Bool { + guard let call = self.currentCall else { return false } + call.isMuted = isMuted + return true + } +} diff --git a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift index 4b83f2295..704a590e0 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift @@ -5,45 +5,62 @@ extension SessionCallManager { public func startCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { guard case .offer = call.mode else { return } guard !call.hasConnected else { return } - let handle = CXHandle(type: .generic, value: call.sessionID) - let startCallAction = CXStartCallAction(call: call.callID, handle: handle) - - startCallAction.isVideo = false - - let transaction = CXTransaction() - transaction.addAction(startCallAction) - reportOutgoingCall(call) - requestTransaction(transaction, completion: completion) + if callController != nil { + let handle = CXHandle(type: .generic, value: call.sessionID) + let startCallAction = CXStartCallAction(call: call.callID, handle: handle) + + startCallAction.isVideo = false + + let transaction = CXTransaction() + transaction.addAction(startCallAction) + + requestTransaction(transaction, completion: completion) + } else { + startCallAction() + completion?(nil) + } } public func answerCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { - let answerCallAction = CXAnswerCallAction(call: call.callID) - let transaction = CXTransaction() - transaction.addAction(answerCallAction) + if callController != nil { + let answerCallAction = CXAnswerCallAction(call: call.callID) + let transaction = CXTransaction() + transaction.addAction(answerCallAction) - requestTransaction(transaction, completion: completion) + requestTransaction(transaction, completion: completion) + } else { + answerCallAction() + completion?(nil) + } } public func endCall(_ call: SessionCall, completion: ((Error?) -> Void)?) { - let endCallAction = CXEndCallAction(call: call.callID) - let transaction = CXTransaction() - transaction.addAction(endCallAction) + if callController != nil { + let endCallAction = CXEndCallAction(call: call.callID) + let transaction = CXTransaction() + transaction.addAction(endCallAction) - requestTransaction(transaction, completion: completion) + requestTransaction(transaction, completion: completion) + } else { + endCallAction() + completion?(nil) + } } // Not currently in use public func setOnHoldStatus(for call: SessionCall) { - let setHeldCallAction = CXSetHeldCallAction(call: call.callID, onHold: true) - let transaction = CXTransaction() - transaction.addAction(setHeldCallAction) + if callController != nil { + let setHeldCallAction = CXSetHeldCallAction(call: call.callID, onHold: true) + let transaction = CXTransaction() + transaction.addAction(setHeldCallAction) - requestTransaction(transaction) + requestTransaction(transaction) + } } private func requestTransaction(_ transaction: CXTransaction, completion: ((Error?) -> Void)? = nil) { - callController.request(transaction) { error in + callController?.request(transaction) { error in if let error = error { SNLog("Error requesting transaction: \(error)") } else { diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index e0512066e..c66932788 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -8,9 +8,11 @@ extension SessionCallManager: CXProviderDelegate { public func provider(_ provider: CXProvider, perform action: CXStartCallAction) { AssertIsOnMainThread() - guard let call = self.currentCall else { return action.fail() } - call.startSessionCall() - action.fulfill() + if startCallAction() { + action.fulfill() + } else { + action.fail() + } } public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { @@ -18,21 +20,11 @@ extension SessionCallManager: CXProviderDelegate { print("[CallKit] Perform CXAnswerCallAction") guard let call = self.currentCall else { return action.fail() } if CurrentAppContext().isMainAppAndActive { - if let _ = CurrentAppContext().frontmostViewController() as? CallVC { - call.answerSessionCall() + if answerCallAction() { + action.fulfill() } else { - guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // FIXME: Handle more gracefully - let callVC = CallVC(for: self.currentCall!) - if let conversationVC = presentingVC as? ConversationVC { - callVC.conversationVC = conversationVC - conversationVC.inputAccessoryView?.isHidden = true - conversationVC.inputAccessoryView?.alpha = 0 - } - presentingVC.present(callVC, animated: true) { - call.answerSessionCall() - } + action.fail() } - action.fulfill() } else { call.answerSessionCallInBackground(action: action) } @@ -41,22 +33,21 @@ extension SessionCallManager: CXProviderDelegate { public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { print("[CallKit] Perform CXEndCallAction") AssertIsOnMainThread() - guard let call = self.currentCall else { return action.fail() } - call.endSessionCall() - if call.didTimeout { - reportCurrentCallEnded(reason: .unanswered) + if endCallAction() { + action.fulfill() } else { - reportCurrentCallEnded(reason: nil) + action.fail() } - action.fulfill() } public func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { print("[CallKit] Perform CXSetMutedCallAction, isMuted: \(action.isMuted)") AssertIsOnMainThread() - guard let call = self.currentCall else { return action.fail() } - call.isMuted = action.isMuted - action.fulfill() + if setMutedCallAction(isMuted: action.isMuted) { + action.fulfill() + } else { + action.fail() + } } public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) { diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index f7eaef6d4..2a2fa5313 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -2,8 +2,8 @@ import CallKit import SessionMessagingKit public final class SessionCallManager: NSObject { - let provider: CXProvider - let callController = CXCallController() + let provider: CXProvider? + let callController: CXCallController? var currentCall: SessionCall? = nil { willSet { if (newValue != nil) { @@ -49,12 +49,16 @@ public final class SessionCallManager: NSObject { init(useSystemCallLog: Bool = false) { AssertIsOnMainThread() - self.provider = type(of: self).sharedProvider(useSystemCallLog: useSystemCallLog) - + if SSKPreferences.isCallKitSupported { + self.provider = type(of: self).sharedProvider(useSystemCallLog: useSystemCallLog) + self.callController = CXCallController() + } else { + self.provider = nil + self.callController = nil + } super.init() - // We cannot assert singleton here, because this class gets rebuilt when the user changes relevant call settings - self.provider.setDelegate(self, queue: nil) + self.provider?.setDelegate(self, queue: nil) } // MARK: Report calls @@ -63,10 +67,10 @@ public final class SessionCallManager: NSObject { UserDefaults(suiteName: "group.com.loki-project.loki-messenger")?.set(true, forKey: "isCallOngoing") call.stateDidChange = { if call.hasStartedConnecting { - self.provider.reportOutgoingCall(with: call.callID, startedConnectingAt: call.connectingDate) + self.provider?.reportOutgoingCall(with: call.callID, startedConnectingAt: call.connectingDate) } if call.hasConnected { - self.provider.reportOutgoingCall(with: call.callID, connectedAt: call.connectedDate) + self.provider?.reportOutgoingCall(with: call.callID, connectedAt: call.connectedDate) } } } @@ -74,21 +78,26 @@ public final class SessionCallManager: NSObject { public func reportIncomingCall(_ call: SessionCall, callerName: String, completion: @escaping (Error?) -> Void) { AssertIsOnMainThread() - // Construct a CXCallUpdate describing the incoming call, including the caller. - let update = CXCallUpdate() - update.localizedCallerName = callerName - update.remoteHandle = CXHandle(type: .generic, value: call.callID.uuidString) - update.hasVideo = false + if let provider = provider { + // Construct a CXCallUpdate describing the incoming call, including the caller. + let update = CXCallUpdate() + update.localizedCallerName = callerName + update.remoteHandle = CXHandle(type: .generic, value: call.callID.uuidString) + update.hasVideo = false - disableUnsupportedFeatures(callUpdate: update) + disableUnsupportedFeatures(callUpdate: update) - // Report the incoming call to the system - self.provider.reportNewIncomingCall(with: call.callID, update: update) { error in - guard error == nil else { - self.reportCurrentCallEnded(reason: .failed) - completion(error) - return + // Report the incoming call to the system + provider.reportNewIncomingCall(with: call.callID, update: update) { error in + guard error == nil else { + self.reportCurrentCallEnded(reason: .failed) + completion(error) + return + } + UserDefaults(suiteName: "group.com.loki-project.loki-messenger")?.set(true, forKey: "isCallOngoing") + completion(nil) } + } else { UserDefaults(suiteName: "group.com.loki-project.loki-messenger")?.set(true, forKey: "isCallOngoing") completion(nil) } @@ -97,7 +106,7 @@ public final class SessionCallManager: NSObject { public func reportCurrentCallEnded(reason: CXCallEndedReason?) { guard let call = currentCall else { return } if let reason = reason { - self.provider.reportCall(with: call.callID, endedAt: nil, reason: reason) + self.provider?.reportCall(with: call.callID, endedAt: nil, reason: reason) switch (reason) { case .answeredElsewhere: call.updateCallMessage(mode: .answeredElsewhere) case .unanswered: call.updateCallMessage(mode: .unanswered) diff --git a/SessionMessagingKit/Database/SSKPreferences.swift b/SessionMessagingKit/Database/SSKPreferences.swift index 5897e08ce..25ab1ba7f 100644 --- a/SessionMessagingKit/Database/SSKPreferences.swift +++ b/SessionMessagingKit/Database/SSKPreferences.swift @@ -39,7 +39,7 @@ public class SSKPreferences: NSObject { } @objc - public static var isCallKitSupported() -> Bool { + public static var isCallKitSupported: Bool { let userLocale = NSLocale.current guard let regionCode = userLocale.regionCode else { return false }