diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 50a2a414d..fef6abe60 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -2,6 +2,7 @@ import Foundation import WebRTC import SessionMessagingKit import PromiseKit +import CallKit public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Metadata Properties @@ -13,6 +14,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { var remoteSDP: RTCSessionDescription? = nil var callMessageTimestamp: UInt64? var isWaitingForRemoteSDP = false + var answerCallAction: CXAnswerCallAction? = nil var contactName: String { let contact = Storage.shared.getContact(with: self.sessionID) return contact?.displayName(for: Contact.Context.regular) ?? self.sessionID @@ -181,8 +183,9 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { }) } - func answerSessionCall() { + func answerSessionCall(action: CXAnswerCallAction) { guard case .answer = mode else { return } + answerCallAction = action hasStartedConnecting = true if let sdp = remoteSDP { webRTCSession.handleRemoteSDP(sdp, from: sessionID) // This sends an answer message internally @@ -249,6 +252,7 @@ public final class SessionCall: NSObject, WebRTCSessionDelegate { // MARK: Delegate public func webRTCIsConnected() { self.hasConnected = true + self.answerCallAction?.fulfill() } public func isRemoteVideoDidChange(isEnabled: Bool) { diff --git a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift index 62607e08b..94a3e6b04 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXCallController.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXCallController.swift @@ -4,6 +4,7 @@ import SessionUtilitiesKit 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.uuid, handle: handle) diff --git a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift index 23d69e6bc..3a9344ed0 100644 --- a/Session/Calls/Call Management/SessionCallManager+CXProvider.swift +++ b/Session/Calls/Call Management/SessionCallManager+CXProvider.swift @@ -15,18 +15,22 @@ extension SessionCallManager: CXProviderDelegate { public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { AssertIsOnMainThread() + print("[CallKit] Perform CXAnswerCallAction") guard let call = self.currentCall else { return action.fail() } - if let _ = CurrentAppContext().frontmostViewController() as? CallVC { - call.answerSessionCall() - } else { - let userDefaults = UserDefaults.standard - if userDefaults[.hasSeenCallIPExposureWarning] { - showCallVC() + if CurrentAppContext().isMainAppAndActive { + if let _ = CurrentAppContext().frontmostViewController() as? CallVC { + call.answerSessionCall(action: action) } else { - showCallModal() + let userDefaults = UserDefaults.standard + if userDefaults[.hasSeenCallIPExposureWarning] { + showCallVC() + } else { + showCallModal() + } } + } else { + call.answerSessionCall(action: action) } - action.fulfill() } public func provider(_ provider: CXProvider, perform action: CXEndCallAction) { @@ -37,6 +41,14 @@ extension SessionCallManager: CXProviderDelegate { 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() + } + public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) { // TODO: set on hold } diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index ba35b2379..826e37afa 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -49,7 +49,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var minimizeButton: UIButton = { let result = UIButton(type: .custom) - result.isHidden = true + result.isHidden = !call.hasConnected let image = UIImage(named: "Minimize")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -60,6 +60,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { private lazy var answerButton: UIButton = { let result = UIButton(type: .custom) + result.isHidden = call.hasConnected let image = UIImage(named: "AnswerCall")!.withTint(.white) result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) @@ -108,7 +109,7 @@ final class CallVC : UIViewController, VideoPreviewDelegate { result.setImage(image, for: UIControl.State.normal) result.set(.width, to: 60) result.set(.height, to: 60) - result.backgroundColor = UIColor(hex: 0x1F1F1F) + result.backgroundColor = call.isMuted ? Colors.destructive : UIColor(hex: 0x1F1F1F) result.layer.cornerRadius = 30 result.addTarget(self, action: #selector(switchAudio), for: UIControl.Event.touchUpInside) return result diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index dfab8a3b3..1da921767 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -407,6 +407,7 @@ static NSTimeInterval launchStartedAt; if (CurrentAppContext().isMainApp) { [SNJobQueue.shared resumePendingJobs]; [self syncConfigurationIfNeeded]; + [self handleAppActivatedWithOngoingCallIfNeeded]; } }); } diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 20a92a884..2669cd533 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -6,6 +6,20 @@ import UIKit extension AppDelegate { // MARK: Call handling + @objc func handleAppActivatedWithOngoingCallIfNeeded() { + guard let call = AppEnvironment.shared.callManager.currentCall else { return } + if let callVC = CurrentAppContext().frontmostViewController() as? CallVC, callVC.call == call { return } + guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully + let callVC = CallVC(for: call) + if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == call.sessionID { + callVC.conversationVC = conversationVC + conversationVC.inputAccessoryView?.isHidden = true + conversationVC.inputAccessoryView?.alpha = 0 + } + presentingVC.present(callVC, animated: true, completion: nil) + + } + @objc func setUpCallHandling() { // Pre offer messages MessageReceiver.handlePreOfferCallMessage = { message in diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 3d30cf816..b2299e23c 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -243,6 +243,10 @@ public enum PushRegistrationError: Error { if let uuid = payload["uuid"] as? String, let caller = payload["caller"] as? String, let timestamp = payload["timestamp"] as? UInt64 { let call = SessionCall(for: caller, uuid: uuid, mode: .answer) call.callMessageTimestamp = timestamp + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.startPollerIfNeeded() + appDelegate.startClosedGroupPoller() + appDelegate.startOpenGroupPollersIfNeeded() call.reportIncomingCallIfNeeded { error in if let error = error { SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)")