Antidote/Antidote/CallManagement/ProviderDelegate.swift

239 lines
7.6 KiB
Swift

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// ProviderDelegate.swift
// Hotline
//
// Created by Steve Baker on 10/27/17.
// Copyright © 2017 Razeware LLC. All rights reserved.
//
import AVFoundation
import CallKit
import os
class ProviderDelegate: NSObject {
fileprivate let callManager: CallManager
fileprivate let provider: CXProvider
init(callManager: CallManager) {
os_log("ProviderDelegate:init")
self.callManager = callManager
provider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
// static var belongs to the type
// subclasses can't override static
static var providerConfiguration: CXProviderConfiguration {
// initialize
let providerConfiguration = CXProviderConfiguration(localizedName: "Antidote")
// set call capabilities
providerConfiguration.supportsVideo = true
providerConfiguration.maximumCallsPerCallGroup = 2 // Signal Messenger seems to think 2 is needed
providerConfiguration.supportedHandleTypes = [.generic]
return providerConfiguration
}
func endIncomingCalls() {
os_log("ProviderDelegate:endIncomingCalls")
for call in callManager.calls {
os_log("ProviderDelegate:endcall")
provider.reportCall(with: call.uuid, endedAt: Date(), reason: .remoteEnded)
call.end()
}
callManager.removeAllCalls()
}
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
os_log("ProviderDelegate:reportIncomingCall")
// prepare update to send to system
let update = CXCallUpdate()
// add call metadata
update.remoteHandle = CXHandle(type: .generic, value: handle)
update.hasVideo = hasVideo
// use provider to notify system
provider.reportNewIncomingCall(with: uuid, update: update) { error in
// now we are inside reportNewIncomingCall's final argument, a completion block
if error == nil {
// no error, so add call
let call = Call(uuid: uuid, handle: handle)
self.callManager.add(call: call)
}
// execute "completion", the final argument that was passed to outer method reportIncomingCall
// execute if it isn't nil
completion?(error as NSError?)
}
}
}
extension ProviderDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
os_log("ProviderDelegate:providerDidReset")
// stopAudio()
for call in callManager.calls {
call.end()
}
callManager.removeAllCalls()
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
os_log("cc:ProviderDelegate:didActivate")
print("cc:ProviderDelegate:didActivate %@", audioSession)
// HINT: audio session has to be started here!
// also answer Tox Call -------------
// -- HaXX0r --
// -- HaXX0r --
// -- HaXX0r --
let coord = AppDelegate.shared.coordinator.activeCoordinator
let runcoord = coord as! RunningCoordinator
runcoord.activeSessionCoordinator?.callCoordinator.answerCall(enableVideo: false)
// -- HaXX0r --
// -- HaXX0r --
// -- HaXX0r --
// also answer Tox Call -------------
// startAudio()
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
os_log("cc:ProviderDelegate:call-answer %@", action)
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
// HINT: audio session has to be configured here!
configureAudioSession()
os_log("cc:ProviderDelegate:call-answer:answer()")
call.answer()
// when processing an action, app should fulfill it or fail
os_log("cc:ProviderDelegate:call-answer:fulfill()")
action.fulfill()
}
func configureAudioSession()
{
os_log("cc:ProviderDelegate:configureAudioSession:start")
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
os_log("cc:ProviderDelegate:configureAudioSession:try_001")
try session.setMode(AVAudioSessionModeVoiceChat)
os_log("cc:ProviderDelegate:configureAudioSession:try_002")
// try session.setActive(true)
// os_log("cc:ProviderDelegate:configureAudioSession:try_003")
} catch (let error) {
os_log("cc:ProviderDelegate:configureAudioSession:EE_01")
print("cc:ProviderDelegate:configureAudioSession:Error while configuring audio session: \(error)")
}
os_log("ProviderDelegate:configureAudioSession:end")
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
os_log("ProviderDelegate:call-end %@", action)
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
// also decline Tox Call -------------
// -- HaXX0r --
// -- HaXX0r --
// -- HaXX0r --
let coord = AppDelegate.shared.coordinator.activeCoordinator
let runcoord = coord as! RunningCoordinator
runcoord.activeSessionCoordinator?.callCoordinator.declineCall(callWasRemoved: false)
// -- HaXX0r --
// -- HaXX0r --
// -- HaXX0r --
// also decline Tox Call -------------
// stopAudio()
// call.end changes the call's status, allows other classes to react to new state
call.end()
action.fulfill()
callManager.remove(call: call)
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
os_log("ProviderDelegate:call-held %@", action)
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
call.state = action.isOnHold ? .held : .active
if call.state == .held {
// stopAudio()
} else {
// startAudio()
}
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
let call = Call(uuid: action.callUUID, outgoing: true, handle: action.handle.value)
// configure. provider(_:didActivate) will start audio
configureAudioSession()
os_log("cc:ProviderDelegate:call-start %s", action.handle.value)
// set connectedStateChanged as a closure to monitor call lifecycle
call.connectedStateChanged = { [weak self, weak call] in
guard let strongSelf = self, let call = call else { return }
if call.connectedState == .pending {
strongSelf.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: nil)
} else if call.connectedState == .complete {
strongSelf.provider.reportOutgoingCall(with: call.uuid, connectedAt: nil)
}
}
call.start { [weak self, weak call] success in
guard let strongSelf = self, let call = call else { return }
if success {
action.fulfill()
strongSelf.callManager.add(call: call)
} else {
action.fail()
}
}
}
}