session-ios/Session/Calls/UserInterface/Individual/CallKit/CallKitCallManager.swift

153 lines
4.7 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import UIKit
import CallKit
import SignalServiceKit
/**
* Requests actions from CallKit
*
* @Discussion:
* Based on SpeakerboxCallManager, from the Apple CallKit Example app. Though, it's responsibilities are mostly
* mirrored (and delegated from) CallKitCallUIAdaptee.
* TODO: Would it simplify things to merge this into CallKitCallUIAdaptee?
*/
final class CallKitCallManager: NSObject {
let callController = CXCallController()
let showNamesOnCallScreen: Bool
@objc
static let kAnonymousCallHandlePrefix = "Signal:"
required init(showNamesOnCallScreen: Bool) {
AssertIsOnMainThread()
self.showNamesOnCallScreen = showNamesOnCallScreen
super.init()
// We cannot assert singleton here, because this class gets rebuilt when the user changes relevant call settings
}
// MARK: Actions
func startCall(_ call: SignalCall) {
let handle: CXHandle
if showNamesOnCallScreen {
let type: CXHandle.HandleType
let value: String
if let phoneNumber = call.individualCall.remoteAddress.phoneNumber {
type = .phoneNumber
value = phoneNumber
} else {
type = .generic
value = call.individualCall.remoteAddress.stringForDisplay
}
handle = CXHandle(type: type, value: value)
} else {
let callKitId = CallKitCallManager.kAnonymousCallHandlePrefix + call.individualCall.localId.uuidString
handle = CXHandle(type: .generic, value: callKitId)
CallKitIdStore.setAddress(call.individualCall.remoteAddress, forCallKitId: callKitId)
}
let startCallAction = CXStartCallAction(call: call.individualCall.localId, handle: handle)
startCallAction.isVideo = call.individualCall.hasLocalVideo
let transaction = CXTransaction()
transaction.addAction(startCallAction)
requestTransaction(transaction)
}
func localHangup(call: SignalCall) {
let endCallAction = CXEndCallAction(call: call.individualCall.localId)
let transaction = CXTransaction()
transaction.addAction(endCallAction)
requestTransaction(transaction)
}
func setHeld(call: SignalCall, onHold: Bool) {
let setHeldCallAction = CXSetHeldCallAction(call: call.individualCall.localId, onHold: onHold)
let transaction = CXTransaction()
transaction.addAction(setHeldCallAction)
requestTransaction(transaction)
}
func setIsMuted(call: SignalCall, isMuted: Bool) {
let muteCallAction = CXSetMutedCallAction(call: call.individualCall.localId, muted: isMuted)
let transaction = CXTransaction()
transaction.addAction(muteCallAction)
requestTransaction(transaction)
}
func answer(call: SignalCall) {
let answerCallAction = CXAnswerCallAction(call: call.individualCall.localId)
let transaction = CXTransaction()
transaction.addAction(answerCallAction)
requestTransaction(transaction)
}
private func requestTransaction(_ transaction: CXTransaction) {
callController.request(transaction) { error in
if let error = error {
Logger.error("Error requesting transaction: \(error)")
} else {
Logger.debug("Requested transaction successfully")
}
}
}
// MARK: Call Management
private(set) var calls = [SignalCall]()
func callWithLocalId(_ localId: UUID) -> SignalCall? {
guard let index = calls.firstIndex(where: { $0.individualCall.localId == localId }) else {
return nil
}
return calls[index]
}
func addCall(_ call: SignalCall) {
Logger.verbose("call: \(call)")
owsAssertDebug(call.isIndividualCall)
call.individualCall.wasReportedToSystem = true
calls.append(call)
}
func removeCall(_ call: SignalCall) {
Logger.verbose("call: \(call)")
owsAssertDebug(call.isIndividualCall)
call.individualCall.wasRemovedFromSystem = true
guard calls.removeFirst(where: { $0 === call }) != nil else {
Logger.warn("no call matching: \(call) to remove")
return
}
}
func removeAllCalls() {
Logger.verbose("")
calls.forEach { $0.individualCall.wasRemovedFromSystem = true }
calls.removeAll()
}
}
fileprivate extension Array {
mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? {
guard let index = try firstIndex(where: predicate) else {
return nil
}
return remove(at: index)
}
}