2017-01-12 21:55:14 +01:00
|
|
|
//
|
2018-02-16 21:32:29 +01:00
|
|
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
2017-01-12 21:55:14 +01:00
|
|
|
//
|
2016-11-12 18:22:29 +01:00
|
|
|
|
|
|
|
import Foundation
|
2017-11-28 00:17:46 +01:00
|
|
|
import SignalServiceKit
|
2016-11-12 18:22:29 +01:00
|
|
|
|
|
|
|
enum CallState: String {
|
|
|
|
case idle
|
|
|
|
case dialing
|
|
|
|
case answering
|
|
|
|
case remoteRinging
|
|
|
|
case localRinging
|
|
|
|
case connected
|
2018-04-19 15:56:09 +02:00
|
|
|
case reconnecting
|
2016-11-12 18:22:29 +01:00
|
|
|
case localFailure // terminal
|
|
|
|
case localHangup // terminal
|
|
|
|
case remoteHangup // terminal
|
|
|
|
case remoteBusy // terminal
|
|
|
|
}
|
|
|
|
|
2017-02-08 01:37:05 +01:00
|
|
|
enum CallDirection {
|
|
|
|
case outgoing, incoming
|
|
|
|
}
|
|
|
|
|
2017-01-26 16:05:41 +01:00
|
|
|
// All Observer methods will be invoked from the main thread.
|
2017-01-19 15:38:50 +01:00
|
|
|
protocol CallObserver: class {
|
2017-01-09 15:28:04 +01:00
|
|
|
func stateDidChange(call: SignalCall, state: CallState)
|
2017-01-27 17:11:33 +01:00
|
|
|
func hasLocalVideoDidChange(call: SignalCall, hasLocalVideo: Bool)
|
2017-01-09 15:28:04 +01:00
|
|
|
func muteDidChange(call: SignalCall, isMuted: Bool)
|
2017-10-28 20:44:29 +02:00
|
|
|
func holdDidChange(call: SignalCall, isOnHold: Bool)
|
2017-07-13 21:37:01 +02:00
|
|
|
func audioSourceDidChange(call: SignalCall, audioSource: AudioSource?)
|
2017-01-09 15:28:04 +01:00
|
|
|
}
|
|
|
|
|
2016-11-12 18:22:29 +01:00
|
|
|
/**
|
|
|
|
* Data model for a WebRTC backed voice/video call.
|
2017-01-26 16:05:41 +01:00
|
|
|
*
|
2017-01-31 21:28:01 +01:00
|
|
|
* This class' state should only be accessed on the main queue.
|
2016-11-12 18:22:29 +01:00
|
|
|
*/
|
|
|
|
@objc class SignalCall: NSObject {
|
|
|
|
|
|
|
|
let TAG = "[SignalCall]"
|
|
|
|
|
2017-01-19 15:38:50 +01:00
|
|
|
var observers = [Weak<CallObserver>]()
|
2017-01-09 15:28:04 +01:00
|
|
|
let remotePhoneNumber: String
|
|
|
|
|
2018-02-17 02:41:41 +01:00
|
|
|
var isTerminated: Bool {
|
|
|
|
switch state {
|
|
|
|
case .localFailure, .localHangup, .remoteHangup, .remoteBusy:
|
|
|
|
return true
|
2018-04-19 15:56:09 +02:00
|
|
|
case .idle, .dialing, .answering, .remoteRinging, .localRinging, .connected, .reconnecting:
|
2018-02-17 02:41:41 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-09 15:28:04 +01:00
|
|
|
// Signal Service identifier for this Call. Used to coordinate the call across remote clients.
|
|
|
|
let signalingId: UInt64
|
|
|
|
|
2017-02-08 01:37:05 +01:00
|
|
|
let direction: CallDirection
|
|
|
|
|
2017-01-09 15:28:04 +01:00
|
|
|
// Distinguishes between calls locally, e.g. in CallKit
|
|
|
|
let localId: UUID
|
2017-01-26 16:05:41 +01:00
|
|
|
|
2017-06-21 20:55:08 +02:00
|
|
|
let thread: TSContactThread
|
|
|
|
|
2017-02-08 21:52:48 +01:00
|
|
|
var callRecord: TSCall? {
|
|
|
|
didSet {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-02-08 21:52:48 +01:00
|
|
|
assert(oldValue == nil)
|
|
|
|
|
2017-02-08 22:39:32 +01:00
|
|
|
updateCallRecordType()
|
2017-02-08 21:52:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-27 17:11:33 +01:00
|
|
|
var hasLocalVideo = false {
|
2017-01-18 23:29:47 +01:00
|
|
|
didSet {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-01-26 16:05:41 +01:00
|
|
|
|
2017-01-31 21:28:01 +01:00
|
|
|
for observer in observers {
|
|
|
|
observer.value?.hasLocalVideoDidChange(call: self, hasLocalVideo: hasLocalVideo)
|
2017-01-19 15:38:50 +01:00
|
|
|
}
|
2017-01-18 23:29:47 +01:00
|
|
|
}
|
|
|
|
}
|
2017-01-19 15:38:50 +01:00
|
|
|
|
2016-11-12 18:22:29 +01:00
|
|
|
var state: CallState {
|
|
|
|
didSet {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-06-22 00:06:29 +02:00
|
|
|
Logger.debug("\(TAG) state changed: \(oldValue) -> \(self.state) for call: \(self.identifiersForLogs)")
|
2017-01-13 23:25:28 +01:00
|
|
|
|
|
|
|
// Update connectedDate
|
2018-04-19 15:56:09 +02:00
|
|
|
if case .connected = self.state {
|
|
|
|
// if it's the first time we've connected (not a reconnect)
|
2017-01-13 23:25:28 +01:00
|
|
|
if connectedDate == nil {
|
|
|
|
connectedDate = NSDate()
|
|
|
|
}
|
|
|
|
}
|
2017-01-26 16:05:41 +01:00
|
|
|
|
2017-02-08 22:39:32 +01:00
|
|
|
updateCallRecordType()
|
|
|
|
|
2017-01-31 21:28:01 +01:00
|
|
|
for observer in observers {
|
|
|
|
observer.value?.stateDidChange(call: self, state: state)
|
2017-01-19 15:38:50 +01:00
|
|
|
}
|
2017-01-09 15:28:04 +01:00
|
|
|
}
|
|
|
|
}
|
2017-01-19 15:38:50 +01:00
|
|
|
|
2017-01-09 15:28:04 +01:00
|
|
|
var isMuted = false {
|
|
|
|
didSet {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-01-26 16:05:41 +01:00
|
|
|
|
|
|
|
Logger.debug("\(TAG) muted changed: \(oldValue) -> \(self.isMuted)")
|
|
|
|
|
2017-01-31 21:28:01 +01:00
|
|
|
for observer in observers {
|
|
|
|
observer.value?.muteDidChange(call: self, isMuted: isMuted)
|
2017-01-19 15:38:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-16 22:00:11 +01:00
|
|
|
let audioActivity: AudioActivity
|
|
|
|
|
2017-07-13 21:37:01 +02:00
|
|
|
var audioSource: AudioSource? = nil {
|
2017-01-19 15:38:50 +01:00
|
|
|
didSet {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
|
|
|
Logger.debug("\(TAG) audioSource changed: \(String(describing: oldValue)) -> \(String(describing: audioSource))")
|
2017-01-26 16:05:41 +01:00
|
|
|
|
2017-01-31 21:28:01 +01:00
|
|
|
for observer in observers {
|
2017-07-13 21:37:01 +02:00
|
|
|
observer.value?.audioSourceDidChange(call: self, audioSource: audioSource)
|
2017-01-19 15:38:50 +01:00
|
|
|
}
|
2016-11-12 18:22:29 +01:00
|
|
|
}
|
|
|
|
}
|
2017-01-26 16:05:41 +01:00
|
|
|
|
2017-07-13 21:37:01 +02:00
|
|
|
var isSpeakerphoneEnabled: Bool {
|
|
|
|
guard let audioSource = self.audioSource else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return audioSource.isBuiltInSpeaker
|
|
|
|
}
|
|
|
|
|
2017-10-28 20:44:29 +02:00
|
|
|
var isOnHold = false {
|
|
|
|
didSet {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-10-28 20:44:29 +02:00
|
|
|
Logger.debug("\(TAG) isOnHold changed: \(oldValue) -> \(self.isOnHold)")
|
|
|
|
|
|
|
|
for observer in observers {
|
|
|
|
observer.value?.holdDidChange(call: self, isOnHold: isOnHold)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-30 21:40:40 +01:00
|
|
|
|
2017-01-13 23:25:28 +01:00
|
|
|
var connectedDate: NSDate?
|
2016-11-12 18:22:29 +01:00
|
|
|
|
|
|
|
var error: CallError?
|
|
|
|
|
2017-01-19 15:38:50 +01:00
|
|
|
// MARK: Initializers and Factory Methods
|
|
|
|
|
2017-02-08 01:37:05 +01:00
|
|
|
init(direction: CallDirection, localId: UUID, signalingId: UInt64, state: CallState, remotePhoneNumber: String) {
|
|
|
|
self.direction = direction
|
2016-11-12 18:22:29 +01:00
|
|
|
self.localId = localId
|
|
|
|
self.signalingId = signalingId
|
|
|
|
self.state = state
|
|
|
|
self.remotePhoneNumber = remotePhoneNumber
|
2017-06-21 20:55:08 +02:00
|
|
|
self.thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
|
2018-02-16 22:00:11 +01:00
|
|
|
self.audioActivity = AudioActivity(audioDescription: "[SignalCall] with \(remotePhoneNumber)")
|
2017-06-21 20:55:08 +02:00
|
|
|
}
|
|
|
|
|
2017-06-22 00:06:29 +02:00
|
|
|
// A string containing the three identifiers for this call.
|
|
|
|
var identifiersForLogs: String {
|
2017-06-21 20:55:08 +02:00
|
|
|
return "{\(remotePhoneNumber), \(localId), \(signalingId)}"
|
2016-11-12 18:22:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class func outgoingCall(localId: UUID, remotePhoneNumber: String) -> SignalCall {
|
2017-02-08 01:37:05 +01:00
|
|
|
return SignalCall(direction: .outgoing, localId: localId, signalingId: newCallSignalingId(), state: .dialing, remotePhoneNumber: remotePhoneNumber)
|
2016-11-12 18:22:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class func incomingCall(localId: UUID, remotePhoneNumber: String, signalingId: UInt64) -> SignalCall {
|
2017-02-08 01:37:05 +01:00
|
|
|
return SignalCall(direction: .incoming, localId: localId, signalingId: signalingId, state: .answering, remotePhoneNumber: remotePhoneNumber)
|
2016-11-12 18:22:29 +01:00
|
|
|
}
|
|
|
|
|
2017-01-19 15:38:50 +01:00
|
|
|
// -
|
|
|
|
|
|
|
|
func addObserverAndSyncState(observer: CallObserver) {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-01-26 16:05:41 +01:00
|
|
|
|
2017-01-19 15:38:50 +01:00
|
|
|
observers.append(Weak(value: observer))
|
|
|
|
|
2017-01-31 21:28:01 +01:00
|
|
|
// Synchronize observer with current call state
|
|
|
|
observer.stateDidChange(call: self, state: state)
|
2017-01-19 15:38:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func removeObserver(_ observer: CallObserver) {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-01-26 16:05:41 +01:00
|
|
|
|
2017-01-19 15:38:50 +01:00
|
|
|
while let index = observers.index(where: { $0.value === observer }) {
|
|
|
|
observers.remove(at: index)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func removeAllObservers() {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-01-26 16:05:41 +01:00
|
|
|
|
2017-01-19 15:38:50 +01:00
|
|
|
observers = []
|
|
|
|
}
|
|
|
|
|
2017-02-08 22:39:32 +01:00
|
|
|
private func updateCallRecordType() {
|
2018-04-11 21:17:34 +02:00
|
|
|
SwiftAssertIsOnMainThread(#function)
|
2017-02-08 22:39:32 +01:00
|
|
|
|
|
|
|
guard let callRecord = self.callRecord else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark incomplete calls as completed if call has connected.
|
|
|
|
if state == .connected &&
|
|
|
|
callRecord.callType == RPRecentCallTypeOutgoingIncomplete {
|
|
|
|
callRecord.updateCallType(RPRecentCallTypeOutgoing)
|
|
|
|
}
|
|
|
|
if state == .connected &&
|
|
|
|
callRecord.callType == RPRecentCallTypeIncomingIncomplete {
|
|
|
|
callRecord.updateCallType(RPRecentCallTypeIncoming)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-12 18:22:29 +01:00
|
|
|
// MARK: Equatable
|
2017-01-19 15:38:50 +01:00
|
|
|
|
2016-11-12 18:22:29 +01:00
|
|
|
static func == (lhs: SignalCall, rhs: SignalCall) -> Bool {
|
2017-01-26 20:07:55 +01:00
|
|
|
return lhs.localId == rhs.localId
|
2016-11-12 18:22:29 +01:00
|
|
|
}
|
|
|
|
|
2017-01-12 21:55:14 +01:00
|
|
|
static func newCallSignalingId() -> UInt64 {
|
|
|
|
return UInt64.ows_random()
|
|
|
|
}
|
2017-01-13 23:25:28 +01:00
|
|
|
|
|
|
|
// This method should only be called when the call state is "connected".
|
|
|
|
func connectionDuration() -> TimeInterval {
|
|
|
|
return -connectedDate!.timeIntervalSinceNow
|
|
|
|
}
|
2016-11-12 18:22:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate extension UInt64 {
|
|
|
|
static func ows_random() -> UInt64 {
|
|
|
|
var random: UInt64 = 0
|
|
|
|
arc4random_buf(&random, MemoryLayout.size(ofValue: random))
|
|
|
|
return random
|
|
|
|
}
|
|
|
|
}
|