session-ios/Session/Signal/CallService.swift

1965 lines
83 KiB
Swift

////
//// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
////
//
//import Foundation
//import PromiseKit
//import WebRTC
//import SignalUtilitiesKit
//import SignalUtilitiesKit
//
///**
// * `CallService` is a global singleton that manages the state of WebRTC-backed Signal Calls
// * (as opposed to legacy "RedPhone Calls").
// *
// * It serves as a connection between the `CallUIAdapter` and the `PeerConnectionClient`.
// *
// * ## Signaling
// *
// * Signaling refers to the setup and tear down of the connection. Before the connection is established, this must happen
// * out of band (using Signal Service), but once the connection is established it's possible to publish updates
// * (like hangup) via the established channel.
// *
// * Signaling state is synchronized on the main thread and only mutated in the handleXXX family of methods.
// *
// * Following is a high level process of the exchange of messages that takes place during call signaling.
// *
// * ### 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
// *
// * | Caller | Callee |
// * +----------------------------+-------------------------+
// * Start outgoing call: `handleOutgoingCall`...
// --[SS.CallOffer]-->
// * ...and start generating ICE updates.
// * As ICE candidates are generated, `handleLocalAddedIceCandidate` is called.
// * and we *store* the ICE updates for later.
// *
// * Received call offer: `handleReceivedOffer`
// * Send call answer
// * <--[SS.CallAnswer]--
// * Start generating ICE updates.
// * As they are generated `handleLocalAddedIceCandidate` is called
// which immediately sends the ICE updates to the Caller.
// * <--[SS.ICEUpdate]-- (sent multiple times)
// *
// * Received CallAnswer: `handleReceivedAnswer`
// * So send any stored ice updates (and send future ones immediately)
// * --[SS.ICEUpdates]-->
// *
// * Once compatible ICE updates have been exchanged...
// * both parties: `handleIceConnected`
// *
// * Show remote ringing UI
// * Connect to offered Data Channel
// * Show incoming call UI.
// *
// * If callee answers Call
// * send connected message
// * <--[DC.ConnectedMesage]--
// * Received connected message
// * Show Call is connected.
// *
// * Hang up (this could equally be sent by the Callee)
// * --[DC.Hangup]-->
// * --[SS.Hangup]-->
// */
//
//public enum CallError: Error {
// case providerReset
// case assertionError(description: String)
// case disconnected
// case externalError(underlyingError: Error)
// case timeout(description: String)
// case obsoleteCall(description: String)
// case fatalError(description: String)
// case messageSendFailure(underlyingError: Error)
//}
//
//// Should be roughly synced with Android client for consistency
//private let connectingTimeoutSeconds: TimeInterval = 120
//
//// All Observer methods will be invoked from the main thread.
//protocol CallServiceObserver: class {
// /**
// * Fired whenever the call changes.
// */
// func didUpdateCall(call: SignalCall?)
//
// /**
// * Fired whenever the local or remote video track become active or inactive.
// */
// func didUpdateVideoTracks(call: SignalCall?,
// localCaptureSession: AVCaptureSession?,
// remoteVideoTrack: RTCVideoTrack?)
//}
//
//protocol SignalCallDataDelegate: class {
// func outgoingIceUpdateDidFail(call: SignalCall, error: Error)
//}
//
//// Gather all per-call state in one place.
//private class SignalCallData: NSObject {
//
// fileprivate weak var delegate: SignalCallDataDelegate?
//
// public let call: SignalCall
//
// // Used to coordinate promises across delegate methods
// let callConnectedPromise: Promise<Void>
// let callConnectedResolver: Resolver<Void>
//
// // Used to ensure any received ICE messages wait until the peer connection client is set up.
// let peerConnectionClientPromise: Promise<Void>
// let peerConnectionClientResolver: Resolver<Void>
//
// // Used to ensure CallOffer was sent before sending any ICE updates.
// let readyToSendIceUpdatesPromise: Promise<Void>
// let readyToSendIceUpdatesResolver: Resolver<Void>
//
// weak var localCaptureSession: AVCaptureSession? {
// didSet {
// AssertIsOnMainThread()
//
// Logger.info("")
// }
// }
//
// weak var remoteVideoTrack: RTCVideoTrack? {
// didSet {
// AssertIsOnMainThread()
//
// Logger.info("")
// }
// }
//
// var isRemoteVideoEnabled = false {
// didSet {
// AssertIsOnMainThread()
//
// Logger.info("\(isRemoteVideoEnabled)")
// }
// }
//
// var peerConnectionClient: PeerConnectionClient? {
// didSet {
// AssertIsOnMainThread()
//
// Logger.debug(".peerConnectionClient setter: \(oldValue != nil) -> \(peerConnectionClient != nil) \(String(describing: peerConnectionClient))")
// }
// }
//
// required init(call: SignalCall, delegate: SignalCallDataDelegate) {
// self.call = call
// self.delegate = delegate
//
// let (callConnectedPromise, callConnectedResolver) = Promise<Void>.pending()
// self.callConnectedPromise = callConnectedPromise
// self.callConnectedResolver = callConnectedResolver
//
// let (peerConnectionClientPromise, peerConnectionClientResolver) = Promise<Void>.pending()
// self.peerConnectionClientPromise = peerConnectionClientPromise
// self.peerConnectionClientResolver = peerConnectionClientResolver
//
// let (readyToSendIceUpdatesPromise, readyToSendIceUpdatesResolver) = Promise<Void>.pending()
// self.readyToSendIceUpdatesPromise = readyToSendIceUpdatesPromise
// self.readyToSendIceUpdatesResolver = readyToSendIceUpdatesResolver
//
// super.init()
// }
//
// deinit {
// Logger.debug("[SignalCallData] deinit")
// }
//
// // MARK: -
//
// public func terminate() {
// AssertIsOnMainThread()
//
// Logger.debug("")
//
// self.call.removeAllObservers()
//
// // In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
// // There is no harm in rejecting a previously fulfilled promise.
// self.callConnectedResolver.reject(CallError.obsoleteCall(description: "Terminating call"))
//
// // In case we're still waiting on the peer connection setup somewhere, we need to reject it to avoid a memory leak.
// // There is no harm in rejecting a previously fulfilled promise.
// self.peerConnectionClientResolver.reject(CallError.obsoleteCall(description: "Terminating call"))
//
// // In case we're still waiting on this promise somewhere, we need to reject it to avoid a memory leak.
// // There is no harm in rejecting a previously fulfilled promise.
// self.readyToSendIceUpdatesResolver.reject(CallError.obsoleteCall(description: "Terminating call"))
//
// peerConnectionClient?.terminate()
// Logger.debug("setting peerConnectionClient")
//
// outgoingIceUpdateQueue.removeAll()
// }
//
// // MARK: - Dependencies
//
// private var messageSender: MessageSender {
// return SSKEnvironment.shared.messageSender
// }
//
// // MARK: - Outgoing ICE updates.
//
// // Setting up a call involves sending many (currently 20+) ICE updates.
// // We send messages serially in order to preserve outgoing message order.
// // There are so many ICE updates per call that the cost of sending all of
// // those messages becomes significant. So we batch outgoing ICE updates,
// // making sure that we only have one outgoing ICE update message at a time.
// //
// // This variable should only be accessed on the main thread.
// private var outgoingIceUpdateQueue = [SSKProtoCallMessageIceUpdate]()
// private var outgoingIceUpdatesInFlight = false
//
// func sendOrEnqueue(outgoingIceUpdate iceUpdateProto: SSKProtoCallMessageIceUpdate) {
// AssertIsOnMainThread()
//
// outgoingIceUpdateQueue.append(iceUpdateProto)
//
// tryToSendIceUpdates()
// }
//
// private func tryToSendIceUpdates() {
// AssertIsOnMainThread()
//
// guard !outgoingIceUpdatesInFlight else {
// Logger.verbose("Enqueued outgoing ice update")
// return
// }
//
// let iceUpdateProtos = outgoingIceUpdateQueue
// guard iceUpdateProtos.count > 0 else {
// // Nothing in the queue.
// return
// }
//
// outgoingIceUpdateQueue.removeAll()
// outgoingIceUpdatesInFlight = true
//
// /**
// * 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.
// */
// let callMessage = OWSOutgoingCallMessage(thread: call.thread, iceUpdateMessages: iceUpdateProtos)
// let sendPromise = self.messageSender.sendPromise(message: callMessage)
// .done { [weak self] in
// AssertIsOnMainThread()
//
// guard let strongSelf = self else {
// return
// }
//
// strongSelf.outgoingIceUpdatesInFlight = false
// strongSelf.tryToSendIceUpdates()
// }.catch { [weak self] (error) in
// AssertIsOnMainThread()
//
// guard let strongSelf = self else {
// return
// }
//
// strongSelf.outgoingIceUpdatesInFlight = false
// strongSelf.delegate?.outgoingIceUpdateDidFail(call: strongSelf.call, error: error)
// }
// sendPromise.retainUntilComplete()
// }
//}
//
//// This class' state should only be accessed on the main queue.
//@objc public class CallService: NSObject, CallObserver, PeerConnectionClientDelegate, SignalCallDataDelegate {
//
// // MARK: - Properties
//
// var observers = [Weak<CallServiceObserver>]()
//
// // Exposed by environment.m
//
// @objc public var callUIAdapter: CallUIAdapter!
//
// // MARK: Class
//
// static let fallbackIceServer = RTCIceServer(urlStrings: ["stun:stun1.l.google.com:19302"])
//
// // MARK: Ivars
//
// fileprivate var callData: SignalCallData? {
// didSet {
// AssertIsOnMainThread()
//
// oldValue?.delegate = nil
// oldValue?.call.removeObserver(self)
// callData?.call.addObserverAndSyncState(observer: self)
//
// updateIsVideoEnabled()
//
// // Prevent device from sleeping while we have an active call.
// if oldValue != callData {
// if let oldValue = oldValue {
// DeviceSleepManager.sharedInstance.removeBlock(blockObject: oldValue)
// }
// if let callData = callData {
// DeviceSleepManager.sharedInstance.addBlock(blockObject: callData)
// self.startCallTimer()
// } else {
// stopAnyCallTimer()
// }
// }
//
// Logger.debug(".callData setter: \(oldValue?.call.identifiersForLogs as Optional) -> \(callData?.call.identifiersForLogs as Optional)")
//
// for observer in observers {
// observer.value?.didUpdateCall(call: callData?.call)
// }
// }
// }
//
// @objc
// var call: SignalCall? {
// get {
// AssertIsOnMainThread()
//
// return callData?.call
// }
// }
// var peerConnectionClient: PeerConnectionClient? {
// get {
// AssertIsOnMainThread()
//
// return callData?.peerConnectionClient
// }
// }
//
// weak var localCaptureSession: AVCaptureSession? {
// get {
// AssertIsOnMainThread()
//
// return callData?.localCaptureSession
// }
// }
//
// var remoteVideoTrack: RTCVideoTrack? {
// get {
// AssertIsOnMainThread()
//
// return callData?.remoteVideoTrack
// }
// }
// var isRemoteVideoEnabled: Bool {
// get {
// AssertIsOnMainThread()
//
// guard let callData = callData else {
// return false
// }
// return callData.isRemoteVideoEnabled
// }
// }
//
// @objc public override init() {
//
// super.init()
//
// SwiftSingletons.register(self)
//
// NotificationCenter.default.addObserver(self,
// selector: #selector(didEnterBackground),
// name: NSNotification.Name.OWSApplicationDidEnterBackground,
// object: nil)
// NotificationCenter.default.addObserver(self,
// selector: #selector(didBecomeActive),
// name: NSNotification.Name.OWSApplicationDidBecomeActive,
// object: nil)
// }
//
// deinit {
// NotificationCenter.default.removeObserver(self)
// }
//
// // MARK: - Dependencies
//
// private var contactsManager: OWSContactsManager {
// return Environment.shared.contactsManager
// }
//
// private var messageSender: MessageSender {
// return SSKEnvironment.shared.messageSender
// }
//
// private var accountManager: AccountManager {
// return AppEnvironment.shared.accountManager
// }
//
// private var notificationPresenter: NotificationPresenter {
// return AppEnvironment.shared.notificationPresenter
// }
//
// // MARK: - Notifications
//
// @objc func didEnterBackground() {
// AssertIsOnMainThread()
// self.updateIsVideoEnabled()
// }
//
// @objc func didBecomeActive() {
// AssertIsOnMainThread()
// self.updateIsVideoEnabled()
// }
//
// /**
// * Choose whether to use CallKit or a Notification backed interface for calling.
// */
// @objc public func createCallUIAdapter() {
// AssertIsOnMainThread()
//
// if self.call != nil {
// Logger.warn("ending current call in. Did user toggle callkit preference while in a call?")
// self.terminateCall()
// }
// self.callUIAdapter = CallUIAdapter(callService: self, contactsManager: self.contactsManager, notificationPresenter: self.notificationPresenter)
// }
//
// // MARK: - Service Actions
//
// /**
// * Initiate an outgoing call.
// */
// func handleOutgoingCall(_ call: SignalCall) -> Promise<Void> {
// AssertIsOnMainThread()
//
// let callId = call.signalingId
// BenchEventStart(title: "Outgoing Call Connection", eventId: "call-\(callId)")
//
// guard self.call == nil else {
// let errorDescription = "call was unexpectedly already set."
// Logger.error(errorDescription)
// call.state = .localFailure
// OWSProdError(OWSAnalyticsEvents.callServiceCallAlreadySet(), file: #file, function: #function, line: #line)
// return Promise(error: CallError.assertionError(description: errorDescription))
// }
//
// let callData = SignalCallData(call: call, delegate: self)
// self.callData = callData
//
// // MJK TODO remove this timestamp param
// let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), withCallNumber: call.remotePhoneNumber, callType: RPRecentCallTypeOutgoingIncomplete, in: call.thread)
// callRecord.save()
// call.callRecord = callRecord
//
// let promise = getIceServers()
// .then { iceServers -> Promise<HardenedRTCSessionDescription> in
// Logger.debug("got ice servers:\(iceServers) for call: \(call.identifiersForLogs)")
//
// guard self.call == call else {
// throw CallError.obsoleteCall(description: "obsolete call")
// }
//
// guard callData.peerConnectionClient == nil else {
// let errorDescription = "peerconnection was unexpectedly already set."
// Logger.error(errorDescription)
// OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionAlreadySet(), file: #file, function: #function, line: #line)
// throw CallError.assertionError(description: errorDescription)
// }
//
// let useTurnOnly = Environment.shared.preferences.doCallsHideIPAddress()
//
// let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .outgoing, useTurnOnly: useTurnOnly)
// Logger.debug("setting peerConnectionClient for call: \(call.identifiersForLogs)")
// callData.peerConnectionClient = peerConnectionClient
// callData.peerConnectionClientResolver.fulfill(())
//
// return peerConnectionClient.createOffer()
// }.then { (sessionDescription: HardenedRTCSessionDescription) -> Promise<Void> in
// guard self.call == call else {
// throw CallError.obsoleteCall(description: "obsolete call")
// }
// guard let peerConnectionClient = self.peerConnectionClient else {
// owsFailDebug("Missing peerConnectionClient")
// throw CallError.obsoleteCall(description: "Missing peerConnectionClient")
// }
//
// Logger.info("session description for outgoing call: \(call.identifiersForLogs), sdp: \(sessionDescription.logSafeDescription).")
//
// return
// peerConnectionClient.setLocalSessionDescription(sessionDescription)
// .then { _ -> Promise<Void> in
// do {
// let offerBuilder = SSKProtoCallMessageOffer.builder(id: call.signalingId,
// sessionDescription: sessionDescription.sdp)
// let callMessage = OWSOutgoingCallMessage(thread: call.thread, offerMessage: try offerBuilder.build())
// return self.messageSender.sendPromise(message: callMessage)
// } catch {
// owsFailDebug("Couldn't build proto")
// throw CallError.fatalError(description: "Couldn't build proto")
// }
// }
// }.then { () -> Promise<Void> in
// guard self.call == call else {
// throw CallError.obsoleteCall(description: "obsolete call")
// }
//
// // For outgoing calls, wait until call offer is sent before we send any ICE updates, to ensure message ordering for
// // clients that don't support receiving ICE updates before receiving the call offer.
// self.readyToSendIceUpdates(call: call)
//
// // Don't let the outgoing call ring forever. We don't support inbound ringing forever anyway.
// let timeout: Promise<Void> = after(seconds: connectingTimeoutSeconds).done {
// // This code will always be called, whether or not the call has timed out.
// // However, if the call has already connected, the `race` promise will have already been
// // fulfilled. Rejecting an already fulfilled promise is a no-op.
// throw CallError.timeout(description: "timed out waiting to receive call answer")
// }
//
// return race(timeout, callData.callConnectedPromise)
// }.done {
// Logger.info(self.call == call
// ? "outgoing call connected: \(call.identifiersForLogs)."
// : "obsolete outgoing call connected: \(call.identifiersForLogs).")
// }
//
// promise.catch { error in
// Logger.error("placing call \(call.identifiersForLogs) failed with error: \(error)")
//
// if let callError = error as? CallError {
// if case .timeout = callError {
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingOutgoing(), file: #file, function: #function, line: #line)
// }
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorOutgoingConnectionFailedInternal(), file: #file, function: #function, line: #line)
// self.handleFailedCall(failedCall: call, error: callError)
// } else {
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorOutgoingConnectionFailedExternal(), file: #file, function: #function, line: #line)
// let externalError = CallError.externalError(underlyingError: error)
// self.handleFailedCall(failedCall: call, error: externalError)
// }
// }.retainUntilComplete()
//
// return promise
// }
//
// func readyToSendIceUpdates(call: SignalCall) {
// AssertIsOnMainThread()
//
// guard let callData = self.callData else {
// self.handleFailedCall(failedCall: call, error: .obsoleteCall(description:"obsolete call"))
// return
// }
// guard callData.call == call else {
// Logger.warn("ignoring \(#function) for call other than current call")
// return
// }
//
// callData.readyToSendIceUpdatesResolver.fulfill(())
// }
//
// /**
// * Called by the call initiator after receiving a CallAnswer from the callee.
// */
// public func handleReceivedAnswer(thread: TSContactThread, callId: UInt64, sessionDescription: String) {
// AssertIsOnMainThread()
// Logger.info("received call answer for call: \(callId) thread: \(thread.contactIdentifier())")
//
// guard let call = self.call else {
// Logger.warn("ignoring obsolete call: \(callId)")
// return
// }
//
// guard call.signalingId == callId else {
// Logger.warn("ignoring mismatched call: \(callId) currentCall: \(call.signalingId)")
// return
// }
//
// guard let peerConnectionClient = self.peerConnectionClient else {
// OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file: #file, function: #function, line: #line)
// handleFailedCall(failedCall: call, error: CallError.assertionError(description: "peerConnectionClient was unexpectedly nil"))
// return
// }
//
// let sessionDescription = RTCSessionDescription(type: .answer, sdp: sessionDescription)
//
// peerConnectionClient.setRemoteSessionDescription(sessionDescription)
// .done {
// Logger.debug("successfully set remote description")
// }.catch { error in
// if let callError = error as? CallError {
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleReceivedErrorInternal(), file: #file, function: #function, line: #line)
// self.handleFailedCall(failedCall: call, error: callError)
// } else {
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleReceivedErrorExternal(), file: #file, function: #function, line: #line)
// let externalError = CallError.externalError(underlyingError: error)
// self.handleFailedCall(failedCall: call, error: externalError)
// }
// }.retainUntilComplete()
// }
//
// /**
// * User didn't answer incoming call
// */
// public func handleMissedCall(_ call: SignalCall) {
// AssertIsOnMainThread()
//
// if call.callRecord == nil {
// // MJK TODO remove this timestamp param
// call.callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(),
// withCallNumber: call.thread.contactIdentifier(),
// callType: RPRecentCallTypeIncomingMissed,
// in: call.thread)
// }
//
// guard let callRecord = call.callRecord else {
// handleFailedCall(failedCall: call, error: .assertionError(description: "callRecord was unexpectedly nil"))
// return
// }
//
// switch callRecord.callType {
// case RPRecentCallTypeIncomingMissed:
// callRecord.save()
// callUIAdapter.reportMissedCall(call)
// case RPRecentCallTypeIncomingIncomplete, RPRecentCallTypeIncoming:
// callRecord.updateCallType(RPRecentCallTypeIncomingMissed)
// callUIAdapter.reportMissedCall(call)
// case RPRecentCallTypeOutgoingIncomplete:
// callRecord.updateCallType(RPRecentCallTypeOutgoingMissed)
// case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity, RPRecentCallTypeIncomingDeclined, RPRecentCallTypeOutgoingMissed, RPRecentCallTypeOutgoing:
// owsFailDebug("unexpected RPRecentCallType: \(callRecord.callType)")
// callRecord.save()
// default:
// callRecord.save()
// owsFailDebug("unknown RPRecentCallType: \(callRecord.callType)")
// }
// }
//
// /**
// * Received a call while already in another call.
// */
// private func handleLocalBusyCall(_ call: SignalCall) {
// AssertIsOnMainThread()
// Logger.info("for call: \(call.identifiersForLogs) thread: \(call.thread.contactIdentifier())")
//
// do {
// let busyBuilder = SSKProtoCallMessageBusy.builder(id: call.signalingId)
// let callMessage = OWSOutgoingCallMessage(thread: call.thread, busyMessage: try busyBuilder.build())
// let sendPromise = messageSender.sendPromise(message: callMessage)
// sendPromise.retainUntilComplete()
//
// handleMissedCall(call)
// } catch {
// owsFailDebug("Couldn't build proto")
// }
// }
//
// /**
// * The callee was already in another call.
// */
// public func handleRemoteBusy(thread: TSContactThread, callId: UInt64) {
// AssertIsOnMainThread()
// Logger.info("for thread: \(thread.contactIdentifier())")
//
// guard let call = self.call else {
// Logger.warn("ignoring obsolete call: \(callId)")
// return
// }
//
// guard call.signalingId == callId else {
// Logger.warn("ignoring mismatched call: \(callId) currentCall: \(call.signalingId)")
// return
// }
//
// guard thread.contactIdentifier() == call.remotePhoneNumber else {
// Logger.warn("ignoring obsolete call")
// return
// }
//
// call.state = .remoteBusy
// assert(call.callRecord != nil)
// call.callRecord?.updateCallType(RPRecentCallTypeOutgoingMissed)
//
// callUIAdapter.remoteBusy(call)
// 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) {
// AssertIsOnMainThread()
//
// BenchEventStart(title: "Incoming Call Connection", eventId: "call-\(callId)")
//
// let newCall = SignalCall.incomingCall(localId: UUID(), remotePhoneNumber: thread.contactIdentifier(), signalingId: callId)
//
// Logger.info("receivedCallOffer: \(newCall.identifiersForLogs)")
//
// let untrustedIdentity = OWSIdentityManager.shared().untrustedIdentityForSending(toRecipientId: thread.contactIdentifier())
//
// guard untrustedIdentity == nil else {
// Logger.warn("missed a call due to untrusted identity: \(newCall.identifiersForLogs)")
//
// let callerName = self.contactsManager.displayName(forPhoneIdentifier: thread.contactIdentifier())
//
// switch untrustedIdentity!.verificationState {
// case .verified:
// owsFailDebug("shouldn't have missed a call due to untrusted identity if the identity is verified")
// self.notificationPresenter.presentMissedCall(newCall, callerName: callerName)
// case .default:
// self.notificationPresenter.presentMissedCallBecauseOfNewIdentity(call: newCall, callerName: callerName)
// case .noLongerVerified:
// self.notificationPresenter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: newCall, callerName: callerName)
// }
//
// // MJK TODO remove this timestamp param
// let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(),
// withCallNumber: thread.contactIdentifier(),
// callType: RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity,
// in: thread)
// assert(newCall.callRecord == nil)
// newCall.callRecord = callRecord
// callRecord.save()
//
// terminateCall()
//
// return
// }
//
// guard self.call == nil else {
// let existingCall = self.call!
//
// // TODO on iOS10+ we can use CallKit to swap calls rather than just returning busy immediately.
// Logger.info("receivedCallOffer: \(newCall.identifiersForLogs) but we're already in call: \(existingCall.identifiersForLogs)")
//
// handleLocalBusyCall(newCall)
//
// if existingCall.remotePhoneNumber == newCall.remotePhoneNumber {
// Logger.info("handling call from current call user as remote busy.: \(newCall.identifiersForLogs) but we're already in call: \(existingCall.identifiersForLogs)")
//
// // If we're receiving a new call offer from the user we already think we have a call with,
// // terminate our current call to get back to a known good state. If they call back, we'll
// // be ready.
// //
// // TODO: Auto-accept this incoming call if our current call was either a) outgoing or
// // b) never connected. There will be a bit of complexity around making sure that two
// // parties that call each other at the same time end up connected.
// switch existingCall.state {
// case .idle, .dialing, .remoteRinging:
// // If both users are trying to call each other at the same time,
// // both should see busy.
// handleRemoteBusy(thread: existingCall.thread, callId: existingCall.signalingId)
// case .answering, .localRinging, .connected, .localFailure, .localHangup, .remoteHangup, .remoteBusy, .reconnecting:
// // If one user calls another while the other has a "vestigial" call with
// // that same user, fail the old call.
// terminateCall()
// }
// }
//
// return
// }
//
// Logger.info("starting new call: \(newCall.identifiersForLogs)")
//
// let callData = SignalCallData(call: newCall, delegate: self)
// self.callData = callData
//
// var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: "\(#function)", completionBlock: { [weak self] status in
// AssertIsOnMainThread()
//
// guard status == .expired else {
// return
// }
//
// guard let strongSelf = self else {
// return
// }
// let timeout = CallError.timeout(description: "background task time ran out before call connected.")
//
// guard strongSelf.call == newCall else {
// Logger.warn("ignoring obsolete call")
// return
// }
// strongSelf.handleFailedCall(failedCall: newCall, error: timeout)
// })
//
// getIceServers()
// .then { (iceServers: [RTCIceServer]) -> Promise<HardenedRTCSessionDescription> in
// // FIXME for first time call recipients I think we'll see mic/camera permission requests here,
// // even though, from the users perspective, no incoming call is yet visible.
// guard self.call == newCall else {
// throw CallError.obsoleteCall(description: "getIceServers() response for obsolete call")
// }
// assert(self.peerConnectionClient == nil, "Unexpected PeerConnectionClient instance")
//
// // For contacts not stored in our system contacts, we assume they are an unknown caller, and we force
// // a TURN connection, so as not to reveal any connectivity information (IP/port) to the caller.
// let isUnknownCaller = !self.contactsManager.hasSignalAccount(forRecipientId: thread.contactIdentifier())
//
// let useTurnOnly = isUnknownCaller || Environment.shared.preferences.doCallsHideIPAddress()
//
// Logger.debug("setting peerConnectionClient for: \(newCall.identifiersForLogs)")
// let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .incoming, useTurnOnly: useTurnOnly)
// callData.peerConnectionClient = peerConnectionClient
// callData.peerConnectionClientResolver.fulfill(())
//
// let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription)
// let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
//
// // Find a sessionDescription compatible with my constraints and the remote sessionDescription
// return peerConnectionClient.negotiateSessionDescription(remoteDescription: offerSessionDescription, constraints: constraints)
// }.then { (negotiatedSessionDescription: HardenedRTCSessionDescription) -> Promise<Void> in
// guard self.call == newCall else {
// throw CallError.obsoleteCall(description: "negotiateSessionDescription() response for obsolete call")
// }
//
// Logger.info("session description for incoming call: \(newCall.identifiersForLogs), sdp: \(negotiatedSessionDescription.logSafeDescription).")
//
// do {
// let answerBuilder = SSKProtoCallMessageAnswer.builder(id: newCall.signalingId,
// sessionDescription: negotiatedSessionDescription.sdp)
// let callAnswerMessage = OWSOutgoingCallMessage(thread: thread, answerMessage: try answerBuilder.build())
//
// return self.messageSender.sendPromise(message: callAnswerMessage)
// } catch {
// owsFailDebug("Couldn't build proto")
// throw CallError.fatalError(description: "Couldn't build proto")
// }
// }.then { () -> Promise<Void> in
// guard self.call == newCall else {
// throw CallError.obsoleteCall(description: "sendPromise(message: ) response for obsolete call")
// }
// Logger.debug("successfully sent callAnswerMessage for: \(newCall.identifiersForLogs)")
//
// // There's nothing technically forbidding receiving ICE updates before receiving the CallAnswer, but this
// // a more intuitive ordering.
// self.readyToSendIceUpdates(call: newCall)
//
// let timeout: Promise<Void> = after(seconds: connectingTimeoutSeconds).done {
// // rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingIncoming(), file: #file, function: #function, line: #line)
// throw CallError.timeout(description: "timed out waiting for call to connect")
// }
//
// // This will be fulfilled (potentially) by the RTCDataChannel delegate method
// return race(callData.callConnectedPromise, timeout)
// }.done {
// Logger.info(self.call == newCall
// ? "incoming call connected: \(newCall.identifiersForLogs)."
// : "obsolete incoming call connected: \(newCall.identifiersForLogs).")
// }.recover { error in
// guard self.call == newCall else {
// Logger.debug("ignoring error: \(error) for obsolete call: \(newCall.identifiersForLogs).")
// return
// }
// if let callError = error as? CallError {
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorIncomingConnectionFailedInternal(), file: #file, function: #function, line: #line)
// self.handleFailedCall(failedCall: newCall, error: callError)
// } else {
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorIncomingConnectionFailedExternal(), file: #file, function: #function, line: #line)
// let externalError = CallError.externalError(underlyingError: error)
// self.handleFailedCall(failedCall: newCall, error: externalError)
// }
// }.ensure {
// Logger.debug("ending background task awaiting inbound call connection")
//
// assert(backgroundTask != nil)
// backgroundTask = nil
// }.retainUntilComplete()
// }
//
// /**
// * Remote client (could be caller or callee) sent us a connectivity update
// */
// public func handleRemoteAddedIceCandidate(thread: TSContactThread, callId: UInt64, sdp: String, lineIndex: Int32, mid: String) {
// AssertIsOnMainThread()
// Logger.verbose("callId: \(callId)")
//
// guard let callData = self.callData else {
// Logger.info("ignoring remote ice update, since there is no current call.")
// return
// }
//
// callData.peerConnectionClientPromise.done {
// AssertIsOnMainThread()
//
// guard let call = self.call else {
// Logger.warn("ignoring remote ice update for thread: \(String(describing: thread.uniqueId)) since there is no current call. Call already ended?")
// return
// }
//
// guard call.signalingId == callId else {
// Logger.warn("ignoring mismatched call: \(callId) currentCall: \(call.signalingId)")
// return
// }
//
// guard thread.contactIdentifier() == call.thread.contactIdentifier() else {
// Logger.warn("ignoring remote ice update for thread: \(String(describing: thread.uniqueId)) due to thread mismatch. Call already ended?")
// return
// }
//
// guard let peerConnectionClient = self.peerConnectionClient else {
// Logger.warn("ignoring remote ice update for thread: \(String(describing: thread.uniqueId)) since there is no current peerConnectionClient. Call already ended?")
// return
// }
//
// Logger.verbose("addRemoteIceCandidate")
// peerConnectionClient.addRemoteIceCandidate(RTCIceCandidate(sdp: sdp, sdpMLineIndex: lineIndex, sdpMid: mid))
// }.catch { error in
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleRemoteAddedIceCandidate(), file: #file, function: #function, line: #line)
// Logger.error("peerConnectionClientPromise failed with error: \(error)")
// }.retainUntilComplete()
// }
//
// /**
// * Local client (could be caller or callee) generated some connectivity information that we should send to the
// * remote client.
// */
// private func handleLocalAddedIceCandidate(_ iceCandidate: RTCIceCandidate) {
// AssertIsOnMainThread()
//
// guard let callData = self.callData else {
// 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."))
// return
// }
// let call = callData.call
//
// // Wait until we've sent the CallOffer before sending any ice updates for the call to ensure
// // intuitive message ordering for other clients.
// callData.readyToSendIceUpdatesPromise.done {
// guard call == self.call else {
// self.handleFailedCurrentCall(error: .obsoleteCall(description: "current call changed since we became ready to send ice updates"))
// return
// }
//
// guard call.state != .idle else {
// // This will only be called for the current peerConnectionClient, so
// // fail the current call.
// OWSProdError(OWSAnalyticsEvents.callServiceCallUnexpectedlyIdle(), file: #file, function: #function, line: #line)
// self.handleFailedCurrentCall(error: CallError.assertionError(description: "ignoring local ice candidate, since call is now idle."))
// return
// }
//
// guard let sdpMid = iceCandidate.sdpMid else {
// owsFailDebug("Missing sdpMid")
// throw CallError.fatalError(description: "Missing sdpMid")
// }
//
// guard iceCandidate.sdpMLineIndex < UINT32_MAX else {
// owsFailDebug("Invalid sdpMLineIndex")
// throw CallError.fatalError(description: "Invalid sdpMLineIndex")
// }
//
// Logger.info("sending ICE Candidate \(call.identifiersForLogs).")
//
// let iceUpdateProto: SSKProtoCallMessageIceUpdate
// do {
// let iceUpdateBuilder = SSKProtoCallMessageIceUpdate.builder(id: call.signalingId,
// sdpMid: sdpMid,
// sdpMlineIndex: UInt32(iceCandidate.sdpMLineIndex),
// sdp: iceCandidate.sdp)
// iceUpdateProto = try iceUpdateBuilder.build()
// } catch {
// owsFailDebug("Couldn't build proto")
// throw CallError.fatalError(description: "Couldn't build proto")
// }
//
// /**
// * 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.
// */
// callData.sendOrEnqueue(outgoingIceUpdate: iceUpdateProto)
// }.catch { error in
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalAddedIceCandidate(), file: #file, function: #function, line: #line)
// Logger.error("waitUntilReadyToSendIceUpdates failed with error: \(error)")
// }.retainUntilComplete()
// }
//
// /**
// * The clients can now communicate via WebRTC.
// *
// * Called by both caller and callee. Compatible ICE messages have been exchanged between the local and remote
// * client.
// */
// private func handleIceConnected() {
// AssertIsOnMainThread()
//
// guard let callData = self.callData else {
// // This will only be called for the current peerConnectionClient, so
// // fail the current call.
// OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "ignoring \(#function) since there is no current call."))
// return
// }
// let call = callData.call
// let callId = call.signalingId
//
// Logger.info("\(call.identifiersForLogs)")
//
// switch call.state {
// case .dialing:
// if call.state != .remoteRinging {
// BenchEventComplete(eventId: "call-\(callId)")
// }
// call.state = .remoteRinging
// case .answering:
// if call.state != .localRinging {
// BenchEventComplete(eventId: "call-\(callId)")
// }
// call.state = .localRinging
// self.callUIAdapter.reportIncomingCall(call, thread: call.thread)
// case .remoteRinging:
// Logger.info("call already ringing. Ignoring \(#function): \(call.identifiersForLogs).")
// case .connected:
// Logger.info("Call reconnected \(#function): \(call.identifiersForLogs).")
// case .reconnecting:
// call.state = .connected
// case .idle, .localRinging, .localFailure, .localHangup, .remoteHangup, .remoteBusy:
// owsFailDebug("unexpected call state: \(call.state): \(call.identifiersForLogs).")
// }
// }
//
// private func handleIceDisconnected() {
// AssertIsOnMainThread()
//
// guard let call = self.call else {
// // This will only be called for the current peerConnectionClient, so
// // fail the current call.
// OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "ignoring \(#function) since there is no current call."))
// return
// }
//
// Logger.info("\(call.identifiersForLogs).")
//
// switch call.state {
// case .remoteRinging, .localRinging:
// Logger.debug("disconnect while ringing... we'll keep ringing")
// case .connected:
// call.state = .reconnecting
// default:
// owsFailDebug("unexpected call state: \(call.state): \(call.identifiersForLogs).")
// }
// }
//
// /**
// * The remote client (caller or callee) ended the call.
// */
// public func handleRemoteHangup(thread: TSContactThread, callId: UInt64) {
// AssertIsOnMainThread()
// Logger.debug("")
//
// guard let call = self.call else {
// // This may happen if we hang up slightly before they hang up.
// handleFailedCurrentCall(error: .obsoleteCall(description:"call was unexpectedly nil"))
// return
// }
//
// guard call.signalingId == callId else {
// Logger.warn("ignoring mismatched call: \(callId) currentCall: \(call.signalingId)")
// return
// }
//
// guard thread.contactIdentifier() == call.thread.contactIdentifier() else {
// // This can safely be ignored.
// // We don't want to fail the current call because an old call was slow to send us the hangup message.
// Logger.warn("ignoring hangup for thread: \(thread.contactIdentifier()) which is not the current call: \(call.identifiersForLogs)")
// return
// }
//
// Logger.info("\(call.identifiersForLogs).")
//
// switch call.state {
// case .idle, .dialing, .answering, .localRinging, .localFailure, .remoteBusy, .remoteRinging:
// handleMissedCall(call)
// case .connected, .reconnecting, .localHangup, .remoteHangup:
// Logger.info("call is finished.")
// }
//
// call.state = .remoteHangup
// // Notify UI
// callUIAdapter.remoteDidHangupCall(call)
//
// // self.call is nil'd in `terminateCall`, so it's important we update it's state *before* calling `terminateCall`
// terminateCall()
// }
//
// /**
// * User chose to answer call referred to by call `localId`. Used by the Callee only.
// *
// * Used by notification actions which can't serialize a call object.
// */
// @objc public func handleAnswerCall(localId: UUID) {
// AssertIsOnMainThread()
//
// guard let call = self.call else {
// // This should never happen; return to a known good state.
// owsFailDebug("call was unexpectedly nil")
// OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "call was unexpectedly nil"))
// return
// }
//
// guard call.localId == localId else {
// // This should never happen; return to a known good state.
// owsFailDebug("callLocalId:\(localId) doesn't match current calls: \(call.localId)")
// OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "callLocalId:\(localId) doesn't match current calls: \(call.localId)"))
// return
// }
//
// self.handleAnswerCall(call)
// }
//
// /**
// * User chose to answer call referred to by call `localId`. Used by the Callee only.
// */
// public func handleAnswerCall(_ call: SignalCall) {
// AssertIsOnMainThread()
//
// Logger.debug("")
//
// guard let currentCallData = self.callData else {
// OWSProdError(OWSAnalyticsEvents.callServiceCallDataMissing(), file: #file, function: #function, line: #line)
// handleFailedCall(failedCall: call, error: CallError.assertionError(description: "callData unexpectedly nil"))
// return
// }
//
// guard call == currentCallData.call else {
// // This could conceivably happen if the other party of an old call was slow to send us their answer
// // and we've subsequently engaged in another call. Don't kill the current call, but just ignore it.
// Logger.warn("ignoring \(#function) for call other than current call")
// return
// }
//
// guard let peerConnectionClient = self.peerConnectionClient else {
// OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file: #file, function: #function, line: #line)
// handleFailedCall(failedCall: call, error: CallError.assertionError(description: "missing peerconnection client"))
// return
// }
//
// Logger.info("\(call.identifiersForLogs).")
//
// // MJK TODO remove this timestamp param
// let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), withCallNumber: call.remotePhoneNumber, callType: RPRecentCallTypeIncomingIncomplete, in: call.thread)
// callRecord.save()
// call.callRecord = callRecord
//
// var messageData: Data
// do {
// let connectedBuilder = WebRTCProtoConnected.builder(id: call.signalingId)
// let dataBuilder = WebRTCProtoData.builder()
// dataBuilder.setConnected(try connectedBuilder.build())
// messageData = try dataBuilder.buildSerializedData()
// } catch {
// handleFailedCall(failedCall: call, error: CallError.assertionError(description: "couldn't build proto"))
// return
// }
//
// peerConnectionClient.sendDataChannelMessage(data: messageData, description: "connected", isCritical: true)
//
// handleConnectedCall(currentCallData)
// }
//
// /**
// * 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.
// */
// private func handleConnectedCall(_ callData: SignalCallData) {
// AssertIsOnMainThread()
// Logger.info("")
//
// guard let peerConnectionClient = callData.peerConnectionClient else {
// OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file: #file, function: #function, line: #line)
// handleFailedCall(failedCall: call, error: CallError.assertionError(description: "peerConnectionClient unexpectedly nil"))
// return
// }
//
// Logger.info("handleConnectedCall: \(callData.call.identifiersForLogs).")
//
// // cancel connection timeout
// callData.callConnectedResolver.fulfill(())
//
// callData.call.state = .connected
//
// // We don't risk transmitting any media until the remote client has admitted to being connected.
// ensureAudioState(call: callData.call, peerConnectionClient: peerConnectionClient)
// peerConnectionClient.setLocalVideoEnabled(enabled: shouldHaveLocalVideoTrack())
// }
//
// /**
// * 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.
// */
// public func handleDeclineCall(localId: UUID) {
// AssertIsOnMainThread()
//
// guard let call = self.call else {
// // This should never happen; return to a known good state.
// owsFailDebug("call was unexpectedly nil")
// OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "call was unexpectedly nil"))
// return
// }
//
// guard call.localId == localId else {
// // This should never happen; return to a known good state.
// owsFailDebug("callLocalId:\(localId) doesn't match current calls: \(call.localId)")
// OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "callLocalId:\(localId) doesn't match current calls: \(call.localId)"))
// return
// }
//
// self.handleDeclineCall(call)
// }
//
// /**
// * Local user chose to decline the call vs. answering it.
// *
// * Incoming call only.
// */
// public func handleDeclineCall(_ call: SignalCall) {
// AssertIsOnMainThread()
//
// Logger.info("\(call.identifiersForLogs).")
//
// if let callRecord = call.callRecord {
// owsFailDebug("Not expecting callrecord to already be set")
// callRecord.updateCallType(RPRecentCallTypeIncomingDeclined)
// } else {
// // MJK TODO remove this timestamp param
// let callRecord = TSCall(timestamp: NSDate.ows_millisecondTimeStamp(), withCallNumber: call.remotePhoneNumber, callType: RPRecentCallTypeIncomingDeclined, in: call.thread)
// callRecord.save()
// call.callRecord = callRecord
// }
//
// // Currently we just handle this as a hangup. But we could offer more descriptive action. e.g. DataChannel message
// handleLocalHungupCall(call)
// }
//
// /**
// * Local user chose to end the call.
// *
// * Can be used for Incoming and Outgoing calls.
// */
// func handleLocalHungupCall(_ call: SignalCall) {
// AssertIsOnMainThread()
//
// guard let currentCall = self.call else {
// Logger.info("No current call. Other party hung up just before us.")
//
// // terminating the call might be redundant, but it shouldn't hurt.
// terminateCall()
// return
// }
//
// guard call == currentCall else {
// OWSProdError(OWSAnalyticsEvents.callServiceCallMismatch(), file: #file, function: #function, line: #line)
// handleFailedCall(failedCall: call, error: CallError.assertionError(description: "ignoring \(#function) for call other than current call"))
// return
// }
//
// Logger.info("\(call.identifiersForLogs).")
//
// call.state = .localHangup
//
// if let callRecord = call.callRecord {
// if callRecord.callType == RPRecentCallTypeOutgoingIncomplete {
// callRecord.updateCallType(RPRecentCallTypeOutgoingMissed)
// }
// } else {
// owsFailDebug("missing call record")
// }
//
// // TODO something like this lifted from Signal-Android.
// // this.accountManager.cancelInFlightRequests();
// // this.messageSender.cancelInFlightRequests();
//
// if let peerConnectionClient = self.peerConnectionClient {
// // Stop audio capture ASAP
// ensureAudioState(call: call, peerConnectionClient: peerConnectionClient)
//
// // If the call is connected, we can send the hangup via the data channel for faster hangup.
//
// var messageData: Data
// do {
// let hangupBuilder = WebRTCProtoHangup.builder(id: call.signalingId)
// let dataBuilder = WebRTCProtoData.builder()
// dataBuilder.setHangup(try hangupBuilder.build())
// messageData = try dataBuilder.buildSerializedData()
// } catch {
// handleFailedCall(failedCall: call, error: CallError.assertionError(description: "couldn't build proto"))
// return
// }
//
// peerConnectionClient.sendDataChannelMessage(data: messageData, description: "hangup", isCritical: true)
// } else {
// Logger.info("ending call before peer connection created. Device offline or quick hangup.")
// }
//
// // If the call hasn't started yet, we don't have a data channel to communicate the hang up. Use Signal Service Message.
// do {
// let hangupBuilder = SSKProtoCallMessageHangup.builder(id: call.signalingId)
// let callMessage = OWSOutgoingCallMessage(thread: call.thread, hangupMessage: try hangupBuilder.build())
//
// self.messageSender.sendPromise(message: callMessage)
// .done {
// Logger.debug("successfully sent hangup call message to \(call.thread.contactIdentifier())")
// }.catch { error in
// OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalHungupCall(), file: #file, function: #function, line: #line)
// Logger.error("failed to send hangup call message to \(call.thread.contactIdentifier()) with error: \(error)")
// }.retainUntilComplete()
//
// terminateCall()
// } catch {
// handleFailedCall(failedCall: call, error: CallError.assertionError(description: "couldn't build proto"))
// }
// }
//
// /**
// * Local user toggled to mute audio.
// *
// * Can be used for Incoming and Outgoing calls.
// */
// func setIsMuted(call: SignalCall, isMuted: Bool) {
// AssertIsOnMainThread()
//
// guard call == self.call else {
// // This can happen after a call has ended. Reproducible on iOS11, when the other party ends the call.
// Logger.info("ignoring mute request for obsolete call")
// return
// }
//
// call.isMuted = isMuted
//
// guard let peerConnectionClient = self.peerConnectionClient else {
// // The peer connection might not be created yet.
// return
// }
//
// 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) {
// AssertIsOnMainThread()
//
// guard call == self.call else {
// Logger.info("ignoring held request for obsolete call")
// return
// }
//
// call.isOnHold = isOnHold
//
// guard let peerConnectionClient = self.peerConnectionClient else {
// // The peer connection might not be created yet.
// 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
// }
// guard !call.isOnHold else {
// peerConnectionClient.setAudioEnabled(enabled: false)
// return
// }
//
// peerConnectionClient.setAudioEnabled(enabled: true)
// }
//
// /**
// * Local user toggled video.
// *
// * Can be used for Incoming and Outgoing calls.
// */
// func setHasLocalVideo(hasLocalVideo: Bool) {
// AssertIsOnMainThread()
//
// guard let frontmostViewController = UIApplication.shared.frontmostViewController else {
// owsFailDebug("could not identify frontmostViewController")
// return
// }
//
// frontmostViewController.ows_ask(forCameraPermissions: { [weak self] granted in
// guard let strongSelf = self else {
// return
// }
//
// if (granted) {
// // Success callback; camera permissions are granted.
// strongSelf.setHasLocalVideoWithCameraPermissions(hasLocalVideo: hasLocalVideo)
// } else {
// // Failed callback; camera permissions are _NOT_ granted.
//
// // We don't need to worry about the user granting or remoting this permission
// // during a call while the app is in the background, because changing this
// // permission kills the app.
// OWSAlerts.showAlert(title: NSLocalizedString("MISSING_CAMERA_PERMISSION_TITLE", comment: "Alert title when camera is not authorized"),
// message: NSLocalizedString("MISSING_CAMERA_PERMISSION_MESSAGE", comment: "Alert body when camera is not authorized"))
// }
// })
// }
//
// private func setHasLocalVideoWithCameraPermissions(hasLocalVideo: Bool) {
// AssertIsOnMainThread()
//
// guard let call = self.call else {
// // This can happen if you toggle local video right after
// // the other user ends the call.
// Logger.debug("Ignoring event from obsolete call")
// return
// }
//
// call.hasLocalVideo = hasLocalVideo
//
// guard let peerConnectionClient = self.peerConnectionClient else {
// // The peer connection might not be created yet.
// return
// }
//
// if call.state == .connected {
// peerConnectionClient.setLocalVideoEnabled(enabled: shouldHaveLocalVideoTrack())
// }
// }
//
// @objc
// func handleCallKitStartVideo() {
// AssertIsOnMainThread()
//
// self.setHasLocalVideo(hasLocalVideo: true)
// }
//
// func setCameraSource(call: SignalCall, isUsingFrontCamera: Bool) {
// AssertIsOnMainThread()
//
// guard let peerConnectionClient = self.peerConnectionClient else {
// return
// }
//
// peerConnectionClient.setCameraSource(isUsingFrontCamera: isUsingFrontCamera)
// }
//
// /**
// * Local client received a message on the WebRTC data channel.
// *
// * 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
// * action is idemptotent, there's no harm done.
// *
// * Used by both Incoming and Outgoing calls.
// */
// private func handleDataChannelMessage(_ message: WebRTCProtoData) {
// AssertIsOnMainThread()
//
// guard let callData = self.callData else {
// // This should never happen; return to a known good state.
// owsFailDebug("received data message, but there is no current call. Ignoring.")
// OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "received data message, but there is no current call. Ignoring."))
// return
// }
// let call = callData.call
//
// if let connected = message.connected {
// Logger.debug("remote participant sent Connected via data channel: \(call.identifiersForLogs).")
//
// guard connected.id == call.signalingId else {
// // This should never happen; return to a known good state.
// owsFailDebug("received connected message for call with id:\(connected.id) but current call has id:\(call.signalingId)")
// OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "received connected message for call with id:\(connected.id) but current call has id:\(call.signalingId)"))
// return
// }
//
// self.callUIAdapter.recipientAcceptedCall(call)
// handleConnectedCall(callData)
//
// } else if let hangup = message.hangup {
// Logger.debug("remote participant sent Hangup via data channel: \(call.identifiersForLogs).")
//
// guard hangup.id == call.signalingId else {
// // This should never happen; return to a known good state.
// owsFailDebug("received hangup message for call with id:\(hangup.id) but current call has id:\(call.signalingId)")
// OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file: #file, function: #function, line: #line)
// handleFailedCurrentCall(error: CallError.assertionError(description: "received hangup message for call with id:\(hangup.id) but current call has id:\(call.signalingId)"))
// return
// }
//
// handleRemoteHangup(thread: call.thread, callId: hangup.id)
// } else if let videoStreamingStatus = message.videoStreamingStatus {
// Logger.debug("remote participant sent VideoStreamingStatus via data channel: \(call.identifiersForLogs).")
//
// callData.isRemoteVideoEnabled = videoStreamingStatus.enabled
// self.fireDidUpdateVideoTracks()
// } else {
// Logger.info("received unknown or empty DataChannelMessage: \(call.identifiersForLogs).")
// }
// }
//
// // MARK: - PeerConnectionClientDelegate
//
// /**
// * The connection has been established. The clients can now communicate.
// */
// internal func peerConnectionClientIceConnected(_ peerConnectionClient: PeerConnectionClient) {
// AssertIsOnMainThread()
//
// guard peerConnectionClient == self.peerConnectionClient else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
//
// self.handleIceConnected()
// }
//
// func peerConnectionClientIceDisconnected(_ peerconnectionClient: PeerConnectionClient) {
// AssertIsOnMainThread()
//
// guard peerConnectionClient == self.peerConnectionClient else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
//
// self.handleIceDisconnected()
// }
//
// /**
// * The connection failed to establish. The clients will not be able to communicate.
// */
// internal func peerConnectionClientIceFailed(_ peerConnectionClient: PeerConnectionClient) {
// AssertIsOnMainThread()
//
// guard peerConnectionClient == self.peerConnectionClient else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
//
// // Return to a known good state.
// self.handleFailedCurrentCall(error: CallError.disconnected)
// }
//
// /**
// * 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.
// */
// internal func peerConnectionClient(_ peerConnectionClient: PeerConnectionClient, addedLocalIceCandidate iceCandidate: RTCIceCandidate) {
// AssertIsOnMainThread()
//
// guard peerConnectionClient == self.peerConnectionClient else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
//
// self.handleLocalAddedIceCandidate(iceCandidate)
// }
//
// /**
// * Once the peerconnection is established, we can receive messages via the data channel, and notify the delegate.
// */
// internal func peerConnectionClient(_ peerConnectionClient: PeerConnectionClient, received dataChannelMessage: WebRTCProtoData) {
// AssertIsOnMainThread()
//
// guard peerConnectionClient == self.peerConnectionClient else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
//
// self.handleDataChannelMessage(dataChannelMessage)
// }
//
// internal func peerConnectionClient(_ peerConnectionClient: PeerConnectionClient, didUpdateLocalVideoCaptureSession captureSession: AVCaptureSession?) {
// AssertIsOnMainThread()
//
// guard peerConnectionClient == self.peerConnectionClient else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
// guard let callData = callData else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
//
// callData.localCaptureSession = captureSession
// fireDidUpdateVideoTracks()
// }
//
// internal func peerConnectionClient(_ peerConnectionClient: PeerConnectionClient, didUpdateRemoteVideoTrack videoTrack: RTCVideoTrack?) {
// AssertIsOnMainThread()
//
// guard peerConnectionClient == self.peerConnectionClient else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
// guard let callData = callData else {
// Logger.debug("Ignoring event from obsolete peerConnectionClient")
// return
// }
//
// callData.remoteVideoTrack = videoTrack
// fireDidUpdateVideoTracks()
// }
//
// // MARK: -
//
// /**
// * 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.
// */
// private func getIceServers() -> Promise<[RTCIceServer]> {
//
// return self.accountManager.getTurnServerInfo()
// .map(on: DispatchQueue.global()) { turnServerInfo -> [RTCIceServer] in
// Logger.debug("got turn server urls: \(turnServerInfo.urls)")
//
// return turnServerInfo.urls.map { url in
// if url.hasPrefix("turn") {
// // Only "turn:" servers require authentication. Don't include the credentials to other ICE servers
// // as 1.) they aren't used, and 2.) the non-turn servers might not be under our control.
// // e.g. we use a public fallback STUN server.
// return RTCIceServer(urlStrings: [url], username: turnServerInfo.username, credential: turnServerInfo.password)
// } else {
// return RTCIceServer(urlStrings: [url])
// }
// } + [CallService.fallbackIceServer]
// }.recover(on: DispatchQueue.global()) { (error: Error) -> Guarantee<[RTCIceServer]> in
// Logger.error("fetching ICE servers failed with error: \(error)")
// Logger.warn("using fallback ICE Servers")
//
// return Guarantee.value([CallService.fallbackIceServer])
// }
// }
//
// // This method should be called when either: a) we know or assume that
// // the error is related to the current call. b) the error is so serious
// // that we want to terminate the current call (if any) in order to
// // return to a known good state.
// public func handleFailedCurrentCall(error: CallError) {
// Logger.debug("")
//
// // Return to a known good state by ending the current call, if any.
// handleFailedCall(failedCall: self.call, error: error)
// }
//
// // This method should be called when a fatal error occurred for a call.
// //
// // * If we know which call it was, we should update that call's state
// // to reflect the error.
// // * IFF that call is the current call, we want to terminate it.
// public func handleFailedCall(failedCall: SignalCall?, error: CallError) {
// AssertIsOnMainThread()
//
// if case CallError.assertionError(description: let description) = error {
// owsFailDebug(description)
// }
//
// if let failedCall = failedCall {
//
// switch failedCall.state {
// case .answering, .localRinging:
// assert(failedCall.callRecord == nil)
// // call failed before any call record could be created, make one now.
// handleMissedCall(failedCall)
// default:
// assert(failedCall.callRecord != nil)
// }
//
// // It's essential to set call.state before terminateCall, because terminateCall nils self.call
// failedCall.error = error
// failedCall.state = .localFailure
// self.callUIAdapter.failCall(failedCall, error: error)
//
// // Only terminate the current call if the error pertains to the current call.
// guard failedCall == self.call else {
// Logger.debug("ignoring obsolete call: \(failedCall.identifiersForLogs).")
// return
// }
//
// Logger.error("call: \(failedCall.identifiersForLogs) failed with error: \(error)")
// } else {
// Logger.error("unknown call failed with error: \(error)")
// }
//
// // Only terminate the call if it is the current call.
// terminateCall()
// }
//
// /**
// * Clean up any existing call state and get ready to receive a new call.
// */
// private func terminateCall() {
// AssertIsOnMainThread()
//
// Logger.debug("")
//
// let currentCallData = self.callData
// self.callData = nil
//
// currentCallData?.terminate()
//
// self.callUIAdapter.didTerminateCall(currentCallData?.call)
//
// fireDidUpdateVideoTracks()
//
// // Apparently WebRTC will sometimes disable device orientation notifications.
// // After every call ends, we need to ensure they are enabled.
// UIDevice.current.beginGeneratingDeviceOrientationNotifications()
// }
//
// // MARK: - CallObserver
//
// internal func stateDidChange(call: SignalCall, state: CallState) {
// AssertIsOnMainThread()
// Logger.info("\(state)")
//
// updateIsVideoEnabled()
// }
//
// internal func hasLocalVideoDidChange(call: SignalCall, hasLocalVideo: Bool) {
// AssertIsOnMainThread()
// Logger.info("\(hasLocalVideo)")
//
// self.updateIsVideoEnabled()
// }
//
// internal func muteDidChange(call: SignalCall, isMuted: Bool) {
// AssertIsOnMainThread()
// // Do nothing
// }
//
// internal func holdDidChange(call: SignalCall, isOnHold: Bool) {
// AssertIsOnMainThread()
// // Do nothing
// }
//
// internal func audioSourceDidChange(call: SignalCall, audioSource: AudioSource?) {
// AssertIsOnMainThread()
// // Do nothing
// }
//
// // MARK: - Video
//
// private func shouldHaveLocalVideoTrack() -> Bool {
// AssertIsOnMainThread()
//
// guard let call = self.call else {
// return false
// }
//
// // The iOS simulator doesn't provide any sort of camera capture
// // support or emulation (http://goo.gl/rHAnC1) so don't bother
// // trying to open a local stream.
// return (!Platform.isSimulator &&
// UIApplication.shared.applicationState != .background &&
// call.state == .connected &&
// call.hasLocalVideo)
// }
//
// //TODO only fire this when it's changed? as of right now it gets called whenever you e.g. lock the phone while it's incoming ringing.
// private func updateIsVideoEnabled() {
// AssertIsOnMainThread()
//
// guard let call = self.call else {
// return
// }
// guard let peerConnectionClient = self.peerConnectionClient else {
// return
// }
//
// let shouldHaveLocalVideoTrack = self.shouldHaveLocalVideoTrack()
//
// Logger.info("\(shouldHaveLocalVideoTrack)")
//
// self.peerConnectionClient?.setLocalVideoEnabled(enabled: shouldHaveLocalVideoTrack)
//
// var messageData: Data
// do {
// let videoStreamingStatusBuilder = WebRTCProtoVideoStreamingStatus.builder(id: call.signalingId)
// videoStreamingStatusBuilder.setEnabled(shouldHaveLocalVideoTrack)
// let dataBuilder = WebRTCProtoData.builder()
// dataBuilder.setVideoStreamingStatus(try videoStreamingStatusBuilder.build())
// messageData = try dataBuilder.buildSerializedData()
// } catch {
// Logger.error("couldn't build proto")
// return
// }
//
// peerConnectionClient.sendDataChannelMessage(data: messageData, description: "videoStreamingStatus", isCritical: false)
// }
//
// // MARK: - Observers
//
// // The observer-related methods should be invoked on the main thread.
// func addObserverAndSyncState(observer: CallServiceObserver) {
// AssertIsOnMainThread()
//
// observers.append(Weak(value: observer))
//
// // Synchronize observer with current call state
// let remoteVideoTrack = self.isRemoteVideoEnabled ? self.remoteVideoTrack : nil
// observer.didUpdateVideoTracks(call: self.call,
// localCaptureSession: self.localCaptureSession,
// remoteVideoTrack: remoteVideoTrack)
// }
//
// // The observer-related methods should be invoked on the main thread.
// func removeObserver(_ observer: CallServiceObserver) {
// AssertIsOnMainThread()
//
// while let index = observers.firstIndex(where: { $0.value === observer }) {
// observers.remove(at: index)
// }
// }
//
// // The observer-related methods should be invoked on the main thread.
// func removeAllObservers() {
// AssertIsOnMainThread()
//
// observers = []
// }
//
// private func fireDidUpdateVideoTracks() {
// AssertIsOnMainThread()
//
// let remoteVideoTrack = self.isRemoteVideoEnabled ? self.remoteVideoTrack : nil
// for observer in observers {
// observer.value?.didUpdateVideoTracks(call: self.call,
// localCaptureSession: self.localCaptureSession,
// remoteVideoTrack: remoteVideoTrack)
// }
// }
//
// // MARK: CallViewController Timer
//
// var activeCallTimer: Timer?
// func startCallTimer() {
// AssertIsOnMainThread()
//
// stopAnyCallTimer()
// assert(self.activeCallTimer == nil)
//
// self.activeCallTimer = WeakTimer.scheduledTimer(timeInterval: 1, target: self, userInfo: nil, repeats: true) { [weak self] timer in
// guard let strongSelf = self else {
// return
// }
//
// guard let call = strongSelf.call else {
// owsFailDebug("call has since ended. Timer should have been invalidated.")
// timer.invalidate()
// return
// }
//
// strongSelf.ensureCallScreenPresented(call: call)
// }
// }
//
// func ensureCallScreenPresented(call: SignalCall) {
// guard let currentCall = self.call else {
// owsFailDebug("obsolete call: \(call.identifiersForLogs)")
// return
// }
// guard currentCall == call else {
// owsFailDebug("obsolete call: \(call.identifiersForLogs)")
// return
// }
//
// guard let connectedDate = call.connectedDate else {
// // Ignore; call hasn't connected yet.
// return
// }
//
// let kMaxViewPresentationDelay: Double = 5
// guard fabs(connectedDate.timeIntervalSinceNow) > kMaxViewPresentationDelay else {
// // Ignore; call connected recently.
// return
// }
//
// guard !call.isTerminated else {
// // There's a brief window between when the callViewController is removed
// // and when this timer is terminated.
// //
// // We don't want to fail a call that's already terminated.
// Logger.debug("ignoring screen protection check for already terminated call.")
// return
// }
//
// if !OWSWindowManager.shared().hasCall() {
// OWSProdError(OWSAnalyticsEvents.callServiceCallViewCouldNotPresent(), file: #file, function: #function, line: #line)
// owsFailDebug("Call terminated due to missing call view.")
// self.handleFailedCall(failedCall: call, error: CallError.assertionError(description: "Call view didn't present after \(kMaxViewPresentationDelay) seconds"))
// return
// }
// }
//
// func stopAnyCallTimer() {
// AssertIsOnMainThread()
//
// self.activeCallTimer?.invalidate()
// self.activeCallTimer = nil
// }
//
// // MARK: - SignalCallDataDelegate
//
// func outgoingIceUpdateDidFail(call: SignalCall, error: Error) {
// AssertIsOnMainThread()
//
// guard self.call == call else {
// Logger.warn("obsolete call")
// return
// }
//
// handleFailedCall(failedCall: call, error: CallError.messageSendFailure(underlyingError: error))
// }
//}
//
//extension RPRecentCallType: CustomStringConvertible {
// public var description: String {
// switch self {
// case RPRecentCallTypeIncoming:
// return "RPRecentCallTypeIncoming"
// case RPRecentCallTypeOutgoing:
// return "RPRecentCallTypeOutgoing"
// case RPRecentCallTypeIncomingMissed:
// return "RPRecentCallTypeIncomingMissed"
// case RPRecentCallTypeOutgoingIncomplete:
// return "RPRecentCallTypeOutgoingIncomplete"
// case RPRecentCallTypeIncomingIncomplete:
// return "RPRecentCallTypeIncomingIncomplete"
// case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity:
// return "RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity"
// case RPRecentCallTypeIncomingDeclined:
// return "RPRecentCallTypeIncomingDeclined"
// case RPRecentCallTypeOutgoingMissed:
// return "RPRecentCallTypeOutgoingMissed"
// default:
// owsFailDebug("unexpected RPRecentCallType: \(self)")
// return "RPRecentCallTypeUnknown"
// }
// }
//}