2017-01-18 23:29:47 +01:00
//
2018-01-10 16:54:17 +01:00
// C o p y r i g h t ( c ) 2 0 1 8 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
2017-01-18 23:29:47 +01:00
//
2016-11-12 18:22:29 +01:00
import Foundation
import PromiseKit
import WebRTC
2017-11-28 00:17:46 +01:00
import SignalServiceKit
2017-12-01 16:48:18 +01:00
import SignalMessaging
2016-11-12 18:22:29 +01:00
/* *
2017-01-18 23:29:47 +01:00
* ` CallService ` is a global singleton that manages the state of WebRTC - backed Signal Calls
* ( as opposed to legacy " RedPhone Calls " ) .
2016-11-12 18:22:29 +01:00
*
2017-01-18 23:29:47 +01:00
* It serves as a connection between the ` CallUIAdapter ` and the ` PeerConnectionClient ` .
2017-01-06 14:40:13 +01:00
*
* # # Signaling
*
* Signaling refers to the setup and tear down of the connection . Before the connection is established , this must happen
2017-01-31 23:21:48 +01:00
* out of band ( using Signal Service ) , but once the connection is established it ' s possible to publish updates
2017-01-06 14:40:13 +01:00
* ( like hangup ) via the established channel .
*
2017-01-31 21:28:01 +01:00
* Signaling state is synchronized on the main thread and only mutated in the handleXXX family of methods .
2017-01-10 18:36:54 +01:00
*
* Following is a high level process of the exchange of messages that takes place during call signaling .
2017-01-06 14:40:13 +01:00
*
* # # # Key
*
* -- [ SOMETHING ] --> represents a message of type " Something " sent from the caller to the callee
* <-- [ SOMETHING ] -- represents a message of type " Something " sent from the callee to the caller
* SS : Message sent via Signal Service
* DC : Message sent via WebRTC Data Channel
*
* # # # Message Exchange / State Flow Overview
2016-11-12 18:22:29 +01:00
*
* | Caller | Callee |
* +----------------------------+-------------------------+
2017-01-10 18:36:54 +01:00
* Start outgoing call : ` handleOutgoingCall ` . . .
2017-01-31 23:21:48 +01:00
-- [ SS . CallOffer ] -->
2017-01-10 18:36:54 +01:00
* . . . and start generating ICE updates .
* As ICE candidates are generated , ` handleLocalAddedIceCandidate ` is called .
* and we * store * the ICE updates for later .
2016-11-12 18:22:29 +01:00
*
2017-01-06 14:40:13 +01:00
* Received call offer : ` handleReceivedOffer `
2016-11-12 18:22:29 +01:00
* Send call answer
* <-- [ SS . CallAnswer ] --
2017-01-10 18:36:54 +01:00
* Start generating ICE updates .
* As they are generated ` handleLocalAddedIceCandidate ` is called
2017-01-31 23:21:48 +01:00
which immediately sends the ICE updates to the Caller .
2017-01-06 14:40:13 +01:00
* <-- [ SS . ICEUpdate ] -- ( sent multiple times )
2016-11-12 18:22:29 +01:00
*
2017-01-06 14:40:13 +01:00
* Received CallAnswer : ` handleReceivedAnswer `
2017-01-10 18:36:54 +01:00
* So send any stored ice updates ( and send future ones immediately )
2016-11-12 18:22:29 +01:00
* -- [ SS . ICEUpdates ] -->
*
* Once compatible ICE updates have been exchanged . . .
2017-01-06 14:40:13 +01:00
* both parties : ` handleIceConnected `
2016-11-12 18:22:29 +01:00
*
* Show remote ringing UI
* Connect to offered Data Channel
* Show incoming call UI .
*
2017-01-06 14:40:13 +01:00
* If callee answers Call
2016-11-12 18:22:29 +01:00
* send connected message
* <-- [ DC . ConnectedMesage ] --
* Received connected message
* Show Call is connected .
2017-01-06 14:40:13 +01:00
*
* Hang up ( this could equally be sent by the Callee )
* -- [ DC . Hangup ] -->
* -- [ SS . Hangup ] -->
2016-11-12 18:22:29 +01:00
*/
2018-05-25 22:51:40 +02:00
public enum CallError : Error {
2016-11-12 18:22:29 +01:00
case providerReset
case assertionError ( description : String )
case disconnected
case externalError ( underlyingError : Error )
2017-04-18 17:51:14 +02:00
case timeout ( description : String )
case obsoleteCall ( description : String )
2018-08-06 17:49:25 +02:00
case fatalError ( description : String )
2016-11-12 18:22:29 +01:00
}
2017-02-08 01:37:05 +01:00
// S h o u l d b e r o u g h l y s y n c e d w i t h A n d r o i d c l i e n t f o r c o n s i s t e n c y
2017-10-12 16:26:34 +02:00
private let connectingTimeoutSeconds : TimeInterval = 120
2016-11-12 18:22:29 +01:00
2017-01-26 16:05:41 +01:00
// A l l O b s e r v e r m e t h o d s w i l l b e i n v o k e d f r o m t h e m a i n t h r e a d .
protocol CallServiceObserver : class {
2017-02-03 21:37:16 +01:00
/* *
* Fired whenever the call changes .
*/
func didUpdateCall ( call : SignalCall ? )
2017-01-26 16:05:41 +01:00
/* *
* Fired whenever the local or remote video track become active or inactive .
*/
2017-05-03 15:34:13 +02:00
func didUpdateVideoTracks ( call : SignalCall ? ,
2018-06-25 20:07:48 +02:00
localCaptureSession : AVCaptureSession ? ,
2017-01-26 16:05:41 +01:00
remoteVideoTrack : RTCVideoTrack ? )
}
2018-05-15 20:57:23 +02:00
// G a t h e r a l l p e r - c a l l s t a t e i n o n e p l a c e .
private class SignalCallData : NSObject {
public let call : SignalCall
// U s e d t o c o o r d i n a t e p r o m i s e s a c r o s s d e l e g a t e m e t h o d s
let fulfillCallConnectedPromise : ( ( ) -> Void )
let rejectCallConnectedPromise : ( ( Error ) -> Void )
let callConnectedPromise : Promise < Void >
// U s e d t o e n s u r e a n y r e c e i v e d I C E m e s s a g e s w a i t u n t i l t h e p e e r c o n n e c t i o n c l i e n t i s s e t u p .
let fulfillPeerConnectionClientPromise : ( ( ) -> Void )
let rejectPeerConnectionClientPromise : ( ( Error ) -> Void )
let peerConnectionClientPromise : Promise < Void >
// U s e d t o e n s u r e C a l l O f f e r w a s s e n t b e f o r e s e n d i n g a n y I C E u p d a t e s .
let fulfillReadyToSendIceUpdatesPromise : ( ( ) -> Void )
let rejectReadyToSendIceUpdatesPromise : ( ( Error ) -> Void )
let readyToSendIceUpdatesPromise : Promise < Void >
2018-06-25 20:07:48 +02:00
weak var localCaptureSession : AVCaptureSession ? {
didSet {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-06-25 20:07:48 +02:00
2018-08-23 16:37:34 +02:00
Logger . info ( " " )
2018-06-25 20:07:48 +02:00
}
}
2018-05-15 20:57:23 +02:00
weak var remoteVideoTrack : RTCVideoTrack ? {
didSet {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-05-15 20:57:23 +02:00
2018-08-23 16:37:34 +02:00
Logger . info ( " " )
2018-05-15 20:57:23 +02:00
}
}
var isRemoteVideoEnabled = false {
didSet {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-05-15 20:57:23 +02:00
2018-08-24 18:40:16 +02:00
Logger . info ( " \( isRemoteVideoEnabled ) " )
2018-05-15 20:57:23 +02:00
}
}
2018-05-15 21:05:11 +02:00
var peerConnectionClient : PeerConnectionClient ? {
didSet {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-05-15 21:05:11 +02:00
2018-08-24 18:40:16 +02:00
Logger . debug ( " .peerConnectionClient setter: \( oldValue != nil ) -> \( peerConnectionClient != nil ) \( String ( describing : peerConnectionClient ) ) " )
2018-05-15 21:05:11 +02:00
}
}
2018-05-15 20:57:23 +02:00
required init ( call : SignalCall ) {
self . call = call
let ( callConnectedPromise , fulfillCallConnectedPromise , rejectCallConnectedPromise ) = Promise < Void > . pending ( )
self . callConnectedPromise = callConnectedPromise
2018-05-25 20:59:36 +02:00
self . fulfillCallConnectedPromise = {
fulfillCallConnectedPromise ( ( ) )
}
2018-05-15 20:57:23 +02:00
self . rejectCallConnectedPromise = rejectCallConnectedPromise
let ( peerConnectionClientPromise , fulfillPeerConnectionClientPromise , rejectPeerConnectionClientPromise ) = Promise < Void > . pending ( )
self . peerConnectionClientPromise = peerConnectionClientPromise
2018-05-25 20:59:36 +02:00
self . fulfillPeerConnectionClientPromise = {
fulfillPeerConnectionClientPromise ( ( ) )
}
2018-05-15 20:57:23 +02:00
self . rejectPeerConnectionClientPromise = rejectPeerConnectionClientPromise
let ( readyToSendIceUpdatesPromise , fulfillReadyToSendIceUpdatesPromise , rejectReadyToSendIceUpdatesPromise ) = Promise < Void > . pending ( )
self . readyToSendIceUpdatesPromise = readyToSendIceUpdatesPromise
2018-05-25 20:59:36 +02:00
self . fulfillReadyToSendIceUpdatesPromise = {
fulfillReadyToSendIceUpdatesPromise ( ( ) )
}
2018-05-15 20:57:23 +02:00
self . rejectReadyToSendIceUpdatesPromise = rejectReadyToSendIceUpdatesPromise
super . init ( )
}
deinit {
Logger . debug ( " [SignalCallData] deinit " )
}
// MARK: -
public func terminate ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-05-15 20:57:23 +02:00
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-05-15 20:57:23 +02:00
self . call . removeAllObservers ( )
// I n c a s e w e ' r e s t i l l w a i t i n g o n t h i s p r o m i s e s o m e w h e r e , w e n e e d t o r e j e c t i t t o a v o i d a m e m o r y l e a k .
// T h e r e i s n o h a r m i n r e j e c t i n g a p r e v i o u s l y f u l f i l l e d p r o m i s e .
rejectCallConnectedPromise ( CallError . obsoleteCall ( description : " Terminating call " ) )
// I n c a s e w e ' r e s t i l l w a i t i n g o n t h e p e e r c o n n e c t i o n s e t u p s o m e w h e r e , w e n e e d t o r e j e c t i t t o a v o i d a m e m o r y l e a k .
// T h e r e i s n o h a r m i n r e j e c t i n g a p r e v i o u s l y f u l f i l l e d p r o m i s e .
rejectPeerConnectionClientPromise ( CallError . obsoleteCall ( description : " Terminating call " ) )
// I n c a s e w e ' r e s t i l l w a i t i n g o n t h i s p r o m i s e s o m e w h e r e , w e n e e d t o r e j e c t i t t o a v o i d a m e m o r y l e a k .
// T h e r e i s n o h a r m i n r e j e c t i n g a p r e v i o u s l y f u l f i l l e d p r o m i s e .
rejectReadyToSendIceUpdatesPromise ( CallError . obsoleteCall ( description : " Terminating call " ) )
2018-05-15 21:05:11 +02:00
peerConnectionClient ? . terminate ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " setting peerConnectionClient " )
2018-05-15 20:57:23 +02:00
}
}
2017-01-31 21:28:01 +01:00
// T h i s c l a s s ' s t a t e s h o u l d o n l y b e a c c e s s e d o n t h e m a i n q u e u e .
2018-05-25 22:51:40 +02:00
@objc public class CallService : NSObject , CallObserver , PeerConnectionClientDelegate {
2016-11-12 18:22:29 +01:00
// MARK: - P r o p e r t i e s
2017-01-26 16:05:41 +01:00
var observers = [ Weak < CallServiceObserver > ] ( )
2016-11-12 18:22:29 +01:00
// MARK: D e p e n d e n c i e s
2017-02-02 18:21:48 +01:00
private let accountManager : AccountManager
private let messageSender : MessageSender
private let contactsManager : OWSContactsManager
2018-03-05 15:30:58 +01:00
private let primaryStorage : OWSPrimaryStorage
2017-02-02 18:21:48 +01:00
// E x p o s e d b y e n v i r o n m e n t . m
2017-05-31 17:49:30 +02:00
internal let notificationsAdapter : CallNotificationsAdapter
2018-05-25 22:51:40 +02:00
@objc public var callUIAdapter : CallUIAdapter !
2016-11-12 18:22:29 +01:00
// MARK: C l a s s
static let fallbackIceServer = RTCIceServer ( urlStrings : [ " stun:stun1.l.google.com:19302 " ] )
// MARK: I v a r s
2018-05-15 20:57:23 +02:00
fileprivate var callData : SignalCallData ? {
2017-01-26 16:05:41 +01:00
didSet {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
2018-05-15 20:57:23 +02:00
oldValue ? . call . removeObserver ( self )
callData ? . call . addObserverAndSyncState ( observer : self )
2017-01-26 16:05:41 +01:00
2017-01-31 21:28:01 +01:00
updateIsVideoEnabled ( )
2017-07-05 16:49:27 +02:00
// P r e v e n t d e v i c e f r o m s l e e p i n g w h i l e w e h a v e a n a c t i v e c a l l .
2018-05-15 20:57:23 +02:00
if oldValue != callData {
2017-07-05 16:49:27 +02:00
if let oldValue = oldValue {
DeviceSleepManager . sharedInstance . removeBlock ( blockObject : oldValue )
}
2018-05-15 20:57:23 +02:00
if let callData = callData {
DeviceSleepManager . sharedInstance . addBlock ( blockObject : callData )
2017-08-03 16:49:37 +02:00
self . startCallTimer ( )
2018-05-30 17:29:42 +02:00
} else {
stopAnyCallTimer ( )
2017-07-05 16:49:27 +02:00
}
}
2017-02-03 21:37:16 +01:00
2018-08-24 18:40:16 +02:00
Logger . debug ( " .callData setter: \( oldValue ? . call . identifiersForLogs as Optional ) -> \( callData ? . call . identifiersForLogs as Optional ) " )
2017-02-04 02:17:42 +01:00
2017-02-03 21:37:16 +01:00
for observer in observers {
2018-05-15 20:57:23 +02:00
observer . value ? . didUpdateCall ( call : callData ? . call )
2017-02-03 21:37:16 +01:00
}
2017-01-26 16:05:41 +01:00
}
}
2017-01-06 14:40:13 +01:00
2018-05-25 23:17:15 +02:00
@objc
2018-05-15 20:57:23 +02:00
var call : SignalCall ? {
get {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2018-05-22 15:39:46 +02:00
return callData ? . call
2018-05-15 20:57:23 +02:00
}
}
2018-05-15 21:05:11 +02:00
var peerConnectionClient : PeerConnectionClient ? {
get {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-05-15 21:05:11 +02:00
2018-05-22 15:39:46 +02:00
return callData ? . peerConnectionClient
2018-05-15 21:05:11 +02:00
}
}
2018-06-25 20:07:48 +02:00
weak var localCaptureSession : AVCaptureSession ? {
get {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-06-25 20:07:48 +02:00
return callData ? . localCaptureSession
}
}
2018-05-22 15:39:46 +02:00
var remoteVideoTrack : RTCVideoTrack ? {
2018-05-15 20:57:23 +02:00
get {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
2018-05-22 15:39:46 +02:00
return callData ? . remoteVideoTrack
2017-01-26 16:05:41 +01:00
}
}
2018-05-15 20:57:23 +02:00
var isRemoteVideoEnabled : Bool {
get {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 17:33:42 +01:00
2018-05-15 20:57:23 +02:00
guard let callData = callData else {
return false
}
return callData . isRemoteVideoEnabled
2017-01-26 17:33:42 +01:00
}
}
2017-01-26 16:05:41 +01:00
2018-05-25 22:51:40 +02:00
@objc public required init ( accountManager : AccountManager , contactsManager : OWSContactsManager , messageSender : MessageSender , notificationsAdapter : CallNotificationsAdapter ) {
2016-11-12 18:22:29 +01:00
self . accountManager = accountManager
2017-02-02 18:21:48 +01:00
self . contactsManager = contactsManager
2016-11-12 18:22:29 +01:00
self . messageSender = messageSender
2017-02-02 18:21:48 +01:00
self . notificationsAdapter = notificationsAdapter
2018-03-05 15:30:58 +01:00
self . primaryStorage = OWSPrimaryStorage . shared ( )
2016-11-12 18:22:29 +01:00
super . init ( )
2017-12-07 16:33:27 +01:00
SwiftSingletons . register ( self )
2017-02-02 18:21:48 +01:00
self . createCallUIAdapter ( )
2017-01-30 23:50:28 +01:00
NotificationCenter . default . addObserver ( self ,
2017-11-07 16:04:58 +01:00
selector : #selector ( didEnterBackground ) ,
2018-01-10 16:54:17 +01:00
name : NSNotification . Name . OWSApplicationDidEnterBackground ,
2017-11-07 16:04:58 +01:00
object : nil )
2017-01-30 23:50:28 +01:00
NotificationCenter . default . addObserver ( self ,
2017-11-07 16:04:58 +01:00
selector : #selector ( didBecomeActive ) ,
2018-01-10 16:54:17 +01:00
name : NSNotification . Name . OWSApplicationDidBecomeActive ,
2017-11-07 16:04:58 +01:00
object : nil )
2017-01-31 21:28:01 +01:00
}
2017-01-30 23:50:28 +01:00
deinit {
NotificationCenter . default . removeObserver ( self )
}
2018-05-25 18:54:25 +02:00
@objc func didEnterBackground ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-30 23:50:28 +01:00
self . updateIsVideoEnabled ( )
}
2018-05-25 18:54:25 +02:00
@objc func didBecomeActive ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-30 23:50:28 +01:00
self . updateIsVideoEnabled ( )
2016-11-12 18:22:29 +01:00
}
2017-02-22 16:06:01 +01:00
/* *
* Choose whether to use CallKit or a Notification backed interface for calling .
*/
2018-05-25 22:54:51 +02:00
@objc public func createCallUIAdapter ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-02-02 18:21:48 +01:00
if self . call != nil {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ending current call in. Did user toggle callkit preference while in a call? " )
2017-02-02 18:21:48 +01:00
self . terminateCall ( )
}
self . callUIAdapter = CallUIAdapter ( callService : self , contactsManager : self . contactsManager , notificationsAdapter : self . notificationsAdapter )
}
2016-11-12 18:22:29 +01:00
// MARK: - S e r v i c e A c t i o n s
/* *
* Initiate an outgoing call .
*/
2018-05-25 22:51:40 +02:00
func handleOutgoingCall ( _ call : SignalCall ) -> Promise < Void > {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2017-02-02 21:10:50 +01:00
guard self . call = = nil else {
2018-09-07 21:31:55 +02:00
let errorDescription = " call was unexpectedly already set. "
2017-02-02 21:10:50 +01:00
Logger . error ( errorDescription )
call . state = . localFailure
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallAlreadySet ( ) , file : #file , function : #function , line : #line )
return Promise ( error : CallError . assertionError ( description : errorDescription ) )
2017-02-02 21:10:50 +01:00
}
2018-05-15 20:57:23 +02:00
let callData = SignalCallData ( call : call )
self . callData = callData
2016-11-12 18:22:29 +01:00
2018-09-19 00:10:02 +02:00
// M J K T O D O r e m o v e t h i s t i m e s t a m p p a r a m
let callRecord = TSCall ( senderTimestamp : NSDate . ows_millisecondTimeStamp ( ) , withCallNumber : call . remotePhoneNumber , callType : RPRecentCallTypeOutgoingIncomplete , in : call . thread )
2016-11-12 18:22:29 +01:00
callRecord . save ( )
2017-02-08 21:52:48 +01:00
call . callRecord = callRecord
2016-11-12 18:22:29 +01:00
2017-06-21 20:55:08 +02:00
let promise = getIceServers ( ) . then { iceServers -> Promise < HardenedRTCSessionDescription > in
2018-08-23 16:37:34 +02:00
Logger . debug ( " got ice servers: \( iceServers ) for call: \( call . identifiersForLogs ) " )
2017-01-31 21:28:01 +01:00
2017-04-18 17:50:55 +02:00
guard self . call = = call else {
2018-08-23 16:37:34 +02:00
throw CallError . obsoleteCall ( description : " obsolete call " )
2017-04-18 17:50:55 +02:00
}
2018-05-15 21:05:11 +02:00
guard callData . peerConnectionClient = = nil else {
2018-09-07 21:31:55 +02:00
let errorDescription = " peerconnection was unexpectedly already set. "
2017-05-04 19:52:25 +02:00
Logger . error ( errorDescription )
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServicePeerConnectionAlreadySet ( ) , file : #file , function : #function , line : #line )
throw CallError . assertionError ( description : errorDescription )
2017-05-04 19:52:25 +02:00
}
2018-08-31 19:22:19 +02:00
let useTurnOnly = Environment . shared . preferences . doCallsHideIPAddress ( )
2017-02-22 16:06:01 +01:00
let peerConnectionClient = PeerConnectionClient ( iceServers : iceServers , delegate : self , callDirection : . outgoing , useTurnOnly : useTurnOnly )
2018-08-23 16:37:34 +02:00
Logger . debug ( " setting peerConnectionClient for call: \( call . identifiersForLogs ) " )
2018-05-15 21:05:11 +02:00
callData . peerConnectionClient = peerConnectionClient
2018-05-15 20:57:23 +02:00
callData . fulfillPeerConnectionClientPromise ( )
2016-11-12 18:22:29 +01:00
2017-04-24 21:16:17 +02:00
return peerConnectionClient . createOffer ( )
2017-02-03 16:51:40 +01:00
} . then { ( sessionDescription : HardenedRTCSessionDescription ) -> Promise < Void > in
2017-04-18 17:50:55 +02:00
guard self . call = = call else {
2018-08-23 16:37:34 +02:00
throw CallError . obsoleteCall ( description : " obsolete call " )
2017-04-18 17:50:55 +02:00
}
2017-04-24 21:16:17 +02:00
guard let peerConnectionClient = self . peerConnectionClient else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Missing peerConnectionClient " )
2018-08-23 16:37:34 +02:00
throw CallError . obsoleteCall ( description : " Missing peerConnectionClient " )
2017-04-24 21:16:17 +02:00
}
2018-08-24 16:44:23 +02:00
Logger . info ( " session description for outgoing call: \( call . identifiersForLogs ) , sdp: \( sessionDescription . logSafeDescription ) . " )
2018-08-23 19:37:22 +02:00
2017-04-24 21:16:17 +02:00
return peerConnectionClient . setLocalSessionDescription ( sessionDescription ) . then {
2018-08-06 17:36:57 +02:00
do {
2018-08-08 19:08:47 +02:00
let offerBuilder = SSKProtoCallMessageOffer . SSKProtoCallMessageOfferBuilder ( id : call . signalingId ,
sessionDescription : sessionDescription . sdp )
let callMessage = OWSOutgoingCallMessage ( thread : call . thread , offerMessage : try offerBuilder . build ( ) )
2018-08-06 17:36:57 +02:00
return self . messageSender . sendPromise ( message : callMessage )
} catch {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Couldn't build proto " )
2018-08-23 16:37:34 +02:00
throw CallError . fatalError ( description : " Couldn't build proto " )
2018-08-06 17:36:57 +02:00
}
2017-01-31 23:21:48 +01:00
}
2017-02-08 01:37:05 +01:00
} . then {
2017-04-18 17:50:55 +02:00
guard self . call = = call else {
2018-08-23 16:37:34 +02:00
throw CallError . obsoleteCall ( description : " obsolete call " )
2017-04-18 17:50:55 +02:00
}
2017-07-11 23:42:43 +02:00
// F o r o u t g o i n g c a l l s , w a i t u n t i l c a l l o f f e r i s s e n t b e f o r e w e s e n d a n y I C E u p d a t e s , t o e n s u r e m e s s a g e o r d e r i n g f o r
// c l i e n t s t h a t d o n ' t s u p p o r t r e c e i v i n g I C E u p d a t e s b e f o r e r e c e i v i n g t h e c a l l o f f e r .
self . readyToSendIceUpdates ( call : call )
2017-02-08 01:37:05 +01:00
// D o n ' t l e t t h e o u t g o i n g c a l l r i n g f o r e v e r . W e d o n ' t s u p p o r t i n b o u n d r i n g i n g f o r e v e r a n y w a y .
2017-10-12 16:26:34 +02:00
let timeout : Promise < Void > = after ( interval : connectingTimeoutSeconds ) . then { ( ) -> Void in
2018-04-19 13:47:30 +02:00
// T h i s c o d e w i l l a l w a y s b e c a l l e d , w h e t h e r o r n o t t h e c a l l h a s t i m e d o u t .
// H o w e v e r , i f t h e c a l l h a s a l r e a d y c o n n e c t e d , t h e ` r a c e ` p r o m i s e w i l l h a v e a l r e a d y b e e n
// f u l f i l l e d . R e j e c t i n g a n a l r e a d y f u l f i l l e d p r o m i s e i s a n o - o p .
2017-04-18 17:51:14 +02:00
throw CallError . timeout ( description : " timed out waiting to receive call answer " )
2017-02-08 01:37:05 +01:00
}
2018-05-15 20:57:23 +02:00
return race ( timeout , callData . callConnectedPromise )
2017-02-08 19:39:23 +01:00
} . then {
2017-04-18 18:26:18 +02:00
Logger . info ( self . call = = call
2018-09-07 21:31:55 +02:00
? " outgoing call connected: \( call . identifiersForLogs ) . "
: " obsolete outgoing call connected: \( call . identifiersForLogs ) . " )
2017-02-03 16:51:40 +01:00
} . catch { error in
2018-08-23 16:37:34 +02:00
Logger . error ( " placing call \( call . identifiersForLogs ) failed with error: \( error ) " )
2017-01-31 23:21:48 +01:00
if let callError = error as ? CallError {
2018-04-19 13:47:30 +02:00
if case . timeout = callError {
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorTimeoutWhileConnectingOutgoing ( ) , file : #file , function : #function , line : #line )
}
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorOutgoingConnectionFailedInternal ( ) , file : #file , function : #function , line : #line )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : call , error : callError )
2017-01-31 23:21:48 +01:00
} else {
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorOutgoingConnectionFailedExternal ( ) , file : #file , function : #function , line : #line )
2017-01-31 23:21:48 +01:00
let externalError = CallError . externalError ( underlyingError : error )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : call , error : externalError )
2017-01-31 23:21:48 +01:00
}
2016-11-12 18:22:29 +01:00
}
2017-06-21 20:55:08 +02:00
promise . retainUntilComplete ( )
return promise
2016-11-12 18:22:29 +01:00
}
2017-07-11 23:42:43 +02:00
func readyToSendIceUpdates ( call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-07-11 23:42:43 +02:00
2018-05-15 20:57:23 +02:00
guard let callData = self . callData else {
2018-08-23 16:37:34 +02:00
self . handleFailedCall ( failedCall : call , error : . obsoleteCall ( description : " obsolete call " ) )
2017-07-11 23:42:43 +02:00
return
}
2018-05-15 20:57:23 +02:00
guard callData . call = = call else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring \( #function ) for call other than current call " )
2017-07-11 23:42:43 +02:00
return
}
2018-05-15 20:57:23 +02:00
callData . fulfillReadyToSendIceUpdatesPromise ( )
2017-07-11 23:42:43 +02:00
}
2016-11-12 18:22:29 +01:00
/* *
* Called by the call initiator after receiving a CallAnswer from the callee .
*/
public func handleReceivedAnswer ( thread : TSContactThread , callId : UInt64 , sessionDescription : String ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . info ( " received call answer for call: \( callId ) thread: \( thread . contactIdentifier ( ) ) " )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring obsolete call: \( callId ) " )
2016-11-12 18:22:29 +01:00
return
}
guard call . signalingId = = callId else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring mismatched call: \( callId ) currentCall: \( call . signalingId ) " )
2016-11-12 18:22:29 +01:00
return
}
guard let peerConnectionClient = self . peerConnectionClient else {
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServicePeerConnectionMissing ( ) , file : #file , function : #function , line : #line )
2018-08-23 16:37:34 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " peerConnectionClient was unexpectedly nil " ) )
2016-11-12 18:22:29 +01:00
return
}
let sessionDescription = RTCSessionDescription ( type : . answer , sdp : sessionDescription )
2017-05-04 19:49:44 +02:00
let setDescriptionPromise = peerConnectionClient . setRemoteSessionDescription ( sessionDescription ) . then {
2018-08-23 16:37:34 +02:00
Logger . debug ( " successfully set remote description " )
2017-02-03 16:51:40 +01:00
} . catch { error in
2017-01-31 23:21:48 +01:00
if let callError = error as ? CallError {
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorHandleReceivedErrorInternal ( ) , file : #file , function : #function , line : #line )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : call , error : callError )
2017-01-31 23:21:48 +01:00
} else {
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorHandleReceivedErrorExternal ( ) , file : #file , function : #function , line : #line )
2017-01-31 23:21:48 +01:00
let externalError = CallError . externalError ( underlyingError : error )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : call , error : externalError )
2017-01-31 23:21:48 +01:00
}
2016-11-12 18:22:29 +01:00
}
2017-05-04 19:49:44 +02:00
setDescriptionPromise . retainUntilComplete ( )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* User didn ' t answer incoming call
*/
2017-10-12 16:26:34 +02:00
public func handleMissedCall ( _ call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-02-08 22:39:32 +01:00
2016-11-12 18:22:29 +01:00
// I n s e r t m i s s e d c a l l r e c o r d
2017-02-08 22:39:32 +01:00
if let callRecord = call . callRecord {
2017-03-22 17:49:26 +01:00
if callRecord . callType = = RPRecentCallTypeIncoming {
2018-07-11 15:58:02 +02:00
callRecord . updateCallType ( RPRecentCallTypeIncomingMissed )
2017-02-08 22:39:32 +01:00
}
} else {
2018-09-19 00:10:02 +02:00
// M J K T O D O r e m o v e t h i s t i m e s t a m p p a r a m
call . callRecord = TSCall ( senderTimestamp : NSDate . ows_millisecondTimeStamp ( ) ,
2017-10-12 16:26:34 +02:00
withCallNumber : call . thread . contactIdentifier ( ) ,
2018-07-11 15:58:02 +02:00
callType : RPRecentCallTypeIncomingMissed ,
2017-10-12 16:26:34 +02:00
in : call . thread )
2017-02-08 21:52:48 +01:00
}
assert ( call . callRecord != nil )
call . callRecord ? . save ( )
2016-11-12 18:22:29 +01:00
2017-01-31 21:28:01 +01:00
self . callUIAdapter . reportMissedCall ( call )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* Received a call while already in another call .
*/
2017-10-12 16:26:34 +02:00
private func handleLocalBusyCall ( _ call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . info ( " for call: \( call . identifiersForLogs ) thread: \( call . thread . contactIdentifier ( ) ) " )
2017-01-06 14:40:13 +01:00
2018-08-06 18:30:35 +02:00
do {
2018-08-08 19:08:47 +02:00
let busyBuilder = SSKProtoCallMessageBusy . SSKProtoCallMessageBusyBuilder ( id : call . signalingId )
let callMessage = OWSOutgoingCallMessage ( thread : call . thread , busyMessage : try busyBuilder . build ( ) )
2018-08-06 18:30:35 +02:00
let sendPromise = messageSender . sendPromise ( message : callMessage )
sendPromise . retainUntilComplete ( )
handleMissedCall ( call )
} catch {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Couldn't build proto " )
2018-08-06 18:30:35 +02:00
}
2017-01-06 14:40:13 +01:00
}
/* *
* The callee was already in another call .
*/
2017-08-11 01:21:05 +02:00
public func handleRemoteBusy ( thread : TSContactThread , callId : UInt64 ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . info ( " for thread: \( thread . contactIdentifier ( ) ) " )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring obsolete call: \( callId ) " )
2017-08-11 01:21:05 +02:00
return
}
guard call . signalingId = = callId else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring mismatched call: \( callId ) currentCall: \( call . signalingId ) " )
2017-04-18 17:50:55 +02:00
return
}
guard thread . contactIdentifier ( ) = = call . remotePhoneNumber else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring obsolete call " )
2016-11-12 18:22:29 +01:00
return
}
call . state = . remoteBusy
2017-02-02 03:33:30 +01:00
callUIAdapter . remoteBusy ( call )
2016-11-12 18:22:29 +01:00
terminateCall ( )
}
/* *
* Received an incoming call offer . We still have to complete setting up the Signaling channel before we notify
* the user of an incoming call .
*/
public func handleReceivedOffer ( thread : TSContactThread , callId : UInt64 , sessionDescription callerSessionDescription : String ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
let newCall = SignalCall . incomingCall ( localId : UUID ( ) , remotePhoneNumber : thread . contactIdentifier ( ) , signalingId : callId )
2018-08-23 16:37:34 +02:00
Logger . info ( " receivedCallOffer: \( newCall . identifiersForLogs ) " )
2017-06-21 20:55:08 +02:00
2017-06-07 00:59:38 +02:00
let untrustedIdentity = OWSIdentityManager . shared ( ) . untrustedIdentityForSending ( toRecipientId : thread . contactIdentifier ( ) )
guard untrustedIdentity = = nil else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " missed a call due to untrusted identity: \( newCall . identifiersForLogs ) " )
2017-06-13 17:06:21 +02:00
2017-05-31 17:49:30 +02:00
let callerName = self . contactsManager . displayName ( forPhoneIdentifier : thread . contactIdentifier ( ) )
2017-06-07 00:59:38 +02:00
2017-06-21 21:16:57 +02:00
switch untrustedIdentity ! . verificationState {
2017-06-07 00:59:38 +02:00
case . verified :
2018-08-27 16:27:48 +02:00
owsFailDebug ( " shouldn't have missed a call due to untrusted identity if the identity is verified " )
2017-06-07 00:59:38 +02:00
self . notificationsAdapter . presentMissedCall ( newCall , callerName : callerName )
case . default :
self . notificationsAdapter . presentMissedCallBecauseOfNewIdentity ( call : newCall , callerName : callerName )
case . noLongerVerified :
self . notificationsAdapter . presentMissedCallBecauseOfNoLongerVerifiedIdentity ( call : newCall , callerName : callerName )
}
2018-09-19 00:10:02 +02:00
// M J K T O D O r e m o v e t h i s t i m e s t a m p p a r a m
let callRecord = TSCall ( senderTimestamp : NSDate . ows_millisecondTimeStamp ( ) ,
2017-06-07 18:37:37 +02:00
withCallNumber : thread . contactIdentifier ( ) ,
2018-07-11 15:58:02 +02:00
callType : RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity ,
2017-06-07 18:37:37 +02:00
in : thread )
assert ( newCall . callRecord = = nil )
newCall . callRecord = callRecord
callRecord . save ( )
2017-06-13 17:06:21 +02:00
terminateCall ( )
2016-11-12 18:22:29 +01:00
return
}
2017-06-15 21:19:33 +02:00
2017-06-15 20:17:34 +02:00
guard self . call = = nil else {
2017-08-11 01:21:05 +02:00
let existingCall = self . call !
2017-06-15 20:17:34 +02:00
// T O D O o n i O S 1 0 + w e c a n u s e C a l l K i t t o s w a p c a l l s r a t h e r t h a n j u s t r e t u r n i n g b u s y i m m e d i a t e l y .
2018-08-23 16:37:34 +02:00
Logger . info ( " receivedCallOffer: \( newCall . identifiersForLogs ) but we're already in call: \( existingCall . identifiersForLogs ) " )
2017-06-15 21:19:33 +02:00
2017-10-12 16:26:34 +02:00
handleLocalBusyCall ( newCall )
2017-06-15 21:19:33 +02:00
2017-08-11 01:21:05 +02:00
if existingCall . remotePhoneNumber = = newCall . remotePhoneNumber {
2018-08-23 16:37:34 +02:00
Logger . info ( " handling call from current call user as remote busy.: \( newCall . identifiersForLogs ) but we're already in call: \( existingCall . identifiersForLogs ) " )
2017-06-22 16:27:09 +02:00
// I f w e ' r e r e c e i v i n g a n e w c a l l o f f e r f r o m t h e u s e r w e a l r e a d y t h i n k w e h a v e a c a l l w i t h ,
// t e r m i n a t e o u r c u r r e n t c a l l t o g e t b a c k t o a k n o w n g o o d s t a t e . I f t h e y c a l l b a c k , w e ' l l
// b e r e a d y .
2017-06-21 21:16:57 +02:00
//
// TODO: A u t o - a c c e p t t h i s i n c o m i n g c a l l i f o u r c u r r e n t c a l l w a s e i t h e r a ) o u t g o i n g o r
2017-07-10 18:39:44 +02:00
// b ) n e v e r c o n n e c t e d . T h e r e w i l l b e a b i t o f c o m p l e x i t y a r o u n d m a k i n g s u r e t h a t t w o
// p a r t i e s t h a t c a l l e a c h o t h e r a t t h e s a m e t i m e e n d u p c o n n e c t e d .
2017-08-11 01:21:05 +02:00
switch existingCall . state {
2017-06-22 16:27:09 +02:00
case . idle , . dialing , . remoteRinging :
// I f b o t h u s e r s a r e t r y i n g t o c a l l e a c h o t h e r a t t h e s a m e t i m e ,
// b o t h s h o u l d s e e b u s y .
2017-11-07 16:04:58 +01:00
handleRemoteBusy ( thread : existingCall . thread , callId : existingCall . signalingId )
2018-04-19 15:56:09 +02:00
case . answering , . localRinging , . connected , . localFailure , . localHangup , . remoteHangup , . remoteBusy , . reconnecting :
2017-06-22 16:27:09 +02:00
// I f o n e u s e r c a l l s a n o t h e r w h i l e t h e o t h e r h a s a " v e s t i g i a l " c a l l w i t h
// t h a t s a m e u s e r , f a i l t h e o l d c a l l .
terminateCall ( )
}
2017-06-21 21:16:57 +02:00
}
2017-06-15 20:17:34 +02:00
return
}
2016-11-12 18:22:29 +01:00
2018-08-23 16:37:34 +02:00
Logger . info ( " starting new call: \( newCall . identifiersForLogs ) " )
2017-06-13 17:06:21 +02:00
2018-05-15 20:57:23 +02:00
let callData = SignalCallData ( call : newCall )
self . callData = callData
2016-11-12 18:22:29 +01:00
2018-02-26 19:49:30 +01:00
var backgroundTask : OWSBackgroundTask ? = OWSBackgroundTask ( label : " \( #function ) " , completionBlock : { [ weak self ] status in
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-02-02 01:49:54 +01:00
guard status = = . expired else {
return
}
2017-12-15 19:03:03 +01:00
guard let strongSelf = self else {
return
}
2017-04-18 17:51:14 +02:00
let timeout = CallError . timeout ( description : " background task time ran out before call connected. " )
2017-12-15 19:03:03 +01:00
guard strongSelf . call = = newCall else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring obsolete call " )
2017-12-15 19:03:03 +01:00
return
2016-11-12 18:22:29 +01:00
}
2017-12-15 19:03:03 +01:00
strongSelf . handleFailedCall ( failedCall : newCall , error : timeout )
} )
2016-11-12 18:22:29 +01:00
2017-06-21 20:55:08 +02:00
let incomingCallPromise = firstly {
2016-11-12 18:22:29 +01:00
return getIceServers ( )
2017-02-03 16:51:40 +01:00
} . then { ( iceServers : [ RTCIceServer ] ) -> Promise < HardenedRTCSessionDescription > in
2017-01-31 23:21:48 +01:00
// F I X M E f o r f i r s t t i m e c a l l r e c i p i e n t s I t h i n k w e ' l l s e e m i c / c a m e r a p e r m i s s i o n r e q u e s t s h e r e ,
// e v e n t h o u g h , f r o m t h e u s e r s p e r s p e c t i v e , n o i n c o m i n g c a l l i s y e t v i s i b l e .
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
2017-04-18 17:51:14 +02:00
throw CallError . obsoleteCall ( description : " getIceServers() response for obsolete call " )
2017-02-04 02:17:42 +01:00
}
2017-02-01 23:38:17 +01:00
assert ( self . peerConnectionClient = = nil , " Unexpected PeerConnectionClient instance " )
2017-02-22 16:06:01 +01:00
// F o r c o n t a c t s n o t s t o r e d i n o u r s y s t e m c o n t a c t s , w e a s s u m e t h e y a r e a n u n k n o w n c a l l e r , a n d w e f o r c e
// a T U R N c o n n e c t i o n , s o a s n o t t o r e v e a l a n y c o n n e c t i v i t y i n f o r m a t i o n ( I P / p o r t ) t o t h e c a l l e r .
2018-07-27 20:19:11 +02:00
let isUnknownCaller = ! self . contactsManager . hasSignalAccount ( forRecipientId : thread . contactIdentifier ( ) )
2017-02-22 16:06:01 +01:00
2018-08-31 19:22:19 +02:00
let useTurnOnly = isUnknownCaller || Environment . shared . preferences . doCallsHideIPAddress ( )
2017-02-22 16:06:01 +01:00
2018-08-23 16:37:34 +02:00
Logger . debug ( " setting peerConnectionClient for: \( newCall . identifiersForLogs ) " )
2017-04-24 21:16:17 +02:00
let peerConnectionClient = PeerConnectionClient ( iceServers : iceServers , delegate : self , callDirection : . incoming , useTurnOnly : useTurnOnly )
2018-05-15 21:05:11 +02:00
callData . peerConnectionClient = peerConnectionClient
2018-05-15 20:57:23 +02:00
callData . fulfillPeerConnectionClientPromise ( )
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
let offerSessionDescription = RTCSessionDescription ( type : . offer , sdp : callerSessionDescription )
let constraints = RTCMediaConstraints ( mandatoryConstraints : nil , optionalConstraints : nil )
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
// F i n d a s e s s i o n D e s c r i p t i o n c o m p a t i b l e w i t h m y c o n s t r a i n t s a n d t h e r e m o t e s e s s i o n D e s c r i p t i o n
2017-04-24 21:16:17 +02:00
return peerConnectionClient . negotiateSessionDescription ( remoteDescription : offerSessionDescription , constraints : constraints )
2017-02-03 16:51:40 +01:00
} . then { ( negotiatedSessionDescription : HardenedRTCSessionDescription ) in
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
2017-04-18 17:51:14 +02:00
throw CallError . obsoleteCall ( description : " negotiateSessionDescription() response for obsolete call " )
2017-02-04 02:17:42 +01:00
}
2016-11-12 18:22:29 +01:00
2018-08-24 16:44:23 +02:00
Logger . info ( " session description for incoming call: \( newCall . identifiersForLogs ) , sdp: \( negotiatedSessionDescription . logSafeDescription ) . " )
2018-08-23 19:37:22 +02:00
2018-08-06 17:40:58 +02:00
do {
2018-08-08 19:08:47 +02:00
let answerBuilder = SSKProtoCallMessageAnswer . SSKProtoCallMessageAnswerBuilder ( id : newCall . signalingId ,
sessionDescription : negotiatedSessionDescription . sdp )
let callAnswerMessage = OWSOutgoingCallMessage ( thread : thread , answerMessage : try answerBuilder . build ( ) )
2018-08-06 17:40:58 +02:00
return self . messageSender . sendPromise ( message : callAnswerMessage )
} catch {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Couldn't build proto " )
2018-08-23 16:37:34 +02:00
throw CallError . fatalError ( description : " Couldn't build proto " )
2018-08-06 17:40:58 +02:00
}
2017-02-03 16:51:40 +01:00
} . then {
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
2017-08-11 15:13:54 +02:00
throw CallError . obsoleteCall ( description : " sendPromise(message: ) response for obsolete call " )
2017-02-04 02:17:42 +01:00
}
2018-08-23 16:37:34 +02:00
Logger . debug ( " successfully sent callAnswerMessage for: \( newCall . identifiersForLogs ) " )
2016-11-12 18:22:29 +01:00
2017-07-11 23:42:43 +02:00
// T h e r e ' s n o t h i n g t e c h n i c a l l y f o r b i d d i n g r e c e i v i n g I C E u p d a t e s b e f o r e r e c e i v i n g t h e C a l l A n s w e r , b u t t h i s
// a m o r e i n t u i t i v e o r d e r i n g .
self . readyToSendIceUpdates ( call : newCall )
2017-10-12 16:26:34 +02:00
let timeout : Promise < Void > = after ( interval : connectingTimeoutSeconds ) . then { ( ) -> Void in
2017-01-31 23:21:48 +01:00
// r e j e c t i n g a p r o m i s e b y t h r o w i n g i s s a f e l y a n o - o p i f t h e p r o m i s e h a s a l r e a d y b e e n f u l f i l l e d
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorTimeoutWhileConnectingIncoming ( ) , file : #file , function : #function , line : #line )
2017-04-18 17:51:14 +02:00
throw CallError . timeout ( description : " timed out waiting for call to connect " )
2017-01-31 23:21:48 +01:00
}
2016-11-12 18:22:29 +01:00
2017-01-31 23:21:48 +01:00
// T h i s w i l l b e f u l f i l l e d ( p o t e n t i a l l y ) b y t h e R T C D a t a C h a n n e l d e l e g a t e m e t h o d
2018-05-15 20:57:23 +02:00
return race ( callData . callConnectedPromise , timeout )
2017-02-08 19:39:23 +01:00
} . then {
2017-04-18 18:26:18 +02:00
Logger . info ( self . call = = newCall
2018-09-07 21:31:55 +02:00
? " incoming call connected: \( newCall . identifiersForLogs ) . "
: " obsolete incoming call connected: \( newCall . identifiersForLogs ) . " )
2017-02-03 16:51:40 +01:00
} . catch { error in
2017-02-04 02:17:42 +01:00
guard self . call = = newCall else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " ignoring error: \( error ) for obsolete call: \( newCall . identifiersForLogs ) . " )
2017-02-04 02:17:42 +01:00
return
}
2017-01-31 23:21:48 +01:00
if let callError = error as ? CallError {
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorIncomingConnectionFailedInternal ( ) , file : #file , function : #function , line : #line )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : newCall , error : callError )
2017-01-31 23:21:48 +01:00
} else {
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorIncomingConnectionFailedExternal ( ) , file : #file , function : #function , line : #line )
2017-01-31 23:21:48 +01:00
let externalError = CallError . externalError ( underlyingError : error )
2017-04-18 18:12:17 +02:00
self . handleFailedCall ( failedCall : newCall , error : externalError )
2017-01-31 23:21:48 +01:00
}
} . always {
2018-08-23 16:37:34 +02:00
Logger . debug ( " ending background task awaiting inbound call connection " )
2017-12-15 19:03:03 +01:00
2018-05-15 20:57:23 +02:00
assert ( backgroundTask != nil )
2017-12-15 19:03:03 +01:00
backgroundTask = nil
2016-11-12 18:22:29 +01:00
}
2017-06-21 20:55:08 +02:00
incomingCallPromise . retainUntilComplete ( )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* Remote client ( could be caller or callee ) sent us a connectivity update
*/
2016-11-12 18:22:29 +01:00
public func handleRemoteAddedIceCandidate ( thread : TSContactThread , callId : UInt64 , sdp : String , lineIndex : Int32 , mid : String ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . verbose ( " callId: \( callId ) " )
2018-05-15 20:57:23 +02:00
guard let callData = self . callData else {
2018-08-23 16:37:34 +02:00
Logger . info ( " ignoring remote ice update, since there is no current call. " )
2018-05-15 20:57:23 +02:00
return
}
callData . peerConnectionClientPromise . then { ( ) -> Void in
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2017-07-08 23:45:06 +02:00
guard let call = self . call else {
2018-02-21 15:50:36 +01:00
Logger . warn ( " ignoring remote ice update for thread: \( String ( describing : thread . uniqueId ) ) since there is no current call. Call already ended? " )
2017-07-08 23:45:06 +02:00
return
}
2016-11-12 18:22:29 +01:00
2017-07-08 23:45:06 +02:00
guard call . signalingId = = callId else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring mismatched call: \( callId ) currentCall: \( call . signalingId ) " )
2017-07-08 23:45:06 +02:00
return
}
2016-11-12 18:22:29 +01:00
2017-07-08 23:45:06 +02:00
guard thread . contactIdentifier ( ) = = call . thread . contactIdentifier ( ) else {
2018-02-21 15:50:36 +01:00
Logger . warn ( " ignoring remote ice update for thread: \( String ( describing : thread . uniqueId ) ) due to thread mismatch. Call already ended? " )
2017-07-08 23:45:06 +02:00
return
}
2016-11-12 18:22:29 +01:00
2017-07-08 23:45:06 +02:00
guard let peerConnectionClient = self . peerConnectionClient else {
2018-02-21 15:50:36 +01:00
Logger . warn ( " ignoring remote ice update for thread: \( String ( describing : thread . uniqueId ) ) since there is no current peerConnectionClient. Call already ended? " )
2017-07-08 23:45:06 +02:00
return
}
2016-11-12 18:22:29 +01:00
2018-08-23 16:37:34 +02:00
Logger . verbose ( " addRemoteIceCandidate " )
2017-07-08 23:45:06 +02:00
peerConnectionClient . addRemoteIceCandidate ( RTCIceCandidate ( sdp : sdp , sdpMLineIndex : lineIndex , sdpMid : mid ) )
} . catch { error in
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorHandleRemoteAddedIceCandidate ( ) , file : #file , function : #function , line : #line )
2018-08-23 16:37:34 +02:00
Logger . error ( " peerConnectionClientPromise failed with error: \( error ) " )
2017-07-08 23:45:06 +02:00
} . retainUntilComplete ( )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
2017-01-31 23:21:48 +01:00
* Local client ( could be caller or callee ) generated some connectivity information that we should send to the
2017-01-06 14:40:13 +01:00
* remote client .
*/
2016-11-12 18:22:29 +01:00
private func handleLocalAddedIceCandidate ( _ iceCandidate : RTCIceCandidate ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2018-05-15 20:57:23 +02:00
guard let callData = self . callData else {
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallMissing ( ) , file : #file , function : #function , line : #line )
self . handleFailedCurrentCall ( error : CallError . assertionError ( description : " ignoring local ice candidate, since there is no current call. " ) )
2016-11-12 18:22:29 +01:00
return
}
2018-05-15 20:57:23 +02:00
let call = callData . call
2016-11-12 18:22:29 +01:00
2017-07-11 23:42:43 +02:00
// W a i t u n t i l w e ' v e s e n t t h e C a l l O f f e r b e f o r e s e n d i n g a n y i c e u p d a t e s f o r t h e c a l l t o e n s u r e
// i n t u i t i v e m e s s a g e o r d e r i n g f o r o t h e r c l i e n t s .
2018-05-15 20:57:23 +02:00
callData . readyToSendIceUpdatesPromise . then { ( ) -> Void in
2017-07-11 23:42:43 +02:00
guard call = = self . call else {
self . handleFailedCurrentCall ( error : . obsoleteCall ( description : " current call changed since we became ready to send ice updates " ) )
return
}
2016-11-12 18:22:29 +01:00
2017-07-11 23:42:43 +02:00
guard call . state != . idle else {
// T h i s w i l l o n l y b e c a l l e d f o r t h e c u r r e n t p e e r C o n n e c t i o n C l i e n t , s o
// f a i l t h e c u r r e n t c a l l .
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallUnexpectedlyIdle ( ) , file : #file , function : #function , line : #line )
self . handleFailedCurrentCall ( error : CallError . assertionError ( description : " ignoring local ice candidate, since call is now idle. " ) )
2017-07-11 23:42:43 +02:00
return
}
2016-11-12 18:22:29 +01:00
2018-08-06 17:49:25 +02:00
guard let sdpMid = iceCandidate . sdpMid else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Missing sdpMid " )
2018-08-23 16:37:34 +02:00
throw CallError . fatalError ( description : " Missing sdpMid " )
2018-08-06 17:49:25 +02:00
}
guard iceCandidate . sdpMLineIndex < UINT32_MAX else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Invalid sdpMLineIndex " )
2018-08-23 16:37:34 +02:00
throw CallError . fatalError ( description : " Invalid sdpMLineIndex " )
2018-08-06 17:49:25 +02:00
}
2017-07-11 23:42:43 +02:00
2018-08-23 16:37:34 +02:00
Logger . info ( " sending ICE Candidate \( call . identifiersForLogs ) . " )
2018-08-06 17:49:25 +02:00
do {
2018-08-07 20:47:35 +02:00
/* *
* Sent by both parties out of band of the RTC calling channels , as part of setting up those channels . The messages
* include network accessibility information from the perspective of each client . Once compatible ICEUpdates have been
* exchanged , the clients can connect .
*/
2018-08-08 19:08:47 +02:00
let iceUpdateBuilder = SSKProtoCallMessageIceUpdate . SSKProtoCallMessageIceUpdateBuilder ( id : call . signalingId ,
sdpMid : sdpMid ,
sdpMlineIndex : UInt32 ( iceCandidate . sdpMLineIndex ) ,
sdp : iceCandidate . sdp )
let callMessage = OWSOutgoingCallMessage ( thread : call . thread , iceUpdateMessage : try iceUpdateBuilder . build ( ) )
2018-08-06 17:49:25 +02:00
let sendPromise = self . messageSender . sendPromise ( message : callMessage )
sendPromise . retainUntilComplete ( )
} catch {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Couldn't build proto " )
2018-08-23 16:37:34 +02:00
throw CallError . fatalError ( description : " Couldn't build proto " )
2018-08-06 17:49:25 +02:00
}
2017-07-11 23:42:43 +02:00
} . catch { error in
2017-11-07 16:04:58 +01:00
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorHandleLocalAddedIceCandidate ( ) , file : #file , function : #function , line : #line )
2018-08-23 16:37:34 +02:00
Logger . error ( " waitUntilReadyToSendIceUpdates failed with error: \( error ) " )
2017-07-11 23:42:43 +02:00
} . retainUntilComplete ( )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* The clients can now communicate via WebRTC .
*
2017-01-31 23:21:48 +01:00
* Called by both caller and callee . Compatible ICE messages have been exchanged between the local and remote
2017-01-06 14:40:13 +01:00
* client .
*/
2016-11-12 18:22:29 +01:00
private func handleIceConnected ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s w i l l o n l y b e c a l l e d f o r t h e c u r r e n t p e e r C o n n e c t i o n C l i e n t , s o
// f a i l t h e c u r r e n t c a l l .
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallMissing ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " ignoring \( #function ) since there is no current call. " ) )
2016-11-12 18:22:29 +01:00
return
}
2018-08-23 16:37:34 +02:00
Logger . info ( " \( call . identifiersForLogs ) . " )
2016-11-12 18:22:29 +01:00
switch call . state {
case . dialing :
call . state = . remoteRinging
case . answering :
call . state = . localRinging
2017-06-21 20:55:08 +02:00
self . callUIAdapter . reportIncomingCall ( call , thread : call . thread )
2016-11-12 18:22:29 +01:00
case . remoteRinging :
2018-08-23 16:37:34 +02:00
Logger . info ( " call already ringing. Ignoring \( #function ) : \( call . identifiersForLogs ) . " )
2017-01-31 19:42:15 +01:00
case . connected :
2018-08-23 16:37:34 +02:00
Logger . info ( " Call reconnected \( #function ) : \( call . identifiersForLogs ) . " )
2018-04-19 15:56:09 +02:00
case . reconnecting :
call . state = . connected
case . idle , . localRinging , . localFailure , . localHangup , . remoteHangup , . remoteBusy :
2018-08-27 16:27:48 +02:00
owsFailDebug ( " unexpected call state: \( call . state ) : \( call . identifiersForLogs ) . " )
2018-04-19 15:56:09 +02:00
}
}
private func handleIceDisconnected ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-04-19 15:56:09 +02:00
guard let call = self . call else {
// T h i s w i l l o n l y b e c a l l e d f o r t h e c u r r e n t p e e r C o n n e c t i o n C l i e n t , s o
// f a i l t h e c u r r e n t c a l l .
OWSProdError ( OWSAnalyticsEvents . callServiceCallMissing ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " ignoring \( #function ) since there is no current call. " ) )
2018-04-19 15:56:09 +02:00
return
}
2018-08-23 16:37:34 +02:00
Logger . info ( " \( call . identifiersForLogs ) . " )
2018-04-19 15:56:09 +02:00
switch call . state {
2018-05-23 20:37:12 +02:00
case . remoteRinging , . localRinging :
2018-08-23 16:37:34 +02:00
Logger . debug ( " disconnect while ringing... we'll keep ringing " )
2018-04-19 15:56:09 +02:00
case . connected :
call . state = . reconnecting
2016-11-12 18:22:29 +01:00
default :
2018-08-27 16:27:48 +02:00
owsFailDebug ( " unexpected call state: \( call . state ) : \( call . identifiersForLogs ) . " )
2016-11-12 18:22:29 +01:00
}
}
2017-01-06 14:40:13 +01:00
/* *
* The remote client ( caller or callee ) ended the call .
*/
2017-08-11 01:21:05 +02:00
public func handleRemoteHangup ( thread : TSContactThread , callId : UInt64 ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2016-11-12 18:22:29 +01:00
2017-06-21 20:55:08 +02:00
guard let call = self . call else {
// T h i s m a y h a p p e n i f w e h a n g u p s l i g h t l y b e f o r e t h e y h a n g u p .
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : . obsoleteCall ( description : " call was unexpectedly nil " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-08-11 01:21:05 +02:00
guard call . signalingId = = callId else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring mismatched call: \( callId ) currentCall: \( call . signalingId ) " )
2017-08-11 01:21:05 +02:00
return
}
2017-06-21 20:55:08 +02:00
guard thread . contactIdentifier ( ) = = call . thread . contactIdentifier ( ) else {
// T h i s c a n s a f e l y b e i g n o r e d .
// W e d o n ' t w a n t t o f a i l t h e c u r r e n t c a l l b e c a u s e a n o l d c a l l w a s s l o w t o s e n d u s t h e h a n g u p m e s s a g e .
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring hangup for thread: \( thread . contactIdentifier ( ) ) which is not the current call: \( call . identifiersForLogs ) " )
2016-11-12 18:22:29 +01:00
return
}
2018-08-23 16:37:34 +02:00
Logger . info ( " \( call . identifiersForLogs ) . " )
2017-06-21 20:55:08 +02:00
2016-11-12 18:22:29 +01:00
switch call . state {
case . idle , . dialing , . answering , . localRinging , . localFailure , . remoteBusy , . remoteRinging :
2017-10-12 16:26:34 +02:00
handleMissedCall ( call )
2018-04-19 15:56:09 +02:00
case . connected , . reconnecting , . localHangup , . remoteHangup :
2018-08-23 16:37:34 +02:00
Logger . info ( " call is finished. " )
2016-11-12 18:22:29 +01:00
}
call . state = . remoteHangup
// N o t i f y U I
2017-01-31 21:28:01 +01:00
callUIAdapter . remoteDidHangupCall ( call )
2016-11-12 18:22:29 +01:00
// s e l f . c a l l i s n i l ' d i n ` t e r m i n a t e C a l l ` , s o i t ' s i m p o r t a n t w e u p d a t e i t ' s s t a t e * b e f o r e * c a l l i n g ` t e r m i n a t e C a l l `
terminateCall ( )
}
2017-01-04 23:32:18 +01:00
/* *
2017-07-03 20:50:56 +02:00
* User chose to answer call referred to by call ` localId ` . Used by the Callee only .
2017-01-06 14:40:13 +01:00
*
* Used by notification actions which can ' t serialize a call object .
2017-01-04 23:32:18 +01:00
*/
2018-05-25 22:51:40 +02:00
@objc public func handleAnswerCall ( localId : UUID ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " call was unexpectedly nil " )
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallMissing ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " call was unexpectedly nil " ) )
2016-11-12 18:22:29 +01:00
return
}
guard call . localId = = localId else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " callLocalId: \( localId ) doesn't match current calls: \( call . localId ) " )
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallIdMismatch ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " callLocalId: \( localId ) doesn't match current calls: \( call . localId ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . handleAnswerCall ( call )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
2017-07-03 20:50:56 +02:00
* User chose to answer call referred to by call ` localId ` . Used by the Callee only .
2017-01-06 14:40:13 +01:00
*/
2016-11-12 18:22:29 +01:00
public func handleAnswerCall ( _ call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2016-11-12 18:22:29 +01:00
2018-05-22 15:39:46 +02:00
guard let currentCallData = self . callData else {
OWSProdError ( OWSAnalyticsEvents . callServiceCallDataMissing ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " callData unexpectedly nil " ) )
2016-11-12 18:22:29 +01:00
return
}
2018-05-22 15:39:46 +02:00
guard call = = currentCallData . call else {
2016-11-12 18:22:29 +01:00
// T h i s c o u l d c o n c e i v a b l y h a p p e n i f t h e o t h e r p a r t y o f a n o l d c a l l w a s s l o w t o s e n d u s t h e i r a n s w e r
// a n d w e ' v e s u b s e q u e n t l y e n g a g e d i n a n o t h e r c a l l . D o n ' t k i l l t h e c u r r e n t c a l l , b u t j u s t i g n o r e i t .
2018-08-23 16:37:34 +02:00
Logger . warn ( " ignoring \( #function ) for call other than current call " )
2016-11-12 18:22:29 +01:00
return
}
guard let peerConnectionClient = self . peerConnectionClient else {
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServicePeerConnectionMissing ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " missing peerconnection client " ) )
2016-11-12 18:22:29 +01:00
return
}
2018-08-23 16:37:34 +02:00
Logger . info ( " \( call . identifiersForLogs ) . " )
2017-06-21 20:55:08 +02:00
2018-09-19 00:10:02 +02:00
// M J K T O D O r e m o v e t h i s t i m e s t a m p p a r a m
let callRecord = TSCall ( senderTimestamp : NSDate . ows_millisecondTimeStamp ( ) , withCallNumber : call . remotePhoneNumber , callType : RPRecentCallTypeIncomingIncomplete , in : call . thread )
2016-11-12 18:22:29 +01:00
callRecord . save ( )
2017-02-08 21:52:48 +01:00
call . callRecord = callRecord
2016-11-12 18:22:29 +01:00
2018-08-06 17:15:08 +02:00
var messageData : Data
do {
2018-08-08 19:08:47 +02:00
let connectedBuilder = WebRTCProtoConnected . WebRTCProtoConnectedBuilder ( id : call . signalingId )
2018-08-06 17:15:08 +02:00
let dataBuilder = WebRTCProtoData . WebRTCProtoDataBuilder ( )
2018-08-08 19:08:47 +02:00
dataBuilder . setConnected ( try connectedBuilder . build ( ) )
2018-08-06 17:15:08 +02:00
messageData = try dataBuilder . buildSerializedData ( )
} catch {
2018-09-07 21:31:55 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " couldn't build proto " ) )
2018-08-06 17:15:08 +02:00
return
}
peerConnectionClient . sendDataChannelMessage ( data : messageData , description : " connected " , isCritical : true )
2016-11-12 18:22:29 +01:00
2018-05-22 15:39:46 +02:00
handleConnectedCall ( currentCallData )
2016-11-12 18:22:29 +01:00
}
2017-01-04 23:32:18 +01:00
/* *
2017-01-06 14:40:13 +01:00
* For outgoing call , when the callee has chosen to accept the call .
* For incoming call , when the local user has chosen to accept the call .
2017-01-04 23:32:18 +01:00
*/
2018-05-22 15:39:46 +02:00
private func handleConnectedCall ( _ callData : SignalCallData ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . info ( " " )
2016-11-12 18:22:29 +01:00
2018-05-21 20:59:57 +02:00
guard let peerConnectionClient = callData . peerConnectionClient else {
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServicePeerConnectionMissing ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " peerConnectionClient unexpectedly nil " ) )
2016-11-12 18:22:29 +01:00
return
}
2018-08-23 16:37:34 +02:00
Logger . info ( " handleConnectedCall: \( callData . call . identifiersForLogs ) . " )
2017-06-21 20:55:08 +02:00
2017-02-08 19:39:23 +01:00
// c a n c e l c o n n e c t i o n t i m e o u t
2018-05-15 20:57:23 +02:00
callData . fulfillCallConnectedPromise ( )
2017-02-08 19:39:23 +01:00
2018-05-22 15:39:46 +02:00
callData . call . state = . connected
2016-11-12 18:22:29 +01:00
// W e d o n ' t r i s k t r a n s m i t t i n g a n y m e d i a u n t i l t h e r e m o t e c l i e n t h a s a d m i t t e d t o b e i n g c o n n e c t e d .
2018-05-22 15:39:46 +02:00
ensureAudioState ( call : callData . call , peerConnectionClient : peerConnectionClient )
2017-01-26 16:05:41 +01:00
peerConnectionClient . setLocalVideoEnabled ( enabled : shouldHaveLocalVideoTrack ( ) )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* Local user chose to decline the call vs . answering it .
*
* The call is referred to by call ` localId ` , which is included in Notification actions .
*
* Incoming call only .
*/
2016-11-12 18:22:29 +01:00
public func handleDeclineCall ( localId : UUID ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
guard let call = self . call else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " call was unexpectedly nil " )
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallMissing ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " call was unexpectedly nil " ) )
2016-11-12 18:22:29 +01:00
return
}
guard call . localId = = localId else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " callLocalId: \( localId ) doesn't match current calls: \( call . localId ) " )
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallIdMismatch ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " callLocalId: \( localId ) doesn't match current calls: \( call . localId ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . handleDeclineCall ( call )
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* Local user chose to decline the call vs . answering it .
*
* Incoming call only .
*/
2016-11-12 18:22:29 +01:00
public func handleDeclineCall ( _ call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2018-08-23 16:37:34 +02:00
Logger . info ( " \( call . identifiersForLogs ) . " )
2016-11-12 18:22:29 +01:00
2017-07-03 20:50:56 +02:00
if let callRecord = call . callRecord {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Not expecting callrecord to already be set " )
2017-07-03 20:50:56 +02:00
callRecord . updateCallType ( RPRecentCallTypeIncomingDeclined )
} else {
2018-09-19 00:10:02 +02:00
// M J K T O D O r e m o v e t h i s t i m e s t a m p p a r a m
let callRecord = TSCall ( senderTimestamp : NSDate . ows_millisecondTimeStamp ( ) , withCallNumber : call . remotePhoneNumber , callType : RPRecentCallTypeIncomingDeclined , in : call . thread )
2017-07-03 20:50:56 +02:00
callRecord . save ( )
call . callRecord = callRecord
}
2017-07-03 15:42:30 +02:00
2016-11-12 18:22:29 +01:00
// C u r r e n t l y w e j u s t h a n d l e t h i s a s a h a n g u p . B u t w e c o u l d o f f e r m o r e d e s c r i p t i v e a c t i o n . e . g . D a t a C h a n n e l m e s s a g e
handleLocalHungupCall ( call )
}
2017-01-06 14:40:13 +01:00
/* *
* Local user chose to end the call .
*
* Can be used for Incoming and Outgoing calls .
*/
2016-11-12 18:22:29 +01:00
func handleLocalHungupCall ( _ call : SignalCall ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2017-04-29 03:14:40 +02:00
guard let currentCall = self . call else {
2018-08-23 16:37:34 +02:00
Logger . info ( " No current call. Other party hung up just before us. " )
2017-11-17 23:40:25 +01:00
// t e r m i n a t i n g t h e c a l l m i g h t b e r e d u n d a n t , b u t i t s h o u l d n ' t h u r t .
terminateCall ( )
2016-11-12 18:22:29 +01:00
return
}
2017-04-29 03:14:40 +02:00
guard call = = currentCall else {
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallMismatch ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " ignoring \( #function ) for call other than current call " ) )
2016-11-12 18:22:29 +01:00
return
}
2018-08-23 16:37:34 +02:00
Logger . info ( " \( call . identifiersForLogs ) . " )
2016-11-12 18:22:29 +01:00
call . state = . localHangup
2018-07-11 15:58:02 +02:00
if let callRecord = call . callRecord {
if callRecord . callType = = RPRecentCallTypeOutgoingIncomplete {
callRecord . updateCallType ( RPRecentCallTypeOutgoingMissed )
}
} else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " missing call record " )
2018-07-11 15:58:02 +02:00
}
2016-11-12 18:22:29 +01:00
// T O D O s o m e t h i n g l i k e t h i s l i f t e d f r o m S i g n a l - A n d r o i d .
// t h i s . a c c o u n t M a n a g e r . c a n c e l I n F l i g h t R e q u e s t s ( ) ;
// t h i s . m e s s a g e S e n d e r . c a n c e l I n F l i g h t R e q u e s t s ( ) ;
2017-10-27 20:46:30 +02:00
if let peerConnectionClient = self . peerConnectionClient {
2018-02-03 00:35:32 +01:00
// S t o p a u d i o c a p t u r e A S A P
ensureAudioState ( call : call , peerConnectionClient : peerConnectionClient )
2017-10-27 20:46:30 +02:00
// I f t h e c a l l i s c o n n e c t e d , w e c a n s e n d t h e h a n g u p v i a t h e d a t a c h a n n e l f o r f a s t e r h a n g u p .
2018-08-06 17:15:08 +02:00
var messageData : Data
do {
2018-08-08 19:08:47 +02:00
let hangupBuilder = WebRTCProtoHangup . WebRTCProtoHangupBuilder ( id : call . signalingId )
2018-08-06 17:15:08 +02:00
let dataBuilder = WebRTCProtoData . WebRTCProtoDataBuilder ( )
2018-08-08 19:08:47 +02:00
dataBuilder . setHangup ( try hangupBuilder . build ( ) )
2018-08-06 17:15:08 +02:00
messageData = try dataBuilder . buildSerializedData ( )
} catch {
2018-09-07 21:31:55 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " couldn't build proto " ) )
2018-08-06 17:15:08 +02:00
return
}
peerConnectionClient . sendDataChannelMessage ( data : messageData , description : " hangup " , isCritical : true )
2017-10-27 20:46:30 +02:00
} else {
2018-08-23 16:37:34 +02:00
Logger . info ( " ending call before peer connection created. Device offline or quick hangup. " )
2017-10-27 20:46:30 +02:00
}
2016-11-12 18:22:29 +01:00
// I f t h e c a l l h a s n ' t s t a r t e d y e t , w e d o n ' t h a v e a d a t a c h a n n e l t o c o m m u n i c a t e t h e h a n g u p . U s e S i g n a l S e r v i c e M e s s a g e .
2018-08-06 18:22:55 +02:00
do {
2018-08-08 19:08:47 +02:00
let hangupBuilder = SSKProtoCallMessageHangup . SSKProtoCallMessageHangupBuilder ( id : call . signalingId )
let callMessage = OWSOutgoingCallMessage ( thread : call . thread , hangupMessage : try hangupBuilder . build ( ) )
2018-08-06 18:22:55 +02:00
let sendPromise = self . messageSender . sendPromise ( message : callMessage ) . then {
2018-08-23 16:37:34 +02:00
Logger . debug ( " successfully sent hangup call message to \( call . thread . contactIdentifier ( ) ) " )
2018-08-06 18:22:55 +02:00
} . catch { error in
OWSProdInfo ( OWSAnalyticsEvents . callServiceErrorHandleLocalHungupCall ( ) , file : #file , function : #function , line : #line )
2018-08-23 16:37:34 +02:00
Logger . error ( " failed to send hangup call message to \( call . thread . contactIdentifier ( ) ) with error: \( error ) " )
2018-08-06 18:22:55 +02:00
}
sendPromise . retainUntilComplete ( )
terminateCall ( )
} catch {
2018-09-07 21:31:55 +02:00
handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " couldn't build proto " ) )
2018-08-06 18:22:55 +02:00
}
2016-11-12 18:22:29 +01:00
}
2017-01-06 14:40:13 +01:00
/* *
* Local user toggled to mute audio .
*
* Can be used for Incoming and Outgoing calls .
*/
2017-10-31 18:47:12 +01:00
func setIsMuted ( call : SignalCall , isMuted : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2017-10-31 18:47:12 +01:00
guard call = = self . call else {
2017-10-04 18:54:05 +02:00
// T h i s c a n h a p p e n a f t e r a c a l l h a s e n d e d . R e p r o d u c i b l e o n i O S 1 1 , w h e n t h e o t h e r p a r t y e n d s t h e c a l l .
2018-08-23 16:37:34 +02:00
Logger . info ( " ignoring mute request for obsolete call " )
2017-01-09 15:28:04 +01:00
return
}
call . isMuted = isMuted
2017-05-03 15:34:13 +02:00
guard let peerConnectionClient = self . peerConnectionClient else {
// T h e p e e r c o n n e c t i o n m i g h t n o t b e c r e a t e d y e t .
return
}
2017-10-31 18:47:12 +01:00
ensureAudioState ( call : call , peerConnectionClient : peerConnectionClient )
}
/* *
* Local user toggled to hold call . Currently only possible via CallKit screen ,
* e . g . when another Call comes in .
*/
func setIsOnHold ( call : SignalCall , isOnHold : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-10-31 18:47:12 +01:00
guard call = = self . call else {
2018-08-23 16:37:34 +02:00
Logger . info ( " ignoring held request for obsolete call " )
2017-10-31 18:47:12 +01:00
return
}
call . isOnHold = isOnHold
guard let peerConnectionClient = self . peerConnectionClient else {
// T h e p e e r c o n n e c t i o n m i g h t n o t b e c r e a t e d y e t .
return
}
ensureAudioState ( call : call , peerConnectionClient : peerConnectionClient )
}
func ensureAudioState ( call : SignalCall , peerConnectionClient : PeerConnectionClient ) {
guard call . state = = . connected else {
peerConnectionClient . setAudioEnabled ( enabled : false )
return
}
guard ! call . isMuted else {
peerConnectionClient . setAudioEnabled ( enabled : false )
return
2017-05-04 19:14:07 +02:00
}
2017-10-31 18:47:12 +01:00
guard ! call . isOnHold else {
peerConnectionClient . setAudioEnabled ( enabled : false )
return
}
peerConnectionClient . setAudioEnabled ( enabled : true )
2016-11-12 18:22:29 +01:00
}
2017-01-18 23:29:47 +01:00
/* *
* Local user toggled video .
*
* Can be used for Incoming and Outgoing calls .
*/
2017-01-27 17:11:33 +01:00
func setHasLocalVideo ( hasLocalVideo : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-18 23:29:47 +01:00
2017-11-07 16:04:58 +01:00
guard let frontmostViewController = UIApplication . shared . frontmostViewController else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " could not identify frontmostViewController " )
2017-01-30 19:46:58 +01:00
return
}
2017-11-07 16:38:36 +01:00
frontmostViewController . ows_ask ( forCameraPermissions : { [ weak self ] granted in
2017-11-07 16:04:58 +01:00
guard let strongSelf = self else {
return
}
2017-11-07 16:38:36 +01:00
if ( granted ) {
// S u c c e s s c a l l b a c k ; c a m e r a p e r m i s s i o n s a r e g r a n t e d .
strongSelf . setHasLocalVideoWithCameraPermissions ( hasLocalVideo : hasLocalVideo )
} else {
2017-11-07 16:04:58 +01:00
// F a i l e d c a l l b a c k ; c a m e r a p e r m i s s i o n s a r e _ N O T _ g r a n t e d .
// W e d o n ' t n e e d t o w o r r y a b o u t t h e u s e r g r a n t i n g o r r e m o t i n g t h i s p e r m i s s i o n
// d u r i n g a c a l l w h i l e t h e a p p i s i n t h e b a c k g r o u n d , b e c a u s e c h a n g i n g t h i s
// p e r m i s s i o n k i l l s t h e a p p .
2018-03-01 20:41:45 +01:00
OWSAlerts . showAlert ( title : NSLocalizedString ( " MISSING_CAMERA_PERMISSION_TITLE " , comment : " Alert title when camera is not authorized " ) ,
2017-11-07 16:04:58 +01:00
message : NSLocalizedString ( " MISSING_CAMERA_PERMISSION_MESSAGE " , comment : " Alert body when camera is not authorized " ) )
2017-11-07 16:38:36 +01:00
}
2017-11-07 16:04:58 +01:00
} )
}
private func setHasLocalVideoWithCameraPermissions ( hasLocalVideo : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-11-07 16:04:58 +01:00
2017-01-18 23:29:47 +01:00
guard let call = self . call else {
2018-05-30 17:29:42 +02:00
// T h i s c a n h a p p e n i f y o u t o g g l e l o c a l v i d e o r i g h t a f t e r
// t h e o t h e r u s e r e n d s t h e c a l l .
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete call " )
2017-01-18 23:29:47 +01:00
return
}
2017-01-27 17:11:33 +01:00
call . hasLocalVideo = hasLocalVideo
2017-05-03 15:34:13 +02:00
guard let peerConnectionClient = self . peerConnectionClient else {
// T h e p e e r c o n n e c t i o n m i g h t n o t b e c r e a t e d y e t .
return
}
2017-05-04 19:14:07 +02:00
if call . state = = . connected {
peerConnectionClient . setLocalVideoEnabled ( enabled : shouldHaveLocalVideoTrack ( ) )
}
2017-01-18 23:29:47 +01:00
}
2018-05-25 23:17:15 +02:00
@objc
2017-01-18 23:29:47 +01:00
func handleCallKitStartVideo ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
2017-11-07 16:04:58 +01:00
self . setHasLocalVideo ( hasLocalVideo : true )
2017-01-18 23:29:47 +01:00
}
2018-06-25 22:45:56 +02:00
func setCameraSource ( call : SignalCall , isUsingFrontCamera : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-04-23 22:40:56 +02:00
guard let peerConnectionClient = self . peerConnectionClient else {
return
}
2018-06-25 22:45:56 +02:00
peerConnectionClient . setCameraSource ( isUsingFrontCamera : isUsingFrontCamera )
2018-04-23 22:40:56 +02:00
}
2017-01-06 14:40:13 +01:00
/* *
2017-01-31 23:21:48 +01:00
* Local client received a message on the WebRTC data channel .
2017-01-06 14:40:13 +01:00
*
2017-01-31 23:21:48 +01:00
* The WebRTC data channel is a faster signaling channel than out of band Signal Service messages . Once it ' s
* established we use it to communicate further signaling information . The one sort - of exception is that with
* hangup messages we redundantly send a Signal Service hangup message , which is more reliable , and since the hangup
2017-01-06 14:40:13 +01:00
* action is idemptotent , there ' s no harm done .
*
* Used by both Incoming and Outgoing calls .
*/
2018-08-06 17:15:08 +02:00
private func handleDataChannelMessage ( _ message : WebRTCProtoData ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2018-05-15 20:57:23 +02:00
guard let callData = self . callData else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " received data message, but there is no current call. Ignoring. " )
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallMissing ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " received data message, but there is no current call. Ignoring. " ) )
2016-11-12 18:22:29 +01:00
return
}
2018-05-15 20:57:23 +02:00
let call = callData . call
2016-11-12 18:22:29 +01:00
2018-08-08 19:41:12 +02:00
if let connected = message . connected {
2018-08-23 16:37:34 +02:00
Logger . debug ( " remote participant sent Connected via data channel: \( call . identifiersForLogs ) . " )
2016-11-12 18:22:29 +01:00
guard connected . id = = call . signalingId else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " received connected message for call with id: \( connected . id ) but current call has id: \( call . signalingId ) " )
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallIdMismatch ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " received connected message for call with id: \( connected . id ) but current call has id: \( call . signalingId ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . callUIAdapter . recipientAcceptedCall ( call )
2018-05-22 15:39:46 +02:00
handleConnectedCall ( callData )
2016-11-12 18:22:29 +01:00
2018-08-08 19:41:12 +02:00
} else if let hangup = message . hangup {
2018-08-23 16:37:34 +02:00
Logger . debug ( " remote participant sent Hangup via data channel: \( call . identifiersForLogs ) . " )
2016-11-12 18:22:29 +01:00
guard hangup . id = = call . signalingId else {
2017-04-18 18:12:17 +02:00
// T h i s s h o u l d n e v e r h a p p e n ; r e t u r n t o a k n o w n g o o d s t a t e .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " received hangup message for call with id: \( hangup . id ) but current call has id: \( call . signalingId ) " )
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallIdMismatch ( ) , file : #file , function : #function , line : #line )
2018-09-07 21:31:55 +02:00
handleFailedCurrentCall ( error : CallError . assertionError ( description : " received hangup message for call with id: \( hangup . id ) but current call has id: \( call . signalingId ) " ) )
2016-11-12 18:22:29 +01:00
return
}
2017-08-11 01:21:05 +02:00
handleRemoteHangup ( thread : call . thread , callId : hangup . id )
2018-08-08 19:41:12 +02:00
} else if let videoStreamingStatus = message . videoStreamingStatus {
2018-08-23 16:37:34 +02:00
Logger . debug ( " remote participant sent VideoStreamingStatus via data channel: \( call . identifiersForLogs ) . " )
2016-11-12 18:22:29 +01:00
2018-08-06 17:15:08 +02:00
callData . isRemoteVideoEnabled = videoStreamingStatus . enabled
2018-05-15 20:57:23 +02:00
self . fireDidUpdateVideoTracks ( )
2017-02-02 21:51:07 +01:00
} else {
2018-08-23 16:37:34 +02:00
Logger . info ( " received unknown or empty DataChannelMessage: \( call . identifiersForLogs ) . " )
2016-11-12 18:22:29 +01:00
}
}
2017-01-12 01:06:39 +01:00
// MARK: - P e e r C o n n e c t i o n C l i e n t D e l e g a t e
/* *
* The connection has been established . The clients can now communicate .
*/
2017-02-03 16:25:03 +01:00
internal func peerConnectionClientIceConnected ( _ peerConnectionClient : PeerConnectionClient ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2017-02-03 16:25:03 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . handleIceConnected ( )
2017-01-12 01:06:39 +01:00
}
2018-04-19 15:56:09 +02:00
func peerConnectionClientIceDisconnected ( _ peerconnectionClient : PeerConnectionClient ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-04-19 15:56:09 +02:00
guard peerConnectionClient = = self . peerConnectionClient else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2018-04-19 15:56:09 +02:00
return
}
self . handleIceDisconnected ( )
}
2017-01-12 01:06:39 +01:00
/* *
* The connection failed to establish . The clients will not be able to communicate .
*/
2017-02-03 16:25:03 +01:00
internal func peerConnectionClientIceFailed ( _ peerConnectionClient : PeerConnectionClient ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2017-02-03 16:25:03 +01:00
return
}
2017-04-18 18:12:17 +02:00
// R e t u r n t o a k n o w n g o o d s t a t e .
self . handleFailedCurrentCall ( error : CallError . disconnected )
2017-01-12 01:06:39 +01:00
}
/* *
* During the Signaling process each client generates IceCandidates locally , which contain information about how to
* reach the local client via the internet . The delegate must shuttle these IceCandates to the other ( remote ) client
* out of band , as part of establishing a connection over WebRTC .
*/
2017-02-03 16:25:03 +01:00
internal func peerConnectionClient ( _ peerConnectionClient : PeerConnectionClient , addedLocalIceCandidate iceCandidate : RTCIceCandidate ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2017-02-03 16:25:03 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . handleLocalAddedIceCandidate ( iceCandidate )
2017-01-12 01:06:39 +01:00
}
2017-01-12 01:28:07 +01:00
/* *
* Once the peerconnection is established , we can receive messages via the data channel , and notify the delegate .
*/
2018-08-06 17:15:08 +02:00
internal func peerConnectionClient ( _ peerConnectionClient : PeerConnectionClient , received dataChannelMessage : WebRTCProtoData ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2017-02-03 16:25:03 +01:00
return
}
2017-01-31 21:28:01 +01:00
self . handleDataChannelMessage ( dataChannelMessage )
2017-01-12 01:28:07 +01:00
}
2018-06-25 23:17:23 +02:00
internal func peerConnectionClient ( _ peerConnectionClient : PeerConnectionClient , didUpdateLocalVideoCaptureSession captureSession : AVCaptureSession ? ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
2017-02-03 16:51:40 +01:00
guard peerConnectionClient = = self . peerConnectionClient else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2017-02-03 16:25:03 +01:00
return
}
2018-05-15 20:57:23 +02:00
guard let callData = callData else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2017-02-03 16:25:03 +01:00
return
}
2018-06-25 23:02:52 +02:00
callData . localCaptureSession = captureSession
2018-05-15 20:57:23 +02:00
fireDidUpdateVideoTracks ( )
2017-01-26 16:05:41 +01:00
}
2018-06-25 23:02:52 +02:00
internal func peerConnectionClient ( _ peerConnectionClient : PeerConnectionClient , didUpdateRemoteVideoTrack videoTrack : RTCVideoTrack ? ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-07-11 23:42:43 +02:00
2018-05-15 20:57:23 +02:00
guard peerConnectionClient = = self . peerConnectionClient else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2017-07-11 23:42:43 +02:00
return
}
2018-05-15 20:57:23 +02:00
guard let callData = callData else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " Ignoring event from obsolete peerConnectionClient " )
2017-07-11 23:42:43 +02:00
return
}
2018-05-15 20:57:23 +02:00
callData . remoteVideoTrack = videoTrack
fireDidUpdateVideoTracks ( )
2017-07-08 23:45:06 +02:00
}
2018-05-15 20:57:23 +02:00
// MARK: -
2017-07-08 23:45:06 +02:00
2017-01-06 14:40:13 +01:00
/* *
2017-01-12 01:06:39 +01:00
* RTCIceServers are used when attempting to establish an optimal connection to the other party . SignalService supplies
* a list of servers , plus we have fallback servers hardcoded in the app .
2017-01-06 14:40:13 +01:00
*/
2016-11-12 18:22:29 +01:00
private func getIceServers ( ) -> Promise < [ RTCIceServer ] > {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-31 21:28:01 +01:00
2016-11-12 18:22:29 +01:00
return firstly {
return accountManager . getTurnServerInfo ( )
2017-02-03 16:51:40 +01:00
} . then { turnServerInfo -> [ RTCIceServer ] in
2018-08-23 16:37:34 +02:00
Logger . debug ( " got turn server urls: \( turnServerInfo . urls ) " )
2017-01-31 23:21:48 +01:00
return turnServerInfo . urls . map { url in
if url . hasPrefix ( " turn " ) {
// O n l y " t u r n : " s e r v e r s r e q u i r e a u t h e n t i c a t i o n . D o n ' t i n c l u d e t h e c r e d e n t i a l s t o o t h e r I C E s e r v e r s
// a s 1 . ) t h e y a r e n ' t u s e d , a n d 2 . ) t h e n o n - t u r n s e r v e r s m i g h t n o t b e u n d e r o u r c o n t r o l .
// e . g . w e u s e a p u b l i c f a l l b a c k S T U N s e r v e r .
return RTCIceServer ( urlStrings : [ url ] , username : turnServerInfo . username , credential : turnServerInfo . password )
} else {
return RTCIceServer ( urlStrings : [ url ] )
}
} + [ CallService . fallbackIceServer ]
} . recover { error -> [ RTCIceServer ] in
2018-08-23 16:37:34 +02:00
Logger . error ( " fetching ICE servers failed with error: \( error ) " )
Logger . warn ( " using fallback ICE Servers " )
2017-01-31 23:21:48 +01:00
return [ CallService . fallbackIceServer ]
2016-11-12 18:22:29 +01:00
}
}
2017-04-18 18:12:17 +02:00
// T h i s m e t h o d s h o u l d b e c a l l e d w h e n e i t h e r : a ) w e k n o w o r a s s u m e t h a t
// t h e e r r o r i s r e l a t e d t o t h e c u r r e n t c a l l . b ) t h e e r r o r i s s o s e r i o u s
// t h a t w e w a n t t o t e r m i n a t e t h e c u r r e n t c a l l ( i f a n y ) i n o r d e r t o
// r e t u r n t o a k n o w n g o o d s t a t e .
2017-04-18 17:50:55 +02:00
public func handleFailedCurrentCall ( error : CallError ) {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2017-06-21 20:55:08 +02:00
// R e t u r n t o a k n o w n g o o d s t a t e b y e n d i n g t h e c u r r e n t c a l l , i f a n y .
handleFailedCall ( failedCall : self . call , error : error )
2017-04-18 17:50:55 +02:00
}
2017-04-18 18:12:17 +02:00
// T h i s m e t h o d s h o u l d b e c a l l e d w h e n a f a t a l e r r o r o c c u r r e d f o r a c a l l .
//
// * I f w e k n o w w h i c h c a l l i t w a s , w e s h o u l d u p d a t e t h a t c a l l ' s s t a t e
// t o r e f l e c t t h e e r r o r .
// * I F F t h a t c a l l i s t h e c u r r e n t c a l l , w e w a n t t o t e r m i n a t e i t .
2017-06-21 20:55:08 +02:00
public func handleFailedCall ( failedCall : SignalCall ? , error : CallError ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2016-11-12 18:22:29 +01:00
2018-02-26 19:49:30 +01:00
if case CallError . assertionError ( description : let description ) = error {
2018-08-27 16:27:48 +02:00
owsFailDebug ( description )
2017-07-08 23:43:34 +02:00
}
2017-06-21 20:55:08 +02:00
if let failedCall = failedCall {
2017-10-12 16:26:34 +02:00
2018-05-23 20:37:12 +02:00
switch failedCall . state {
case . answering , . localRinging :
2017-10-12 16:26:34 +02:00
assert ( failedCall . callRecord = = nil )
// c a l l f a i l e d b e f o r e a n y c a l l r e c o r d c o u l d b e c r e a t e d , m a k e o n e n o w .
handleMissedCall ( failedCall )
2018-05-23 20:37:12 +02:00
default :
assert ( failedCall . callRecord != nil )
2017-10-12 16:26:34 +02:00
}
2017-06-21 20:55:08 +02:00
// I t ' s e s s e n t i a l t o s e t c a l l . s t a t e b e f o r e t e r m i n a t e C a l l , b e c a u s e t e r m i n a t e C a l l n i l s s e l f . c a l l
failedCall . error = error
failedCall . state = . localFailure
self . callUIAdapter . failCall ( failedCall , error : error )
2017-02-08 20:27:29 +01:00
2017-06-21 20:55:08 +02:00
// O n l y t e r m i n a t e t h e c u r r e n t c a l l i f t h e e r r o r p e r t a i n s t o t h e c u r r e n t c a l l .
guard failedCall = = self . call else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " ignoring obsolete call: \( failedCall . identifiersForLogs ) . " )
2017-06-21 20:55:08 +02:00
return
}
2017-02-08 20:27:29 +01:00
2018-08-23 16:37:34 +02:00
Logger . error ( " call: \( failedCall . identifiersForLogs ) failed with error: \( error ) " )
2017-06-21 20:55:08 +02:00
} else {
2018-08-23 16:37:34 +02:00
Logger . error ( " unknown call failed with error: \( error ) " )
2017-01-26 19:18:06 +01:00
}
2016-11-12 18:22:29 +01:00
2017-04-18 17:50:55 +02:00
// O n l y t e r m i n a t e t h e c a l l i f i t i s t h e c u r r e n t c a l l .
2016-11-12 18:22:29 +01:00
terminateCall ( )
}
2017-01-12 01:06:39 +01:00
/* *
* Clean up any existing call state and get ready to receive a new call .
*/
2016-11-12 18:22:29 +01:00
private func terminateCall ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2016-11-12 18:22:29 +01:00
2018-05-15 20:57:23 +02:00
let currentCallData = self . callData
self . callData = nil
2017-02-03 16:25:03 +01:00
2018-05-15 20:57:23 +02:00
currentCallData ? . terminate ( )
2018-02-17 02:41:41 +01:00
2018-05-24 18:18:39 +02:00
self . callUIAdapter . didTerminateCall ( currentCallData ? . call )
2018-05-15 21:05:11 +02:00
2018-05-15 20:57:23 +02:00
fireDidUpdateVideoTracks ( )
2017-01-26 16:05:41 +01:00
}
// MARK: - C a l l O b s e r v e r
internal func stateDidChange ( call : SignalCall , state : CallState ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . info ( " \( state ) " )
2017-02-07 15:35:37 +01:00
updateIsVideoEnabled ( )
2017-01-26 16:05:41 +01:00
}
2017-01-27 17:11:33 +01:00
internal func hasLocalVideoDidChange ( call : SignalCall , hasLocalVideo : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-08-23 16:37:34 +02:00
Logger . info ( " \( hasLocalVideo ) " )
2017-01-26 16:05:41 +01:00
self . updateIsVideoEnabled ( )
}
internal func muteDidChange ( call : SignalCall , isMuted : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
// D o n o t h i n g
}
2017-10-28 20:44:29 +02:00
internal func holdDidChange ( call : SignalCall , isOnHold : Bool ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-10-28 20:44:29 +02:00
// D o n o t h i n g
}
2017-07-13 21:37:01 +02:00
internal func audioSourceDidChange ( call : SignalCall , audioSource : AudioSource ? ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
// D o n o t h i n g
}
// MARK: - V i d e o
private func shouldHaveLocalVideoTrack ( ) -> Bool {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
2017-04-29 03:14:40 +02:00
guard let call = self . call else {
return false
}
2017-01-26 16:05:41 +01:00
// T h e i O S s i m u l a t o r d o e s n ' t p r o v i d e a n y s o r t o f c a m e r a c a p t u r e
// s u p p o r t o r e m u l a t i o n ( h t t p : / / g o o . g l / r H A n C 1 ) s o d o n ' t b o t h e r
// t r y i n g t o o p e n a l o c a l s t r e a m .
return ( ! Platform . isSimulator &&
2017-01-30 23:50:28 +01:00
UIApplication . shared . applicationState != . background &&
2017-04-29 03:14:40 +02:00
call . state = = . connected &&
call . hasLocalVideo )
2017-01-26 16:05:41 +01:00
}
2017-02-25 02:33:43 +01:00
// T O D O o n l y f i r e t h i s w h e n i t ' s c h a n g e d ? a s o f r i g h t n o w i t g e t s c a l l e d w h e n e v e r y o u e . g . l o c k t h e p h o n e w h i l e i t ' s i n c o m i n g r i n g i n g .
2017-01-26 16:05:41 +01:00
private func updateIsVideoEnabled ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
2017-01-31 21:28:01 +01:00
guard let call = self . call else {
return
}
guard let peerConnectionClient = self . peerConnectionClient else {
return
}
2017-01-26 17:33:42 +01:00
2017-01-31 21:28:01 +01:00
let shouldHaveLocalVideoTrack = self . shouldHaveLocalVideoTrack ( )
2017-01-26 17:33:42 +01:00
2018-08-23 16:37:34 +02:00
Logger . info ( " \( shouldHaveLocalVideoTrack ) " )
2017-01-26 17:33:42 +01:00
2017-01-31 21:28:01 +01:00
self . peerConnectionClient ? . setLocalVideoEnabled ( enabled : shouldHaveLocalVideoTrack )
2017-01-26 16:05:41 +01:00
2018-08-06 17:15:08 +02:00
var messageData : Data
do {
2018-08-08 19:08:47 +02:00
let videoStreamingStatusBuilder = WebRTCProtoVideoStreamingStatus . WebRTCProtoVideoStreamingStatusBuilder ( id : call . signalingId )
2018-08-06 17:15:08 +02:00
videoStreamingStatusBuilder . setEnabled ( shouldHaveLocalVideoTrack )
let dataBuilder = WebRTCProtoData . WebRTCProtoDataBuilder ( )
2018-08-08 19:08:47 +02:00
dataBuilder . setVideoStreamingStatus ( try videoStreamingStatusBuilder . build ( ) )
2018-08-06 17:15:08 +02:00
messageData = try dataBuilder . buildSerializedData ( )
} catch {
2018-08-23 16:37:34 +02:00
Logger . error ( " couldn't build proto " )
2018-08-06 17:15:08 +02:00
return
}
peerConnectionClient . sendDataChannelMessage ( data : messageData , description : " videoStreamingStatus " , isCritical : false )
2017-01-26 16:05:41 +01:00
}
// MARK: - O b s e r v e r s
// T h e o b s e r v e r - r e l a t e d m e t h o d s s h o u l d b e i n v o k e d o n t h e m a i n t h r e a d .
func addObserverAndSyncState ( observer : CallServiceObserver ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
observers . append ( Weak ( value : observer ) )
// S y n c h r o n i z e o b s e r v e r w i t h c u r r e n t c a l l s t a t e
2017-01-31 21:28:01 +01:00
let remoteVideoTrack = self . isRemoteVideoEnabled ? self . remoteVideoTrack : nil
2018-06-25 20:07:48 +02:00
observer . didUpdateVideoTracks ( call : self . call ,
localCaptureSession : self . localCaptureSession ,
2017-11-07 16:04:58 +01:00
remoteVideoTrack : remoteVideoTrack )
2017-01-26 16:05:41 +01:00
}
// T h e o b s e r v e r - r e l a t e d m e t h o d s s h o u l d b e i n v o k e d o n t h e m a i n t h r e a d .
func removeObserver ( _ observer : CallServiceObserver ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
while let index = observers . index ( where : { $0 . value = = = observer } ) {
observers . remove ( at : index )
}
}
// T h e o b s e r v e r - r e l a t e d m e t h o d s s h o u l d b e i n v o k e d o n t h e m a i n t h r e a d .
func removeAllObservers ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
observers = [ ]
}
2017-02-07 15:35:37 +01:00
private func fireDidUpdateVideoTracks ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-01-26 16:05:41 +01:00
2017-01-26 17:33:42 +01:00
let remoteVideoTrack = self . isRemoteVideoEnabled ? self . remoteVideoTrack : nil
2017-01-31 21:28:01 +01:00
for observer in observers {
2018-06-25 20:07:48 +02:00
observer . value ? . didUpdateVideoTracks ( call : self . call ,
localCaptureSession : self . localCaptureSession ,
2017-11-07 16:04:58 +01:00
remoteVideoTrack : remoteVideoTrack )
2017-01-30 21:54:31 +01:00
}
2017-02-07 15:35:37 +01:00
}
2017-08-03 01:07:53 +02:00
// MARK: C a l l V i e w C o n t r o l l e r T i m e r
var activeCallTimer : Timer ?
2017-08-03 16:49:37 +02:00
func startCallTimer ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-08-03 01:07:53 +02:00
2018-05-30 17:29:42 +02:00
stopAnyCallTimer ( )
assert ( self . activeCallTimer = = nil )
2017-08-03 01:07:53 +02:00
self . activeCallTimer = WeakTimer . scheduledTimer ( timeInterval : 1 , target : self , userInfo : nil , repeats : true ) { [ weak self ] timer in
guard let strongSelf = self else {
return
}
2017-08-03 16:26:22 +02:00
guard let call = strongSelf . call else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " call has since ended. Timer should have been invalidated. " )
2017-08-03 01:07:53 +02:00
timer . invalidate ( )
return
}
2017-08-03 16:49:37 +02:00
strongSelf . ensureCallScreenPresented ( call : call )
2017-08-03 01:07:53 +02:00
}
}
2017-08-03 16:49:37 +02:00
func ensureCallScreenPresented ( call : SignalCall ) {
2018-05-30 17:29:42 +02:00
guard let currentCall = self . call else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " obsolete call: \( call . identifiersForLogs ) " )
2018-05-30 17:29:42 +02:00
return
}
guard currentCall = = call else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " obsolete call: \( call . identifiersForLogs ) " )
2018-05-30 17:29:42 +02:00
return
}
2017-08-03 16:26:22 +02:00
guard let connectedDate = call . connectedDate else {
// I g n o r e ; c a l l h a s n ' t c o n n e c t e d y e t .
return
}
2018-03-08 00:09:07 +01:00
let kMaxViewPresentationDelay : Double = 5
2017-08-03 16:26:22 +02:00
guard fabs ( connectedDate . timeIntervalSinceNow ) > kMaxViewPresentationDelay else {
// I g n o r e ; c a l l c o n n e c t e d r e c e n t l y .
return
}
2018-06-27 01:35:21 +02:00
guard ! call . isTerminated else {
// T h e r e ' s a b r i e f w i n d o w b e t w e e n w h e n t h e c a l l V i e w C o n t r o l l e r i s r e m o v e d
// a n d w h e n t h i s t i m e r i s t e r m i n a t e d .
//
// W e d o n ' t w a n t t o f a i l a c a l l t h a t ' s a l r e a d y t e r m i n a t e d .
2018-08-23 16:37:34 +02:00
Logger . debug ( " ignoring screen protection check for already terminated call. " )
2018-06-27 01:35:21 +02:00
return
}
2018-04-18 18:09:42 +02:00
if ! OWSWindowManager . shared ( ) . hasCall ( ) {
2017-11-07 16:04:58 +01:00
OWSProdError ( OWSAnalyticsEvents . callServiceCallViewCouldNotPresent ( ) , file : #file , function : #function , line : #line )
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Call terminated due to missing call view. " )
2018-03-08 00:09:07 +01:00
self . handleFailedCall ( failedCall : call , error : CallError . assertionError ( description : " Call view didn't present after \( kMaxViewPresentationDelay ) seconds " ) )
2017-08-03 01:07:53 +02:00
return
}
}
2017-08-03 16:49:37 +02:00
func stopAnyCallTimer ( ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-08-03 01:07:53 +02:00
self . activeCallTimer ? . invalidate ( )
self . activeCallTimer = nil
}
2016-11-12 18:22:29 +01:00
}