ProviderDelegate.swift 11.2 KB
Newer Older
DanmeiChen's avatar
DanmeiChen committed
1
/*
DanmeiChen's avatar
DanmeiChen committed
2
* Copyright (c) 2010-2020 Belledonne Communications SARL.
DanmeiChen's avatar
DanmeiChen committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import Foundation
import CallKit
import UIKit
import linphonesw
import AVFoundation
import os

DanmeiChen's avatar
DanmeiChen committed
27
@objc class CallInfo: NSObject {
DanmeiChen's avatar
DanmeiChen committed
28 29 30 31
	var callId: String = ""
	var toAddr: Address?
	var isOutgoing = false
	var sasEnabled = false
32
	var connected = false
DanmeiChen's avatar
DanmeiChen committed
33
	var reason: Reason = Reason.None
34
	var displayName: String?
DanmeiChen's avatar
DanmeiChen committed
35

DanmeiChen's avatar
DanmeiChen committed
36 37 38 39 40 41
	static func newIncomingCallInfo(callId: String) -> CallInfo {
		let callInfo = CallInfo()
		callInfo.callId = callId
		return callInfo
	}
	
42
	static func newOutgoingCallInfo(addr: Address, isSas: Bool, displayName: String) -> CallInfo {
DanmeiChen's avatar
DanmeiChen committed
43 44 45 46
		let callInfo = CallInfo()
		callInfo.isOutgoing = true
		callInfo.sasEnabled = isSas
		callInfo.toAddr = addr
47
		callInfo.displayName = displayName
DanmeiChen's avatar
DanmeiChen committed
48 49 50 51
		return callInfo
	}
}

DanmeiChen's avatar
DanmeiChen committed
52 53 54
/*
* A delegate to support callkit.
*/
DanmeiChen's avatar
DanmeiChen committed
55 56 57
class ProviderDelegate: NSObject {
	private let provider: CXProvider
	var uuids: [String : UUID] = [:]
DanmeiChen's avatar
DanmeiChen committed
58
	var callInfos: [UUID : CallInfo] = [:]
DanmeiChen's avatar
DanmeiChen committed
59 60 61 62 63 64 65 66 67 68 69 70

	override init() {
		provider = CXProvider(configuration: ProviderDelegate.providerConfiguration)
		super.init()
		provider.setDelegate(self, queue: nil)
	}

	static var providerConfiguration: CXProviderConfiguration = {
		let providerConfiguration = CXProviderConfiguration(localizedName: Bundle.main.infoDictionary!["CFBundleName"] as! String)
		providerConfiguration.ringtoneSound = "notes_of_the_optimistic.caf"
		providerConfiguration.supportsVideo = true
		providerConfiguration.iconTemplateImageData = UIImage(named: "callkit_logo")?.pngData()
71
		providerConfiguration.supportedHandleTypes = [.generic, .phoneNumber, .emailAddress]
DanmeiChen's avatar
DanmeiChen committed
72

DanmeiChen's avatar
DanmeiChen committed
73
		providerConfiguration.maximumCallsPerCallGroup = 10
74
		providerConfiguration.maximumCallGroups = 10
DanmeiChen's avatar
DanmeiChen committed
75 76 77 78 79 80 81

		//not show app's calls in tel's history
		//providerConfiguration.includesCallsInRecents = YES;
		
		return providerConfiguration
	}()

82
	func reportIncomingCall(call:Call?, uuid: UUID, handle: String, hasVideo: Bool, displayName:String) {
DanmeiChen's avatar
DanmeiChen committed
83 84 85
		let update = CXCallUpdate()
		update.remoteHandle = CXHandle(type:.generic, value: handle)
		update.hasVideo = hasVideo
86
		update.localizedCallerName = displayName
DanmeiChen's avatar
DanmeiChen committed
87

DanmeiChen's avatar
DanmeiChen committed
88 89
		let callInfo = callInfos[uuid]
		let callId = callInfo?.callId
DanmeiChen's avatar
DanmeiChen committed
90
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: report new incoming call with call-id: [\(String(describing: callId))] and UUID: [\(uuid.description)]")
91
		//CallManager.instance().setHeldOtherCalls(exceptCallid: callId ?? "") 
DanmeiChen's avatar
DanmeiChen committed
92 93
		provider.reportNewIncomingCall(with: uuid, update: update) { error in
			if error == nil {
94
				if CallManager.instance().endCallkit {
DanmeiChen's avatar
DanmeiChen committed
95 96 97 98
					let call = CallManager.instance().lc?.getCallByCallid(callId: callId!)
					if (call?.state == .PushIncomingReceived) {
						try? call?.terminate()
					}
99
				}
DanmeiChen's avatar
DanmeiChen committed
100 101 102
			} else {
				Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: cannot complete incoming call with call-id: [\(String(describing: callId))] and UUID: [\(uuid.description)] from [\(handle)] caused by [\(error!.localizedDescription)]")
				let code = (error as NSError?)?.code
DanmeiChen's avatar
DanmeiChen committed
103 104 105 106 107 108 109
				switch code {
				case CXErrorCodeIncomingCallError.filteredByDoNotDisturb.rawValue:
					callInfo?.reason = Reason.DoNotDisturb
				case CXErrorCodeIncomingCallError.filteredByBlockList.rawValue:
					callInfo?.reason = Reason.DoNotDisturb
				default:
					callInfo?.reason = Reason.Unknown
DanmeiChen's avatar
DanmeiChen committed
110
				}
DanmeiChen's avatar
DanmeiChen committed
111 112
				self.callInfos.updateValue(callInfo!, forKey: uuid)
				try? call?.decline(reason: callInfo!.reason)
DanmeiChen's avatar
DanmeiChen committed
113 114 115 116
			}
		}
	}

117
	func updateCall(uuid: UUID, handle: String, hasVideo: Bool = false, displayName:String) {
DanmeiChen's avatar
DanmeiChen committed
118 119
		let update = CXCallUpdate()
		update.remoteHandle = CXHandle(type:.generic, value:handle)
120
		update.localizedCallerName = displayName
DanmeiChen's avatar
DanmeiChen committed
121 122 123 124 125 126 127 128 129 130 131
		update.hasVideo = hasVideo
		provider.reportCall(with:uuid, updated:update);
	}

	func reportOutgoingCallStartedConnecting(uuid:UUID) {
		provider.reportOutgoingCall(with: uuid, startedConnectingAt: nil)
	}

	func reportOutgoingCallConnected(uuid:UUID) {
		provider.reportOutgoingCall(with: uuid, connectedAt: nil)
	}
DanmeiChen's avatar
DanmeiChen committed
132 133
	
	func endCall(uuid: UUID) {
134
		provider.reportCall(with: uuid, endedAt: .init(), reason: .failed)
DanmeiChen's avatar
DanmeiChen committed
135
	}
DanmeiChen's avatar
DanmeiChen committed
136

DanmeiChen's avatar
DanmeiChen committed
137 138
	func endCallNotExist(uuid: UUID, timeout: DispatchTime) {
		DispatchQueue.main.asyncAfter(deadline: timeout) {
DanmeiChen's avatar
DanmeiChen committed
139
			let callId = CallManager.instance().providerDelegate.callInfos[uuid]?.callId
140 141 142 143
			if (callId == nil) {
				// callkit already ended
				return
			}
DanmeiChen's avatar
DanmeiChen committed
144 145 146 147
			let call = CallManager.instance().callByCallId(callId: callId)
			if (call == nil) {
				Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: terminate call with call-id: \(String(describing: callId)) and UUID: \(uuid) which does not exist.")
				CallManager.instance().providerDelegate.endCall(uuid: uuid)
DanmeiChen's avatar
DanmeiChen committed
148 149
			}
		}
DanmeiChen's avatar
DanmeiChen committed
150
	}
DanmeiChen's avatar
DanmeiChen committed
151 152 153 154 155
}

// MARK: - CXProviderDelegate
extension ProviderDelegate: CXProviderDelegate {
	func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
156
		
DanmeiChen's avatar
DanmeiChen committed
157
		let uuid = action.callUUID
DanmeiChen's avatar
DanmeiChen committed
158
		let callId = callInfos[uuid]?.callId
DanmeiChen's avatar
DanmeiChen committed
159 160 161 162 163

		// remove call infos first, otherwise CXEndCallAction will be called more than onece
		if (callId != nil) {
			uuids.removeValue(forKey: callId!)
		}
DanmeiChen's avatar
DanmeiChen committed
164
		callInfos.removeValue(forKey: uuid)
DanmeiChen's avatar
DanmeiChen committed
165

DanmeiChen's avatar
DanmeiChen committed
166
		let call = CallManager.instance().callByCallId(callId: callId)
Paul Cartier's avatar
Paul Cartier committed
167 168 169
		if let call = call {
			CallManager.instance().terminateCall(call: call.getCobject);
			Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call ended with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
DanmeiChen's avatar
DanmeiChen committed
170
		}
171
		action.fulfill()
DanmeiChen's avatar
DanmeiChen committed
172 173 174 175
	}

	func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
		let uuid = action.callUUID
DanmeiChen's avatar
DanmeiChen committed
176 177
		let callInfo = callInfos[uuid]
		let callId = callInfo?.callId
DanmeiChen's avatar
DanmeiChen committed
178 179 180
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: answer call with call-id: \(String(describing: callId)) and UUID: \(uuid.description).")

		let call = CallManager.instance().callByCallId(callId: callId)
181 182 183 184 185 186
		
		if (UIApplication.shared.applicationState != .active) {
			CallManager.instance().backgroundContextCall = call
			CallManager.instance().backgroundContextCameraIsEnabled = call!.params?.videoEnabled ?? false
			call?.cameraEnabled = false // Disable camera while app is not on foreground
		}
187
		CallManager.instance().lc?.configureAudioSession()
188
		CallManager.instance().acceptCall(call: call!, hasVideo: call!.params?.videoEnabled ?? false)
DanmeiChen's avatar
DanmeiChen committed
189 190 191 192 193
		action.fulfill()
	}

	func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
		let uuid = action.callUUID
DanmeiChen's avatar
DanmeiChen committed
194
		let callId = callInfos[uuid]?.callId
DanmeiChen's avatar
DanmeiChen committed
195
		let call = CallManager.instance().callByCallId(callId: callId)
DanmeiChen's avatar
DanmeiChen committed
196 197 198 199
		action.fulfill()
		if (call == nil) {
			return
		}
DanmeiChen's avatar
DanmeiChen committed
200 201

		do {
202
			if (CallManager.instance().lc?.isInConference ?? false && action.isOnHold) {
DanmeiChen's avatar
DanmeiChen committed
203
				try CallManager.instance().lc?.leaveConference()
DanmeiChen's avatar
DanmeiChen committed
204
				Log.directLog(BCTBX_LOG_DEBUG, text: "CallKit: call-id: [\(String(describing: callId))] leaving conference")
DanmeiChen's avatar
DanmeiChen committed
205 206 207 208 209 210 211 212
				NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
				return
			}

			let state = action.isOnHold ? "Paused" : "Resumed"
			Log.directLog(BCTBX_LOG_DEBUG, text: "CallKit: Call  with call-id: [\(String(describing: callId))] and UUID: [\(uuid)] paused status changed to: [\(state)]")
			if (action.isOnHold) {
				if (call!.params?.localConferenceMode ?? false) {
DanmeiChen's avatar
DanmeiChen committed
213 214
					return
				}
215
				CallManager.instance().speakerBeforePause = CallManager.instance().isSpeakerEnabled()
DanmeiChen's avatar
DanmeiChen committed
216 217
				try call!.pause()
			} else {
218
				if (CallManager.instance().lc?.conference != nil && CallManager.instance().lc?.callsNb ?? 0 > 1) {
DanmeiChen's avatar
DanmeiChen committed
219 220
					try CallManager.instance().lc?.enterConference()
					NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
DanmeiChen's avatar
DanmeiChen committed
221
				} else {
DanmeiChen's avatar
DanmeiChen committed
222
					try call!.resume()
DanmeiChen's avatar
DanmeiChen committed
223 224
				}
			}
DanmeiChen's avatar
DanmeiChen committed
225 226
		} catch {
			Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call set held (paused or resumed) \(uuid) failed because \(error)")
DanmeiChen's avatar
DanmeiChen committed
227 228 229 230
		}
	}

	func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
231 232
		
		
DanmeiChen's avatar
DanmeiChen committed
233
		do {
234
						
DanmeiChen's avatar
DanmeiChen committed
235
			let uuid = action.callUUID
DanmeiChen's avatar
DanmeiChen committed
236
			let callInfo = callInfos[uuid]
237 238 239 240 241
			let update = CXCallUpdate()
			update.remoteHandle = action.handle
			update.localizedCallerName = callInfo?.displayName
			self.provider.reportCall(with: action.callUUID, updated: update)
			
DanmeiChen's avatar
DanmeiChen committed
242
			let addr = callInfo?.toAddr
DanmeiChen's avatar
DanmeiChen committed
243 244 245 246 247
			if (addr == nil) {
				Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: can not call a null address!")
				action.fail()
			}

248
			CallManager.instance().lc?.configureAudioSession()
DanmeiChen's avatar
DanmeiChen committed
249
			try CallManager.instance().doCall(addr: addr!, isSas: callInfo?.sasEnabled ?? false)
DanmeiChen's avatar
DanmeiChen committed
250 251 252 253 254 255 256 257 258
		} catch {
			Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call started failed because \(error)")
			action.fail()
		}
		action.fulfill()
	}

	func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call grouped callUUid : \(action.callUUID) with callUUID: \(String(describing: action.callUUIDToGroupWith)).")
259
		CallManager.instance().addAllToConference()
DanmeiChen's avatar
DanmeiChen committed
260 261 262 263 264
		action.fulfill()
	}

	func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
		let uuid = action.callUUID
DanmeiChen's avatar
DanmeiChen committed
265
		let callId = callInfos[uuid]?.callId
DanmeiChen's avatar
DanmeiChen committed
266 267 268 269 270 271 272
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call muted with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
		CallManager.instance().lc!.micEnabled = !CallManager.instance().lc!.micEnabled
		action.fulfill()
	}

	func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {
		let uuid = action.callUUID
DanmeiChen's avatar
DanmeiChen committed
273
		let callId = callInfos[uuid]?.callId
DanmeiChen's avatar
DanmeiChen committed
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call send dtmf with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
		let call = CallManager.instance().callByCallId(callId: callId)
		if (call != nil) {
			let digit = (action.digits.cString(using: String.Encoding.utf8)?[0])!
			do {
				try call!.sendDtmf(dtmf: digit)
			} catch {
				Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call send dtmf \(uuid) failed because \(error)")
			}
		}
		action.fulfill()
	}

	func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
		let uuid = action.uuid
DanmeiChen's avatar
DanmeiChen committed
289
		let callId = callInfos[uuid]?.callId
DanmeiChen's avatar
DanmeiChen committed
290 291 292 293 294 295 296 297 298 299
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call time out with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
		action.fulfill()
	}

	func providerDidReset(_ provider: CXProvider) {
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: did reset.")
	}

	func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: audio session activated.")
DanmeiChen's avatar
DanmeiChen committed
300
		CallManager.instance().lc?.activateAudioSession(actived: true)
DanmeiChen's avatar
DanmeiChen committed
301 302 303 304
	}

	func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
		Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: audio session deactivated.")
DanmeiChen's avatar
DanmeiChen committed
305
		CallManager.instance().lc?.activateAudioSession(actived: false)
DanmeiChen's avatar
DanmeiChen committed
306 307 308
	}
}