session-ios/Signal/src/call/UserInterface/CallUIAdapter.swift

273 lines
9.7 KiB
Swift

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
import CallKit
import SignalServiceKit
import SignalMessaging
import WebRTC
protocol CallUIAdaptee {
var notificationsAdapter: CallNotificationsAdapter { 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)
}
// Shared default implementations
extension CallUIAdaptee {
internal func showCall(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
let callViewController = CallViewController(call: call)
callViewController.modalTransitionStyle = .crossDissolve
if CallViewController.kShowCallViewOnSeparateWindow {
OWSWindowManager.shared().startCall(callViewController)
} else {
guard let presentingViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts else {
owsFail("in \(#function) 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) {
SwiftAssertIsOnMainThread(#function)
notificationsAdapter.presentMissedCall(call, callerName: callerName)
}
internal func startAndShowOutgoingCall(recipientId: String) {
SwiftAssertIsOnMainThread(#function)
guard self.callService.call == nil else {
Logger.info("unexpectedly found an existing call when trying to start outgoing call: \(recipientId)")
//TODO terminate existing call.
return
}
let call = self.startOutgoingCall(handle: recipientId)
self.showCall(call)
}
}
/**
* Notify the user of call related activities.
* Driven by either a CallKit or System notifications adaptee
*/
@objc class CallUIAdapter: NSObject, CallServiceObserver {
let TAG = "[CallUIAdapter]"
private let adaptee: CallUIAdaptee
private let contactsManager: OWSContactsManager
internal let audioService: CallAudioService
required init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: CallNotificationsAdapter) {
SwiftAssertIsOnMainThread(#function)
self.contactsManager = contactsManager
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("\(TAG) choosing non-callkit adaptee for simulator.")
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter)
} else if #available(iOS 11, *) {
Logger.info("\(TAG) choosing callkit adaptee for iOS11+")
let showNames = Environment.preferences().notificationPreviewType() != .noNameNoPreview
let useSystemCallLog = Environment.preferences().isSystemCallLogEnabled()
adaptee = CallKitCallUIAdaptee(callService: callService, contactsManager: contactsManager, notificationsAdapter: notificationsAdapter, showNamesOnCallScreen: showNames, useSystemCallLog: useSystemCallLog)
} else if #available(iOS 10.0, *), Environment.current().preferences.isCallKitEnabled() {
Logger.info("\(TAG) choosing callkit adaptee for iOS10")
let hideNames = Environment.preferences().isCallKitPrivacyEnabled() || Environment.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, notificationsAdapter: notificationsAdapter, showNamesOnCallScreen: showNames, useSystemCallLog: useSystemCallLog)
} else {
Logger.info("\(TAG) choosing non-callkit adaptee")
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter)
}
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)
}
internal func reportIncomingCall(_ call: SignalCall, thread: TSContactThread) {
SwiftAssertIsOnMainThread(#function)
// make sure we don't terminate audio session during call
OWSAudioSession.shared.startAudioActivity(call.audioActivity)
let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber)
adaptee.reportIncomingCall(call, callerName: callerName)
}
internal func reportMissedCall(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
let callerName = self.contactsManager.displayName(forPhoneIdentifier: call.remotePhoneNumber)
adaptee.reportMissedCall(call, callerName: callerName)
}
internal func startOutgoingCall(handle: String) -> SignalCall {
SwiftAssertIsOnMainThread(#function)
let call = adaptee.startOutgoingCall(handle: handle)
return call
}
internal func answerCall(localId: UUID) {
SwiftAssertIsOnMainThread(#function)
adaptee.answerCall(localId: localId)
}
internal func answerCall(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
adaptee.answerCall(call)
}
internal func declineCall(localId: UUID) {
SwiftAssertIsOnMainThread(#function)
adaptee.declineCall(localId: localId)
}
internal func declineCall(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
adaptee.declineCall(call)
}
internal func didTerminateCall(_ call: SignalCall?) {
SwiftAssertIsOnMainThread(#function)
if let call = call {
OWSAudioSession.shared.endAudioActivity(call.audioActivity)
}
}
internal func startAndShowOutgoingCall(recipientId: String) {
SwiftAssertIsOnMainThread(#function)
adaptee.startAndShowOutgoingCall(recipientId: recipientId)
}
internal func recipientAcceptedCall(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
adaptee.recipientAcceptedCall(call)
}
internal func remoteDidHangupCall(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
adaptee.remoteDidHangupCall(call)
}
internal func remoteBusy(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
adaptee.remoteBusy(call)
}
internal func localHangupCall(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
adaptee.localHangupCall(call)
}
internal func failCall(_ call: SignalCall, error: CallError) {
SwiftAssertIsOnMainThread(#function)
adaptee.failCall(call, error: error)
}
internal func showCall(_ call: SignalCall) {
SwiftAssertIsOnMainThread(#function)
adaptee.showCall(call)
}
internal func setIsMuted(call: SignalCall, isMuted: Bool) {
SwiftAssertIsOnMainThread(#function)
// 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) {
SwiftAssertIsOnMainThread(#function)
adaptee.setHasLocalVideo(call: call, hasLocalVideo: hasLocalVideo)
}
internal func setAudioSource(call: SignalCall, audioSource: AudioSource?) {
SwiftAssertIsOnMainThread(#function)
// 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
}
// CallKit handles ringing state on it's own. But for non-call kit we trigger ringing start/stop manually.
internal var hasManualRinger: Bool {
SwiftAssertIsOnMainThread(#function)
return adaptee.hasManualRinger
}
// MARK: - CallServiceObserver
internal func didUpdateCall(call: SignalCall?) {
SwiftAssertIsOnMainThread(#function)
call?.addObserverAndSyncState(observer: audioService)
}
internal func didUpdateVideoTracks(call: SignalCall?,
localVideoTrack: RTCVideoTrack?,
remoteVideoTrack: RTCVideoTrack?) {
SwiftAssertIsOnMainThread(#function)
audioService.didUpdateVideoTracks(call: call)
}
}