refactor for CallKit

This commit is contained in:
ryanzhao 2021-11-08 09:12:18 +11:00
parent 6f78d6dfbe
commit bef20e2f9a
10 changed files with 170 additions and 40 deletions

View File

@ -148,6 +148,8 @@
7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; };
7B7CB192271508AD0079FF93 /* Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* Vibration.swift */; };
7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; };
7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */; };
7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */; };
7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; };
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; };
@ -1133,6 +1135,8 @@
7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = "<group>"; };
7B7CB191271508AD0079FF93 /* Vibration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vibration.swift; sourceTree = "<group>"; };
7BA68908272A27BE00EFC32F /* SessionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCall.swift; sourceTree = "<group>"; };
7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXCallController.swift"; sourceTree = "<group>"; };
7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXProvider.swift"; sourceTree = "<group>"; };
7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; };
7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -2071,8 +2075,10 @@
7BA68907272A279900EFC32F /* Call Management */ = {
isa = PBXGroup;
children = (
7BC707F127290ACB002817AD /* SessionCallManager.swift */,
7BA68908272A27BE00EFC32F /* SessionCall.swift */,
7BC707F127290ACB002817AD /* SessionCallManager.swift */,
7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */,
7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */,
);
path = "Call Management";
sourceTree = "<group>";
@ -4894,6 +4900,7 @@
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */,
346129991FD1E4DA00532771 /* SignalApp.m in Sources */,
3496957121A301A100DCFE74 /* OWSBackupImportJob.m in Sources */,
7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */,
34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */,
C331FFFE2558FF3B00070591 /* ConversationCell.swift in Sources */,
B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */,
@ -4908,6 +4915,7 @@
B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */,
B821494625D4D6FF009C0F2A /* URLModal.swift in Sources */,
B877E24226CA12910007970A /* CallVC.swift in Sources */,
7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */,
C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */,
B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */,
4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */,

View File

@ -1,6 +1,7 @@
import Foundation
import WebRTC
import SessionMessagingKit
import PromiseKit
public final class SessionCall: NSObject, WebRTCSessionDelegate {
// MARK: Metadata Properties
@ -147,14 +148,18 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
// MARK: Actions
func startSessionCall(completion: (() -> Void)?) {
guard case .offer = mode else { return }
AppEnvironment.shared.callManager.reportOutgoingCall(self)
Storage.write { transaction in
self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done {
self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).done {
self.hasStartedConnecting = true
}.retainUntilComplete()
}.retainUntilComplete()
}
var promise: Promise<Void>!
Storage.write(with: { transaction in
promise = self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction)
}, completion: { [weak self] in
let _ = promise.done {
Storage.shared.write { transaction in
self?.webRTCSession.sendOffer(to: self!.sessionID, using: transaction as! YapDatabaseReadWriteTransaction).done {
self?.hasStartedConnecting = true
}.retainUntilComplete()
}
}
})
completion?()
}
@ -175,7 +180,6 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate {
self.webRTCSession.endCall(with: self.sessionID, using: transaction)
}
hasEnded = true
AppEnvironment.shared.callManager.reportCurrentCallEnded()
}
// MARK: Renderer

View File

@ -0,0 +1,47 @@
import CallKit
import SessionUtilitiesKit
extension SessionCallManager {
public func startCall(_ call: SessionCall, completion: (() -> Void)?) {
guard case .offer = call.mode else { return }
let handle = CXHandle(type: .generic, value: call.sessionID)
let startCallAction = CXStartCallAction(call: call.uuid, handle: handle)
startCallAction.isVideo = false
let transaction = CXTransaction()
transaction.addAction(startCallAction)
reportOutgoingCall(call)
requestTransaction(transaction)
completion?()
}
public func endCall(_ call: SessionCall, completion: (() -> Void)?) {
let endCallAction = CXEndCallAction(call: call.uuid)
let transaction = CXTransaction()
transaction.addAction(endCallAction)
requestTransaction(transaction)
completion?()
}
// Not currently in use
public func setOnHoldStatus(for call: SessionCall) {
let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: true)
let transaction = CXTransaction()
transaction.addAction(setHeldCallAction)
requestTransaction(transaction)
}
private func requestTransaction(_ transaction: CXTransaction) {
callController.request(transaction) { error in
if let error = error {
SNLog("Error requesting transaction: \(error)")
} else {
SNLog("Requested transaction successfully")
}
}
}
}

View File

@ -0,0 +1,44 @@
import CallKit
extension SessionCallManager: CXProviderDelegate {
public func providerDidReset(_ provider: CXProvider) {
AssertIsOnMainThread()
currentCall?.endSessionCall()
}
public func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
AssertIsOnMainThread()
guard let call = self.currentCall else { return action.fail() }
call.startSessionCall(completion: nil)
action.fulfill()
}
public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
AssertIsOnMainThread()
guard let _ = self.currentCall else { return action.fail() }
let userDefaults = UserDefaults.standard
if userDefaults[.hasSeenCallIPExposureWarning] {
showCallVC()
} else {
showCallModal()
}
action.fulfill()
}
public func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
AssertIsOnMainThread()
guard let call = self.currentCall else { return action.fail() }
call.endSessionCall()
reportCurrentCallEnded(reason: nil)
action.fulfill()
}
public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
// TODO: set on hold
}
public func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
// TODO: handle timeout
}
}

View File

@ -1,8 +1,9 @@
import CallKit
import SessionMessagingKit
public final class SessionCallManager: NSObject, CXProviderDelegate {
private let provider: CXProvider
public final class SessionCallManager: NSObject {
let provider: CXProvider
let callController = CXCallController()
var currentCall: SessionCall?
private static var _sharedProvider: CXProvider?
@ -43,11 +44,6 @@ public final class SessionCallManager: NSObject, CXProviderDelegate {
self.provider.setDelegate(self, queue: nil)
}
public func providerDidReset(_ provider: CXProvider) {
AssertIsOnMainThread()
currentCall?.endSessionCall()
}
public func reportOutgoingCall(_ call: SessionCall) {
AssertIsOnMainThread()
self.currentCall = call
@ -82,8 +78,14 @@ public final class SessionCallManager: NSObject, CXProviderDelegate {
}
}
public func reportCurrentCallEnded() {
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
@ -99,4 +101,27 @@ public final class SessionCallManager: NSObject, CXProviderDelegate {
// 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)
}
}

View File

@ -194,7 +194,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate {
if shouldRestartCamera { cameraManager.prepare() }
touch(call.videoCapturer)
titleLabel.text = self.call.contactName
self.call.startSessionCall{
AppEnvironment.shared.callManager.startCall(call) {
self.callInfoLabel.text = "Ringing..."
self.answerButton.isHidden = true
}
@ -319,7 +319,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate {
}
@objc private func endCall() {
self.call.endSessionCall()
AppEnvironment.shared.callManager.endCall(call, completion: nil)
}
@objc private func minimize() {

View File

@ -155,8 +155,9 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate {
}
@objc private func endCall() {
self.call.endSessionCall()
self.dismiss()
AppEnvironment.shared.callManager.endCall(call) {
self.dismiss()
}
}
public func showCallVC(answer: Bool) {

View File

@ -8,20 +8,24 @@ extension AppDelegate {
// MARK: Call handling
func createNewIncomingCall(caller: String, uuid: String) {
let call = SessionCall(for: caller, uuid: uuid, mode: .answer)
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller {
let callVC = CallVC(for: call)
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
conversationVC.inputAccessoryView?.alpha = 0
presentingVC.present(callVC, animated: true, completion: nil)
} else {
call.reportIncomingCallIfNeeded{ error in
if let error = error {
SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)")
let incomingCallBanner = IncomingCallBanner(for: call)
incomingCallBanner.show()
if CurrentAppContext().isMainAppAndActive {
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == caller {
DispatchQueue.main.async {
let callVC = CallVC(for: call)
callVC.conversationVC = conversationVC
conversationVC.inputAccessoryView?.isHidden = true
conversationVC.inputAccessoryView?.alpha = 0
presentingVC.present(callVC, animated: true, completion: nil)
}
return
}
}
call.reportIncomingCallIfNeeded{ error in
if let error = error {
SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)")
let incomingCallBanner = IncomingCallBanner(for: call)
incomingCallBanner.show()
}
}
}
@ -53,8 +57,7 @@ extension AppDelegate {
if let currentBanner = IncomingCallBanner.current { currentBanner.dismiss() }
if let callVC = CurrentAppContext().frontmostViewController() as? CallVC { callVC.handleEndCallMessage(message) }
if let miniCallView = MiniCallView.current { miniCallView.dismiss() }
WebRTCSession.current?.dropConnection()
WebRTCSession.current = nil
AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: .remoteEnded)
}
}
}

View File

@ -11,7 +11,7 @@ extension WebRTCSession {
print("[Calls] Received remote SDP: \(sdp.sdp).")
peerConnection.setRemoteDescription(sdp, completionHandler: { [weak self] error in
if let error = error {
SNLog("Couldn't set SDP due to error: \(error).")
SNLog("[Calls] Couldn't set SDP due to error: \(error).")
} else {
guard let self = self,
sdp.type == .offer, self.peerConnection.localDescription == nil else { return }

View File

@ -227,8 +227,6 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
message.kind = .endCall
print("[Calls] Sending end call message.")
MessageSender.sendNonDurably(message, in: thread, using: transaction).retainUntilComplete()
dropConnection()
WebRTCSession.current = nil
}
public func dropConnection() {