239 lines
7.6 KiB
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|