//// //// Copyright (c) 2019 Open Whisper Systems. All rights reserved. //// // //import Foundation //import PromiseKit //import CallKit //import SignalUtilitiesKit //import SignalUtilitiesKit //import WebRTC // //protocol CallUIAdaptee { // var notificationPresenter: NotificationPresenter { get } // var callService: CallService { get } // var hasManualRinger: Bool { get } // // func startOutgoingCall(handle: String) -> SignalCall // func reportIncomingCall(_ call: SignalCall, callerName: String) // func reportMissedCall(_ call: SignalCall, callerName: String) // func answerCall(localId: UUID) // func answerCall(_ call: SignalCall) // func declineCall(localId: UUID) // func declineCall(_ call: SignalCall) // func recipientAcceptedCall(_ call: SignalCall) // func localHangupCall(_ call: SignalCall) // func remoteDidHangupCall(_ call: SignalCall) // func remoteBusy(_ call: SignalCall) // func failCall(_ call: SignalCall, error: CallError) // func setIsMuted(call: SignalCall, isMuted: Bool) // func setHasLocalVideo(call: SignalCall, hasLocalVideo: Bool) // func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) //} // //// Shared default implementations //extension CallUIAdaptee { // internal func showCall(_ call: SignalCall) { // AssertIsOnMainThread() // // let callViewController = CallViewController(call: call) // callViewController.modalTransitionStyle = .crossDissolve // // if CallViewController.kShowCallViewOnSeparateWindow { // OWSWindowManager.shared().startCall(callViewController) // } else { // guard let presentingViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts else { // owsFailDebug("view controller unexpectedly nil") // return // } // // if let presentedViewController = presentingViewController.presentedViewController { // presentedViewController.dismiss(animated: false) { // presentingViewController.present(callViewController, animated: true) // } // } else { // presentingViewController.present(callViewController, animated: true) // } // } // } // // internal func reportMissedCall(_ call: SignalCall, callerName: String) { // AssertIsOnMainThread() // // notificationPresenter.presentMissedCall(call, callerName: callerName) // } // // internal func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) { // AssertIsOnMainThread() // // guard self.callService.call == nil else { // owsFailDebug("unexpectedly found an existing call when trying to start outgoing call: \(recipientId)") // return // } // // let call = self.startOutgoingCall(handle: recipientId) // call.hasLocalVideo = hasLocalVideo // self.showCall(call) // } //} // ///** // * Notify the user of call related activities. // * Driven by either a CallKit or System notifications adaptee // */ //@objc public class CallUIAdapter: NSObject, CallServiceObserver { // // private let adaptee: CallUIAdaptee // private let contactsManager: OWSContactsManager // internal let audioService: CallAudioService // internal let callService: CallService // // public required init(callService: CallService, contactsManager: OWSContactsManager, notificationPresenter: NotificationPresenter) { // AssertIsOnMainThread() // // self.contactsManager = contactsManager // self.callService = callService // // if Platform.isSimulator { // // CallKit doesn't seem entirely supported in simulator. // // e.g. you can't receive calls in the call screen. // // So we use the non-CallKit call UI. // Logger.info("choosing non-callkit adaptee for simulator.") // adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationPresenter: notificationPresenter) // } else if CallUIAdapter.isCallkitDisabledForLocale { // Logger.info("choosing non-callkit adaptee due to locale.") // adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationPresenter: notificationPresenter) // } else if #available(iOS 11, *) { // Logger.info("choosing callkit adaptee for iOS11+") // let showNames = Environment.shared.preferences.notificationPreviewType() != .noNameNoPreview // let useSystemCallLog = Environment.shared.preferences.isSystemCallLogEnabled() // // adaptee = CallKitCallUIAdaptee(callService: callService, contactsManager: contactsManager, notificationPresenter: notificationPresenter, showNamesOnCallScreen: showNames, useSystemCallLog: useSystemCallLog) // } else if #available(iOS 10.0, *), Environment.shared.preferences.isCallKitEnabled() { // Logger.info("choosing callkit adaptee for iOS10") // let hideNames = Environment.shared.preferences.isCallKitPrivacyEnabled() || Environment.shared.preferences.notificationPreviewType() == .noNameNoPreview // let showNames = !hideNames // // // All CallKit calls use the system call log on iOS10 // let useSystemCallLog = true // // adaptee = CallKitCallUIAdaptee(callService: callService, contactsManager: contactsManager, notificationPresenter: notificationPresenter, showNamesOnCallScreen: showNames, useSystemCallLog: useSystemCallLog) // } else { // Logger.info("choosing non-callkit adaptee") // adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationPresenter: notificationPresenter) // } // // audioService = CallAudioService(handleRinging: adaptee.hasManualRinger) // // super.init() // // // We cannot assert singleton here, because this class gets rebuilt when the user changes relevant call settings // // callService.addObserverAndSyncState(observer: self) // } // // @objc // public static var isCallkitDisabledForLocale: Bool { // let locale = Locale.current // guard let regionCode = locale.regionCode else { // owsFailDebug("Missing region code.") // return false // } // // // Apple has stopped approving apps that use CallKit functionality in mainland China. // // When the "CN" region is enabled, this check simply switches to the same pre-CallKit // // interface that is still used by everyone on iOS 9. // // // // For further reference: https://forums.developer.apple.com/thread/103083 // return regionCode == "CN" // } // // // MARK: Dependencies // // var audioSession: OWSAudioSession { // return Environment.shared.audioSession // } // // // MARK: // // internal func reportIncomingCall(_ call: SignalCall, thread: TSContactThread) { // AssertIsOnMainThread() // // // make sure we don't terminate audio session during call // _ = audioSession.startAudioActivity(call.audioActivity) // // let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber) // adaptee.reportIncomingCall(call, callerName: callerName) // } // // internal func reportMissedCall(_ call: SignalCall) { // AssertIsOnMainThread() // // let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber) // adaptee.reportMissedCall(call, callerName: callerName) // } // // internal func startOutgoingCall(handle: String) -> SignalCall { // AssertIsOnMainThread() // // let call = adaptee.startOutgoingCall(handle: handle) // return call // } // // @objc public func answerCall(localId: UUID) { // AssertIsOnMainThread() // // adaptee.answerCall(localId: localId) // } // // internal func answerCall(_ call: SignalCall) { // AssertIsOnMainThread() // // adaptee.answerCall(call) // } // // @objc public func declineCall(localId: UUID) { // AssertIsOnMainThread() // // adaptee.declineCall(localId: localId) // } // // internal func declineCall(_ call: SignalCall) { // AssertIsOnMainThread() // // adaptee.declineCall(call) // } // // internal func didTerminateCall(_ call: SignalCall?) { // AssertIsOnMainThread() // // if let call = call { // self.audioSession.endAudioActivity(call.audioActivity) // } // } // // @objc public func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) { // AssertIsOnMainThread() // // adaptee.startAndShowOutgoingCall(recipientId: recipientId, hasLocalVideo: hasLocalVideo) // } // // internal func recipientAcceptedCall(_ call: SignalCall) { // AssertIsOnMainThread() // // adaptee.recipientAcceptedCall(call) // } // // internal func remoteDidHangupCall(_ call: SignalCall) { // AssertIsOnMainThread() // // adaptee.remoteDidHangupCall(call) // } // // internal func remoteBusy(_ call: SignalCall) { // AssertIsOnMainThread() // // adaptee.remoteBusy(call) // } // // internal func localHangupCall(_ call: SignalCall) { // AssertIsOnMainThread() // // adaptee.localHangupCall(call) // } // // internal func failCall(_ call: SignalCall, error: CallError) { // AssertIsOnMainThread() // // adaptee.failCall(call, error: error) // } // // internal func showCall(_ call: SignalCall) { // AssertIsOnMainThread() // // adaptee.showCall(call) // } // // internal func setIsMuted(call: SignalCall, isMuted: Bool) { // AssertIsOnMainThread() // // // With CallKit, muting is handled by a CXAction, so it must go through the adaptee // adaptee.setIsMuted(call: call, isMuted: isMuted) // } // // internal func setHasLocalVideo(call: SignalCall, hasLocalVideo: Bool) { // AssertIsOnMainThread() // // adaptee.setHasLocalVideo(call: call, hasLocalVideo: hasLocalVideo) // } // // internal func setAudioSource(call: SignalCall, audioSource: AudioSource?) { // AssertIsOnMainThread() // // // AudioSource is not handled by CallKit (e.g. there is no CXAction), so we handle it w/o going through the // // adaptee, relying on the AudioService CallObserver to put the system in a state consistent with the call's // // assigned property. // call.audioSource = audioSource // } // // internal func setCameraSource(call: SignalCall, isUsingFrontCamera: Bool) { // AssertIsOnMainThread() // // callService.setCameraSource(call: call, isUsingFrontCamera: isUsingFrontCamera) // } // // // CallKit handles ringing state on it's own. But for non-call kit we trigger ringing start/stop manually. // internal var hasManualRinger: Bool { // AssertIsOnMainThread() // // return adaptee.hasManualRinger // } // // // MARK: - CallServiceObserver // // internal func didUpdateCall(call: SignalCall?) { // AssertIsOnMainThread() // // call?.addObserverAndSyncState(observer: audioService) // } // // internal func didUpdateVideoTracks(call: SignalCall?, // localCaptureSession: AVCaptureSession?, // remoteVideoTrack: RTCVideoTrack?) { // AssertIsOnMainThread() // // audioService.didUpdateVideoTracks(call: call) // } //}