VoIPCallKitManager.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2019-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CallKit
  22. protocol VoIPCallKitManagerDelegate: class {
  23. func callFailed()
  24. }
  25. final class VoIPCallKitManager: NSObject {
  26. private let provider: CXProvider
  27. private let callController: CXCallController
  28. private var uuid: UUID?
  29. private var answerAction: CXAnswerCallAction?
  30. weak var delegate: VoIPCallKitManagerDelegate?
  31. override init() {
  32. provider = CXProvider(configuration: VoIPCallKitManager.providerConfiguration(contact: nil))
  33. callController = CXCallController.init()
  34. super.init()
  35. provider.setDelegate(self, queue: nil)
  36. }
  37. class func providerConfiguration(contact: Contact?) -> CXProviderConfiguration {
  38. let providerConfiguration = CXProviderConfiguration(localizedName: BundleUtil.localizedString(forKey: "call_callkit_button_title"))
  39. providerConfiguration.supportsVideo = true
  40. providerConfiguration.maximumCallGroups = 1
  41. providerConfiguration.maximumCallsPerCallGroup = 1
  42. providerConfiguration.supportedHandleTypes = [.generic]
  43. if contact != nil {
  44. if let pushSetting = PushSetting.find(forIdentity: contact!.identity) {
  45. if pushSetting.canSendPush() && pushSetting.silent == false {
  46. let voIPSound = UserSettings.shared()?.voIPSound
  47. if voIPSound != "default" {
  48. providerConfiguration.ringtoneSound = "\(voIPSound!).caf"
  49. }
  50. } else {
  51. providerConfiguration.ringtoneSound = "silent.mp3"
  52. }
  53. } else {
  54. let voIPSound = UserSettings.shared()?.voIPSound
  55. if voIPSound != "default" {
  56. providerConfiguration.ringtoneSound = "\(voIPSound!).caf"
  57. }
  58. }
  59. }
  60. let image = BundleUtil.imageNamed("VoipThreema")
  61. providerConfiguration.iconTemplateImageData = image?.pngData()
  62. return providerConfiguration
  63. }
  64. }
  65. extension VoIPCallKitManager {
  66. // MARK: Public functions
  67. func reportIncomingCall(uuid: UUID, contact: Contact) {
  68. provider.configuration = VoIPCallKitManager.providerConfiguration(contact: contact)
  69. let update = CXCallUpdate.init()
  70. update.remoteHandle = CXHandle.init(type: .generic, value: contact.identity)
  71. update.supportsGrouping = false
  72. update.supportsUngrouping = false
  73. update.supportsHolding = false
  74. update.supportsDTMF = false
  75. update.hasVideo = false
  76. update.localizedCallerName = contact.displayName
  77. RTCAudioSession.sharedInstance().useManualAudio = true
  78. VoIPCallKitManager.configureAudioSession()
  79. provider.reportNewIncomingCall(with: uuid, update: update) { (error) in
  80. if error != nil {
  81. self.delegate?.callFailed()
  82. } else {
  83. self.uuid = uuid
  84. }
  85. }
  86. }
  87. func startCall(contact: Contact) {
  88. let handle = CXHandle.init(type: .generic, value: contact.identity)
  89. uuid = UUID.init()
  90. let startCallAction = CXStartCallAction.init(call: uuid!, handle: handle)
  91. let transaction = CXTransaction.init(action: startCallAction)
  92. callController.request(transaction, completion: { (error) in
  93. if error != nil {
  94. self.delegate?.callFailed()
  95. }
  96. let update = CXCallUpdate()
  97. update.remoteHandle = CXHandle.init(type: .generic, value: contact.identity)
  98. update.hasVideo = false
  99. update.localizedCallerName = contact.displayName
  100. self.provider.reportCall(with: self.uuid!, updated: update)
  101. })
  102. }
  103. func callAccepted() {
  104. if let callId = uuid {
  105. provider.reportOutgoingCall(with: callId, startedConnectingAt: Date())
  106. }
  107. }
  108. func callConnected() {
  109. if let callId = uuid {
  110. provider.reportOutgoingCall(with: callId, connectedAt: Date())
  111. answerAction?.fulfill()
  112. }
  113. }
  114. func answerFailed() {
  115. answerAction?.fail()
  116. }
  117. func endCall() {
  118. if let callId = uuid {
  119. let action = CXEndCallAction.init(call: callId)
  120. let transaction = CXTransaction.init(action: action)
  121. self.callController.request(transaction, completion: { (error) in
  122. // do noting
  123. })
  124. }
  125. }
  126. func timeoutCall() {
  127. if let id = uuid {
  128. let action = CXEndCallAction.init(call: id)
  129. let transaction = CXTransaction.init(action: action)
  130. self.callController.request(transaction, completion: { (error) in
  131. // do noting
  132. })
  133. }
  134. }
  135. func rejectCall() {
  136. if let id = uuid {
  137. let action = CXEndCallAction.init(call: id)
  138. let transaction = CXTransaction.init(action: action)
  139. self.callController.request(transaction, completion: { (error) in
  140. // do noting
  141. })
  142. }
  143. }
  144. static func configureAudioSession() {
  145. let audioSession = AVAudioSession.sharedInstance()
  146. do {
  147. try audioSession.setCategory(.playAndRecord, mode: .spokenAudio, options: [.allowBluetooth, .allowBluetoothA2DP])
  148. try audioSession.setActive(true)
  149. } catch let error {
  150. print(error.localizedDescription)
  151. }
  152. }
  153. }
  154. extension VoIPCallKitManager: CXProviderDelegate {
  155. // MARK: CXProviderDelegate
  156. func providerDidReset(_ provider: CXProvider) {
  157. let state = VoIPCallStateManager.shared.currentCallState()
  158. if state == .incomingRinging || state == .calling || state == .reconnecting {
  159. if let contact = VoIPCallStateManager.shared.currentCallContact() {
  160. if let currentCallId = VoIPCallStateManager.shared.currentCallId() {
  161. BackgroundTaskManager.shared.newBackgroundTask(key: kAppVoIPBackgroundTask, timeout: Int(kAppVoIPBackgroundTaskTime)) {
  162. ServerConnector.shared()?.connectWait()
  163. var userAction: VoIPCallUserAction?
  164. if VoIPCallStateManager.shared.currentCallState() == .incomingRinging {
  165. userAction = VoIPCallUserAction.init(action: .reject, contact: contact, callId: currentCallId, completion: nil)
  166. } else {
  167. userAction = VoIPCallUserAction.init(action: .end, contact: contact, callId: currentCallId, completion: nil)
  168. }
  169. VoIPCallStateManager.shared.processUserAction(userAction!)
  170. }
  171. }
  172. }
  173. }
  174. }
  175. func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
  176. action.fulfill()
  177. }
  178. func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
  179. if let contact = VoIPCallStateManager.shared.currentCallContact() {
  180. if let currentCallId = VoIPCallStateManager.shared.currentCallId() {
  181. BackgroundTaskManager.shared.newBackgroundTask(key: kAppVoIPBackgroundTask, timeout: Int(kAppPushBackgroundTaskTime)) {
  182. ServerConnector.shared()?.connectWait()
  183. self.answerAction = action
  184. VoIPCallKitManager.configureAudioSession()
  185. let action = VoIPCallUserAction.init(action: .acceptCallKit, contact: contact, callId: currentCallId, completion: nil)
  186. VoIPCallStateManager.shared.processUserAction(action)
  187. }
  188. }
  189. } else {
  190. action.fail()
  191. }
  192. }
  193. func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
  194. guard let contact = VoIPCallStateManager.shared.currentCallContact() else {
  195. action.fulfill()
  196. return
  197. }
  198. guard let callId = VoIPCallStateManager.shared.currentCallId() else {
  199. action.fulfill()
  200. return
  201. }
  202. let state = VoIPCallStateManager.shared.currentCallState()
  203. switch state {
  204. case .ended, .remoteEnded, .rejected, .rejectedBusy, .rejectedTimeout, .rejectedDisabled, .rejectedOffHours, .rejectedUnknown, .microphoneDisabled:
  205. action.fulfill()
  206. return
  207. case .idle, .sendOffer, .receivedOffer, .outgoingRinging, .incomingRinging, .sendAnswer, .receivedAnswer, .initalizing, .calling, .reconnecting:
  208. // do nothing
  209. break
  210. }
  211. BackgroundTaskManager.shared.newBackgroundTask(key: kAppVoIPBackgroundTask, timeout: Int(kAppPushBackgroundTaskTime)) {
  212. ServerConnector.shared()?.connectWait()
  213. var userAction: VoIPCallUserAction?
  214. if VoIPCallStateManager.shared.currentCallState() == .incomingRinging {
  215. userAction = VoIPCallUserAction.init(action: .reject, contact: contact, callId: callId, completion: {
  216. action.fulfill()
  217. })
  218. } else {
  219. userAction = VoIPCallUserAction.init(action: .end, contact: contact, callId: callId, completion: {
  220. action.fulfill()
  221. })
  222. }
  223. VoIPCallStateManager.shared.processUserAction(userAction!)
  224. }
  225. }
  226. func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
  227. guard let contact = VoIPCallStateManager.shared.currentCallContact() else {
  228. action.fail()
  229. return
  230. }
  231. guard let callId = VoIPCallStateManager.shared.currentCallId() else {
  232. action.fail()
  233. return
  234. }
  235. BackgroundTaskManager.shared.newBackgroundTask(key: kAppVoIPBackgroundTask, timeout: Int(kAppVoIPBackgroundTaskTime)) {
  236. ServerConnector.shared()?.connectWait()
  237. let actionType: VoIPCallUserAction.Action = action.isMuted ? .muteAudio : .unmuteAudio
  238. let userAction = VoIPCallUserAction.init(action: actionType, contact: contact, callId: callId, completion: {
  239. action.fulfill()
  240. })
  241. VoIPCallStateManager.shared.processUserAction(userAction)
  242. }
  243. }
  244. func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
  245. guard let contact = VoIPCallStateManager.shared.currentCallContact() else {
  246. action.fail()
  247. return
  248. }
  249. guard let callId = VoIPCallStateManager.shared.currentCallId() else {
  250. action.fail()
  251. return
  252. }
  253. BackgroundTaskManager.shared.newBackgroundTask(key: kAppVoIPBackgroundTask, timeout: Int(kAppVoIPBackgroundTaskTime)) {
  254. ServerConnector.shared()?.connectWait()
  255. let actionType: VoIPCallUserAction.Action = action.isOnHold ? .muteAudio : .unmuteAudio
  256. let action = VoIPCallUserAction.init(action: actionType, contact: contact, callId: callId, completion: {
  257. action.fulfill()
  258. })
  259. VoIPCallStateManager.shared.processUserAction(action)
  260. }
  261. }
  262. func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
  263. guard let contact = VoIPCallStateManager.shared.currentCallContact() else {
  264. action.fulfill()
  265. return
  266. }
  267. guard let callId = VoIPCallStateManager.shared.currentCallId() else {
  268. action.fulfill()
  269. return
  270. }
  271. BackgroundTaskManager.shared.newBackgroundTask(key: kAppVoIPBackgroundTask, timeout: Int(kAppVoIPBackgroundTaskTime)) {
  272. ServerConnector.shared()?.connectWait()
  273. switch VoIPCallStateManager.shared.currentCallState() {
  274. case .idle, .ended, .remoteEnded, .rejected, .rejectedBusy, .rejectedTimeout, .rejectedOffHours, .rejectedUnknown, .rejectedDisabled, .microphoneDisabled:
  275. action.fulfill()
  276. break
  277. case .sendOffer, .outgoingRinging, .sendAnswer, .receivedAnswer, .initalizing, .calling, .reconnecting:
  278. let userAction = VoIPCallUserAction.init(action: .end, contact: contact, callId: callId, completion: {
  279. action.fulfill()
  280. })
  281. VoIPCallStateManager.shared.processUserAction(userAction)
  282. break
  283. case .receivedOffer, .incomingRinging:
  284. let userAction = VoIPCallUserAction.init(action: .rejectUnknown, contact: contact, callId: callId, completion: {
  285. action.fulfill()
  286. })
  287. VoIPCallStateManager.shared.processUserAction(userAction)
  288. break
  289. }
  290. }
  291. }
  292. func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
  293. VoIPCallStateManager.shared.setRTCAudio(audioSession)
  294. }
  295. }