session-ios/Session/Calls/Call Management/SessionCallManager.swift
2021-11-08 09:12:18 +11:00

128 lines
5.1 KiB
Swift

import CallKit
import SessionMessagingKit
public final class SessionCallManager: NSObject {
let provider: CXProvider
let callController = CXCallController()
var currentCall: SessionCall?
private static var _sharedProvider: CXProvider?
class func sharedProvider(useSystemCallLog: Bool) -> CXProvider {
let configuration = buildProviderConfiguration(useSystemCallLog: useSystemCallLog)
if let sharedProvider = self._sharedProvider {
sharedProvider.configuration = configuration
return sharedProvider
} else {
SwiftSingletons.register(self)
let provider = CXProvider(configuration: configuration)
_sharedProvider = provider
return provider
}
}
class func buildProviderConfiguration(useSystemCallLog: Bool) -> CXProviderConfiguration {
let localizedName = NSLocalizedString("APPLICATION_NAME", comment: "Name of application")
let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)
providerConfiguration.supportsVideo = true
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.generic]
let iconMaskImage = #imageLiteral(resourceName: "SessionGreen32")
providerConfiguration.iconTemplateImageData = iconMaskImage.pngData()
providerConfiguration.includesCallsInRecents = useSystemCallLog
return providerConfiguration
}
init(useSystemCallLog: Bool = false) {
AssertIsOnMainThread()
self.provider = type(of: self).sharedProvider(useSystemCallLog: useSystemCallLog)
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)
}
public func reportOutgoingCall(_ call: SessionCall) {
AssertIsOnMainThread()
self.currentCall = call
call.hasStartedConnectingDidChange = {
self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate)
}
call.hasConnectedDidChange = {
self.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedDate)
}
}
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.uuid.uuidString)
update.hasVideo = true
disableUnsupportedFeatures(callUpdate: update)
// Report the incoming call to the system
self.provider.reportNewIncomingCall(with: call.uuid, update: update) { error in
guard error == nil else {
completion(error)
Logger.error("failed to report new incoming call, error: \(error!)")
return
}
self.currentCall = call
completion(nil)
}
}
public func reportCurrentCallEnded(reason: CXCallEndedReason?) {
guard let call = currentCall else { return }
if let reason = reason {
self.provider.reportCall(with: call.uuid, endedAt: nil, reason: reason)
}
self.currentCall?.webRTCSession.dropConnection()
self.currentCall = nil
WebRTCSession.current = nil
}
// MARK: Util
private func disableUnsupportedFeatures(callUpdate: CXCallUpdate) {
// Call Holding is failing to restart audio when "swapping" calls on the CallKit screen
// until user returns to in-app call screen.
callUpdate.supportsHolding = false
// Not yet supported
callUpdate.supportsGrouping = false
callUpdate.supportsUngrouping = false
// Is there any reason to support this?
callUpdate.supportsDTMF = false
}
internal func showCallModal() {
let callModal = CallModal() { [weak self] in
self?.showCallVC()
}
callModal.modalPresentationStyle = .overFullScreen
callModal.modalTransitionStyle = .crossDissolve
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
presentingVC.present(callModal, animated: true, completion: nil)
}
internal func showCallVC() {
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
let callVC = CallVC(for: self.currentCall!)
callVC.shouldAnswer = true
if let conversationVC = presentingVC as? ConversationVC {
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
conversationVC.inputAccessoryView?.alpha = 0
}
presentingVC.present(callVC, animated: true, completion: nil)
}
}