callkit in Swift

This commit is contained in:
Danmei Chen 2020-01-16 17:02:03 +01:00
parent c4b5d1207f
commit 98a7938b86
24 changed files with 940 additions and 1092 deletions

566
Classes/CallManager.swift Normal file
View file

@ -0,0 +1,566 @@
/*
* Copyright (c) 2010-2019 Belledonne Communications SARL.
*
* 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 linphonesw
import UserNotifications
import os
import CallKit
import AVFoundation
import CoreTelephony
enum NetworkType: Int {
case network_none = 0
case network_2g = 1
case network_3g = 2
case network_4g = 3
case network_lte = 4
case network_wifi = 5
}
@objc class CallAppData: NSObject {
@objc public var batteryWarningShown = false
@objc public var videoRequested = false /*set when user has requested for video*/
}
@objc class CallManager: NSObject {
static var theCallManager: CallManager?
@objc static public var nextCallIsTransfer: Bool = false
let providerDelegate: ProviderDelegate!
let callController: CXCallController!
let manager: CoreManager!
let applicationKey = "app"
var callAppDatas: [String : CallAppData] = [:]
@objc var speakerBeforePause : Bool = false
@objc var speakerEnabled : Bool = false
@objc var bluetoothEnabled : Bool = false
var lc: Core?
var config: Config?
fileprivate override init() {
providerDelegate = ProviderDelegate()
callController = CXCallController()
manager = CoreManager()
}
@objc static func instance() -> CallManager {
if (theCallManager == nil) {
theCallManager = CallManager()
}
return theCallManager!
}
@objc func getAppData (callId : String) -> CallAppData? {
return CallManager.instance().callAppDatas["\(callId)"]
}
@objc func setAppData (callId: String, appData: CallAppData) {
CallManager.instance().callAppDatas.updateValue(appData, forKey: callId)
}
@objc func configCallManager(core: OpaquePointer, db:OpaquePointer) {
lc = Core.getSwiftObject(cObject: core)
lc?.addDelegate(delegate: manager)
config = Config.getSwiftObject(cObject: db)
}
@objc func findCall(callId: String?) -> OpaquePointer? {
let call = callByCallId(callId: callId)
return call?.getCobject
}
func callByCallId(callId: String?) -> Call? {
if (callId == nil) {
return nil
}
let calls = lc?.calls
if let callTmp = calls?.first(where: { $0.callLog?.callId == callId }) {
return callTmp
}
return nil
}
@objc static func callKitEnabled() -> Bool {
#if !targetEnvironment(simulator)
if CallManager.instance().lpConfigBoolForKey(key: "use_callkit", section: "app") {
return true
}
#endif
return false
}
static func network() -> NetworkType {
let info = CTTelephonyNetworkInfo()
let currentRadio = info.currentRadioAccessTechnology
if (currentRadio == CTRadioAccessTechnologyEdge) {
return NetworkType.network_2g
} else if (currentRadio == CTRadioAccessTechnologyLTE) {
return NetworkType.network_4g
}
return NetworkType.network_3g
}
@objc func allowSpeaker() -> Bool {
if (UIDevice.current.userInterfaceIdiom == .pad) {
// For now, ipad support only speaker.
return true
}
var allow = true
let newRoute = AVAudioSession.sharedInstance().currentRoute
if (newRoute.outputs.count > 0) {
let route = newRoute.outputs[0].portType
allow = route != .lineOut || route == .headphones || (AudioHelper.bluetoothRoutes() as Array).contains(where: {($0 as! AVAudioSession.Port) == route})
}
return allow
}
@objc func setSpeakerEnabled(enable: Bool) {
speakerEnabled = enable
do {
if (enable && allowSpeaker()) {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
UIDevice.current.isProximityMonitoringEnabled = false
bluetoothEnabled = false
} else {
let buildinPort = AudioHelper.bluetoothAudioDevice()
try AVAudioSession.sharedInstance().setPreferredInput(buildinPort)
UIDevice.current.isProximityMonitoringEnabled = (lc!.callsNb > 0)
}
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "Failed to change audio route: err \(error)")
}
}
@objc static func recordingFilePathFromCall(address: String) -> String {
var filePath = "recording_"
filePath = filePath.appending(address.isEmpty ? address : "unknow")
let now = Date()
let dateFormat = DateFormatter()
dateFormat.dateFormat = "E-d-MMM-yyyy-HH-mm-ss"
let date = dateFormat.string(from: now)
filePath = filePath.appending("_\(date).mkv")
let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
var writablePath = paths[0]
writablePath = writablePath.appending("/\(filePath)")
Log.directLog(BCTBX_LOG_MESSAGE, text: "file path is \(writablePath)")
return writablePath
//file name is recording_contact-name_dayName-day-monthName-year-hour-minutes-seconds
//The recording prefix is used to identify recordings in the cache directory.
//We will use name_dayName-day-monthName-year to separate recordings by days, then hour-minutes-seconds to order them in each day.
}
func requestTransaction(_ transaction: CXTransaction, action: String) {
callController.request(transaction) { error in
if let error = error {
Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Requested transaction \(action) failed because: \(error)")
} else {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Requested transaction \(action) successfully")
}
}
}
// From ios13, display the callkit view when the notification is received.
@objc func displayIncomingCall(callId: String) {
displayIncomingCall(call: nil, handle: "Calling", hasVideo: false, callId: callId)
}
func displayIncomingCall(call:Call?, handle: String, hasVideo: Bool, callId: String) {
let uuid = UUID()
providerDelegate.uuids.updateValue(uuid, forKey: callId)
providerDelegate.calls.updateValue(callId, forKey: uuid)
providerDelegate.connecteds.updateValue(false, forKey: uuid)
providerDelegate.reportIncomingCall(call:call, uuid: uuid, handle: handle, hasVideo: hasVideo)
}
@objc func acceptCall(call: OpaquePointer?, hasVideo:Bool) {
if (call == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "Can not accept null call!")
return
}
let call = Call.getSwiftObject(cObject: call!)
acceptCall(call: call, hasVideo: hasVideo)
}
func acceptCall(call: Call, hasVideo:Bool) {
do {
let callParams = try lc!.createCallParams(call: call)
callParams.videoEnabled = hasVideo
if (lpConfigBoolForKey(key: "edge_opt_preference")) {
let low_bandwidth = (CallManager.network() == .network_2g)
if (low_bandwidth) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Low bandwidth mode")
}
callParams.lowBandwidthEnabled = low_bandwidth
}
//We set the record file name here because we can't do it after the call is started.
let address = call.callLog?.fromAddress
let writablePath = CallManager.recordingFilePathFromCall(address: address?.username ?? "")
Log.directLog(BCTBX_LOG_MESSAGE, text: "Record file path: \(String(describing: writablePath))")
callParams.recordFile = writablePath
try call.acceptWithParams(params: callParams)
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)")
}
}
// for outgoing call. There is not yet callId
@objc func startCall(addr: OpaquePointer?, isSas: Bool) {
if (addr == nil) {
print("Can not start a call with null address!")
return
}
let sAddr = Address.getSwiftObject(cObject: addr!)
if (CallManager.callKitEnabled()) {
let uuid = UUID()
let name = FastAddressBook.displayName(for: addr) ?? "unknow"
let handle = CXHandle(type: .generic, value: name)
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
let transaction = CXTransaction(action: startCallAction)
providerDelegate.addrs.updateValue(sAddr, forKey: uuid)
providerDelegate.outgoingUuids.updateValue(uuid, forKey: sAddr.asStringUriOnly())
providerDelegate.isSas.updateValue(isSas, forKey: uuid)
requestTransaction(transaction, action: "startCall")
}else {
try? doCall(addr: sAddr, isSas: isSas)
}
}
func doCall(addr: Address, isSas: Bool) throws {
let displayName = FastAddressBook.displayName(for: addr.getCobject)
let lcallParams = try CallManager.instance().lc!.createCallParams(call: nil)
if CallManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && CallManager.network() == .network_2g {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode")
lcallParams.lowBandwidthEnabled = true
}
if (displayName != nil) {
try addr.setDisplayname(newValue: displayName!)
}
if(CallManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) {
try addr.setDomain(newValue: CallManager.instance().lpConfigStringForKey(key: "domain", section: "assistant"))
}
if (CallManager.nextCallIsTransfer) {
let call = CallManager.instance().lc!.currentCall
try call?.transfer(referTo: addr.asString())
CallManager.nextCallIsTransfer = false
} else {
//We set the record file name here because we can't do it after the call is started.
let writablePath = CallManager.recordingFilePathFromCall(address: addr.username )
Log.directLog(BCTBX_LOG_DEBUG, text: "record file path: \(writablePath)")
lcallParams.recordFile = writablePath
if (isSas) {
lcallParams.mediaEncryption = .ZRTP
}
let call = CallManager.instance().lc!.inviteAddressWithParams(addr: addr, params: lcallParams)
if (call != nil) {
let callId = call!.callLog?.callId
// The LinphoneCallAppData object should be set on call creation with callback
// - (void)onCall:StateChanged:withMessage:. If not, we are in big trouble and expect it to crash
// We are NOT responsible for creating the AppData.
let data = CallManager.instance().getAppData(callId: callId ?? "")
if (data == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "New call instanciated but app data was not set. Expect it to crash.")
/* will be used later to notify user if video was not activated because of the linphone core*/
} else {
data!.videoRequested = lcallParams.videoEnabled
CallManager.instance().setAppData(callId: callId!, appData: data!)
}
}
}
}
@objc func groupCall() {
if (CallManager.callKitEnabled()) {
let calls = lc?.calls
if (calls == nil || calls!.isEmpty) {
return
}
let firstCall = calls!.first?.callLog?.callId ?? ""
let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : ""
let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"]
if (currentUuid == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.")
return
}
let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"]
let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid)
let transcation = CXTransaction(action: groupAction)
requestTransaction(transcation, action: "groupCall")
// To simulate the real group call action
heldCall(uuid: currentUuid!, onHold: false)
} else {
try? lc?.addAllToConference()
}
}
func heldCall(uuid: UUID, onHold: Bool) {
let heldAction = CXSetHeldCallAction(call: uuid, onHold: onHold)
let otherTransacation = CXTransaction(action: heldAction)
requestTransaction(otherTransacation, action: "heldCall")
}
static func configAudioSession(audioSession: AVAudioSession) {
do {
try audioSession.setCategory(AVAudioSession.Category.playAndRecord, mode: AVAudioSession.Mode.voiceChat, options: AVAudioSession.CategoryOptions(rawValue: AVAudioSession.CategoryOptions.allowBluetooth.rawValue | AVAudioSession.CategoryOptions.allowBluetoothA2DP.rawValue))
try audioSession.setMode(AVAudioSession.Mode.voiceChat)
try audioSession.setPreferredSampleRate(48000.0)
try AVAudioSession.sharedInstance().setActive(true, options: [])
} catch {
Log.directLog(BCTBX_LOG_WARNING, text: "CallKit: Unable to config audio session because : \(error)")
}
}
//pragma mark - LPConfig Functions
@objc func lpConfigSetString(value:String, key:String, section:String) {
if (!key.isEmpty) {
config?.setString(section: section, key: key, value: value)
}
}
@objc func lpConfigSetString(value:String, key:String) {
lpConfigSetString(value: value, key: key, section: applicationKey)
}
@objc func lpConfigStringForKey(key:String, section:String, defaultValue:String) -> String {
if (key.isEmpty) {
return defaultValue
}
return config?.getString(section: section, key: key, defaultString: "") ?? defaultValue
}
@objc func lpConfigStringForKey(key:String, section:String) -> String {
return lpConfigStringForKey(key: key, section: section, defaultValue: "")
}
@objc func lpConfigStringForKey(key:String, defaultValue:String) -> String {
return lpConfigStringForKey(key: key, section: applicationKey, defaultValue: defaultValue)
}
@objc func lpConfigStringForKey(key:String) -> String {
return lpConfigStringForKey(key: key, defaultValue: "")
}
@objc func lpConfigSetInt(value:Int, key:String, section:String) {
if(!key.isEmpty) {
config?.setInt(section: section, key: key, value: value)
}
}
@objc func lpConfigSetInt(value:Int, key:String) {
lpConfigSetInt(value: value, key: key, section: applicationKey)
}
@objc func lpConfigIntForKey(key:String, section:String, defaultValue:Int) -> Int {
if (key.isEmpty) {
return defaultValue
}
return config?.getInt(section: section, key: key, defaultValue: defaultValue) ?? defaultValue
}
@objc func lpConfigIntForKey(key:String, section:String) -> Int {
return lpConfigIntForKey(key: key, section: section, defaultValue: -1)
}
@objc func lpConfigIntForKey(key:String, defaultValue:Int) -> Int {
return lpConfigIntForKey(key: key, section: applicationKey, defaultValue: defaultValue)
}
@objc func lpConfigIntForKey(key:String) -> Int {
return lpConfigIntForKey(key: key, defaultValue: -1)
}
@objc func lpConfigSetBool(value:Bool, key:String, section:String) {
lpConfigSetInt(value: value ? 1:0, key: key, section: section)
}
@objc func lpConfigSetBool(value:Bool, key:String) {
lpConfigSetBool(value: value, key: key, section: applicationKey)
}
@objc func lpConfigBoolForKey(key:String, section:String, defaultValue:Bool) -> Bool {
if (key.isEmpty) {
return defaultValue
}
let val = lpConfigIntForKey(key: key, section: section, defaultValue: -1)
return (val != -1) ? (val == 1) : defaultValue
}
@objc func lpConfigBoolForKey(key:String, section:String) -> Bool {
return lpConfigBoolForKey(key: key, section: section, defaultValue: false)
}
@objc func lpConfigBoolForKey(key:String, defaultValue:Bool) -> Bool {
return lpConfigBoolForKey(key: key, section: applicationKey, defaultValue: defaultValue)
}
@objc func lpConfigBoolForKey(key:String) -> Bool {
return lpConfigBoolForKey(key: key, defaultValue: false)
}
}
class CoreManager: CoreDelegate {
static var speaker_already_enabled : Bool = false
override func onCallStateChanged(lc: Core, call: Call, cstate: Call.State, message: String) {
let addr = call.remoteAddress;
let address = FastAddressBook.displayName(for: addr?.getCobject) ?? "Unknow"
let callLog = call.callLog
let callId = callLog?.callId
let video = call.params?.videoEnabled ?? false
// we keep the speaker auto-enabled state in this static so that we don't
// force-enable it on ICE re-invite if the user disabled it.
CoreManager.speaker_already_enabled = false
if (callId != nil && CallManager.instance().callAppDatas["\(callId!)"] == nil) {
CallManager.instance().callAppDatas.updateValue(CallAppData(), forKey: callId!)
}
switch cstate {
case .IncomingReceived:
if (CallManager.callKitEnabled()) {
let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
if (uuid != nil) {
// Tha app is now registered, updated the call already existed.
CallManager.instance().providerDelegate.updateCall(uuid: uuid!, handle: address, hasVideo: video)
let connected = CallManager.instance().providerDelegate.connecteds[uuid!] ?? false
if (connected) {
// The call is already answered.
CallManager.instance().acceptCall(call: call, hasVideo: video)
}
} else {
// Nothing happped before, display a new Incoming call.
CallManager.instance().displayIncomingCall(call: call, handle: address, hasVideo: video, callId: callId!)
}
} else if (UIApplication.shared.applicationState != .active) {
// not support callkit , use notif
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Incoming call", comment: "")
content.body = address
content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf"))
content.categoryIdentifier = "call_cat"
content.userInfo = ["CallId" : callId!]
let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil)
UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
}
break
case .StreamsRunning:
if (CallManager.callKitEnabled()) {
let uuid = CallManager.instance().providerDelegate.outgoingUuids["\(addr!.asStringUriOnly())"]
if (uuid != nil) {
CallManager.instance().providerDelegate.uuids.updateValue(uuid!, forKey: callId!)
CallManager.instance().providerDelegate.calls.updateValue(callId!, forKey: uuid!)
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: outgoing call connected with uuid \(uuid!) and callId \(callId!)")
CallManager.instance().providerDelegate.reportOutgoingCallConnected(uuid: uuid!)
}
}
if (CallManager.instance().speakerBeforePause) {
CallManager.instance().speakerBeforePause = false
CallManager.instance().setSpeakerEnabled(enable: true)
CoreManager.speaker_already_enabled = true
}
break
case .OutgoingRinging:
if (CallManager.callKitEnabled()) {
let uuid = CallManager.instance().providerDelegate.outgoingUuids["\(addr!.asStringUriOnly())"]
if (uuid != nil) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: outgoing call started connecting with uuid \(uuid!) and callId \(callId!)")
CallManager.instance().providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!)
}
}
break
case .End,
.Error:
UIDevice.current.isProximityMonitoringEnabled = false
CoreManager.speaker_already_enabled = false
if (CallManager.instance().lc!.callsNb == 0) {
CallManager.instance().setSpeakerEnabled(enable: false)
// disable this because I don't find anygood reason for it: _bluetoothAvailable = FALSE;
// furthermore it introduces a bug when calling multiple times since route may not be
// reconfigured between cause leading to bluetooth being disabled while it should not
CallManager.instance().bluetoothEnabled = false
}
if callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted {
// Configure the notification's payload.
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: NSLocalizedString("Missed call", comment: ""), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: address, arguments: nil)
// Deliver the notification.
let request = UNNotificationRequest(identifier: "call_request", content: content, trigger: nil) // Schedule the notification.
let center = UNUserNotificationCenter.current()
center.add(request) { (error : Error?) in
if error != nil {
Log.directLog(BCTBX_LOG_ERROR, text: "Error while adding notification request : \(error!.localizedDescription)")
}
}
}
if (CallManager.callKitEnabled()) {
// end CallKit
let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
if (uuid != nil) {
let transaction = CXTransaction(action:
CXEndCallAction(call: uuid!))
CallManager.instance().requestTransaction(transaction, action: "endCall")
}
}
break
case .Released:
CallManager.instance().callAppDatas.removeValue(forKey: callId ?? "")
break
default:
break
}
if (cstate == .IncomingReceived || cstate == .OutgoingInit || cstate == .Connected || cstate == .StreamsRunning) {
if (video && CoreManager.speaker_already_enabled && CallManager.instance().bluetoothEnabled) {
CallManager.instance().setSpeakerEnabled(enable: true)
CoreManager.speaker_already_enabled = true
}
}
// post Notification kLinphoneCallUpdate
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self, userInfo: [
AnyHashable("call"): NSValue.init(pointer:UnsafeRawPointer(call.getCobject)),
AnyHashable("state"): NSNumber(value: cstate.rawValue),
AnyHashable("message"): message
])
}
}

View file

@ -93,20 +93,20 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onRoutesBluetoothClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[LinphoneManager.instance setSpeakerEnabled:FALSE];
[CallManager.instance setSpeakerEnabled:FALSE];
[LinphoneManager.instance setBluetoothEnabled:TRUE];
}
- (IBAction)onRoutesEarpieceClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[LinphoneManager.instance setSpeakerEnabled:FALSE];
[CallManager.instance setSpeakerEnabled:FALSE];
[LinphoneManager.instance setBluetoothEnabled:FALSE];
}
- (IBAction)onRoutesSpeakerClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[LinphoneManager.instance setBluetoothEnabled:FALSE];
[LinphoneManager.instance setSpeakerEnabled:TRUE];
[CallManager.instance setSpeakerEnabled:TRUE];
}
- (IBAction)onRoutesClick:(id)sender {
@ -131,8 +131,8 @@ static UICompositeViewDescription *compositeDescription = nil;
[_routesButton setOn];
}
_routesBluetoothButton.selected = LinphoneManager.instance.bluetoothEnabled;
_routesSpeakerButton.selected = LinphoneManager.instance.speakerEnabled;
_routesBluetoothButton.selected = CallManager.instance.bluetoothEnabled;
_routesSpeakerButton.selected = CallManager.instance.speakerEnabled;
_routesEarpieceButton.selected = !_routesBluetoothButton.selected && !_routesSpeakerButton.selected;
if (hidden != _routesView.hidden) {

View file

@ -34,6 +34,8 @@
#include "linphone/linphonecore.h"
#import "linphoneapp-Swift.h"
const NSInteger SECURE_BUTTON_TAG = 5;
@implementation CallView {
@ -137,7 +139,7 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
_waitView.hidden = TRUE;
LinphoneManager.instance.nextCallIsTransfer = NO;
CallManager.nextCallIsTransfer = FALSE;
callRecording = FALSE;
_recordButtonOnView.hidden = TRUE;
@ -241,7 +243,7 @@ static UICompositeViewDescription *compositeDescription = nil;
}
onConfirmationClick:^() {
LinphoneAddress *addr = linphone_address_new(address.UTF8String);
[LinphoneManager.instance doCallWithSas:addr isSas:TRUE];
[CallManager.instance startCallWithAddr:addr isSas:TRUE];
linphone_address_unref(addr);
} ];
[securityDialog.securityImage setImage:[UIImage imageNamed:@"security_alert_indicator.png"]];
@ -488,8 +490,8 @@ static void hideSpinner(LinphoneCall *call, void *user_data) {
[_routesButton setOn];
}
_routesBluetoothButton.selected = LinphoneManager.instance.bluetoothEnabled;
_routesSpeakerButton.selected = LinphoneManager.instance.speakerEnabled;
_routesBluetoothButton.selected = CallManager.instance.bluetoothEnabled;
_routesSpeakerButton.selected = CallManager.instance.speakerEnabled;
_routesEarpieceButton.selected = !_routesBluetoothButton.selected && !_routesSpeakerButton.selected;
if (hidden != _routesView.hidden) {
@ -582,13 +584,16 @@ static void hideSpinner(LinphoneCall *call, void *user_data) {
case LinphoneCallOutgoingInit:
case LinphoneCallConnected:
case LinphoneCallStreamsRunning: {
// check video
// check video, because video can be disabled because of the low bandwidth.
if (!linphone_call_params_video_enabled(linphone_call_get_current_params(call))) {
const LinphoneCallParams *param = linphone_call_get_current_params(call);
const LinphoneCallAppData *callAppData =
(__bridge const LinphoneCallAppData *)(linphone_call_get_user_data(call));
if (state == LinphoneCallStreamsRunning && callAppData->videoRequested &&
linphone_call_params_low_bandwidth_enabled(param)) {
CallAppData *data = nil;
const char *callId = linphone_call_log_get_call_id(linphone_call_get_call_log(call));
if (callId) {
data = [CallManager.instance getAppDataWithCallId:[NSString stringWithUTF8String:callId]];
}
if (state == LinphoneCallStreamsRunning && data && data.videoRequested && linphone_call_params_low_bandwidth_enabled(param)) {
// too bad video was not enabled because low bandwidth
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Low bandwidth", nil)
message:NSLocalizedString(@"Video cannot be activated because of low bandwidth "
@ -602,7 +607,8 @@ static void hideSpinner(LinphoneCall *call, void *user_data) {
[errView addAction:defaultAction];
[self presentViewController:errView animated:YES completion:nil];
callAppData->videoRequested = FALSE; /*reset field*/
data.videoRequested = FALSE;
[CallManager.instance setAppDataWithCallId:[NSString stringWithUTF8String:callId] appData:data];
}
}
break;
@ -802,20 +808,20 @@ static void hideSpinner(LinphoneCall *call, void *user_data) {
- (IBAction)onRoutesBluetoothClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[LinphoneManager.instance setSpeakerEnabled:FALSE];
[CallManager.instance setSpeakerEnabled:FALSE];
[LinphoneManager.instance setBluetoothEnabled:TRUE];
}
- (IBAction)onRoutesEarpieceClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[LinphoneManager.instance setSpeakerEnabled:FALSE];
[CallManager.instance setSpeakerEnabled:FALSE];
[LinphoneManager.instance setBluetoothEnabled:FALSE];
}
- (IBAction)onRoutesSpeakerClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[LinphoneManager.instance setBluetoothEnabled:FALSE];
[LinphoneManager.instance setSpeakerEnabled:TRUE];
[CallManager.instance setSpeakerEnabled:TRUE];
}
- (IBAction)onRoutesClick:(id)sender {
@ -838,7 +844,7 @@ static void hideSpinner(LinphoneCall *call, void *user_data) {
[self hideOptions:TRUE animated:TRUE];
DialerView *view = VIEW(DialerView);
[view setAddress:@""];
LinphoneManager.instance.nextCallIsTransfer = YES;
CallManager.nextCallIsTransfer = TRUE;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
@ -846,13 +852,13 @@ static void hideSpinner(LinphoneCall *call, void *user_data) {
[self hideOptions:TRUE animated:TRUE];
DialerView *view = VIEW(DialerView);
[view setAddress:@""];
LinphoneManager.instance.nextCallIsTransfer = NO;
CallManager.nextCallIsTransfer = FALSE;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
- (IBAction)onOptionsConferenceClick:(id)sender {
[self hideOptions:TRUE animated:TRUE];
linphone_core_add_all_to_conference(LC);
[CallManager.instance groupCall];
}
#pragma mark - Animation

View file

@ -21,10 +21,11 @@
#import <PushKit/PushKit.h>
#import "LinphoneCoreSettingsStore.h"
#import "ProviderDelegate.h"
#import <UserNotifications/UserNotifications.h>
#import <UserNotificationsUI/UserNotificationsUI.h>
#import <CoreLocation/CoreLocation.h>
#import "linphoneapp-Swift.h"
@interface LinphoneAppDelegate : NSObject <UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate, CLLocationManagerDelegate> {
@private
@ -39,7 +40,6 @@
@property (nonatomic, retain) NSString *configURL;
@property (nonatomic, strong) UIWindow* window;
@property PKPushRegistry* voipRegistry;
@property ProviderDelegate *del;
@property BOOL alreadyRegisteredForNotification;
@property BOOL onlyPortrait;
@property UIApplicationShortcutItem *shortcutItem;

View file

@ -111,13 +111,6 @@
instance->currentCallContextBeforeGoingBackground.call = 0;
} else if (linphone_call_get_state(call) ==
LinphoneCallIncomingReceived) {
LinphoneCallAppData *data =
(__bridge LinphoneCallAppData *)linphone_call_get_user_data(
call);
if (data && data->timer) {
[data->timer invalidate];
data->timer = nil;
}
if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) {
linphone_call_accept(call);
@ -125,7 +118,8 @@
} else {
[PhoneMainView.instance displayIncomingCall:call];
}
} else if (linphone_core_get_calls_nb(LC) > 1) {
} else {
// Click the call notification when callkit is disabled, show app view.
[PhoneMainView.instance displayIncomingCall:call];
}
@ -268,10 +262,6 @@
BOOL background_mode = [instance lpConfigBoolForKey:@"backgroundmode_preference"];
BOOL start_at_boot = [instance lpConfigBoolForKey:@"start_at_boot_preference"];
[self registerForNotifications]; // Register for notifications must be done ASAP to give a chance for first SIP register to be done with right token. Specially true in case of remote provisionning or re-install with new type of signing certificate, like debug to release.
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
self.del = [[ProviderDelegate alloc] init];
[LinphoneManager.instance setProviderDelegate:self.del];
}
if (state == UIApplicationStateBackground) {
// we've been woken up directly to background;
@ -411,6 +401,17 @@
return YES;
}
// used for callkit. Called when active video.
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
if ([userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
LOGI(@"CallKit: satrt video.");
CallView *view = VIEW(CallView);
[view.videoButton toggle];
}
return YES;
}
- (NSString *)valueForKey:(NSString *)key fromQueryItems:(NSArray *)queryItems {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", key];
NSURLQueryItem *queryItem = [[queryItems filteredArrayUsingPredicate:predicate] firstObject];
@ -445,12 +446,6 @@
return;
}
// Tell the core to make sure that we are registered.
// It will initiate socket connections, which seems to be required.
// Indeed it is observed that if no network action is done in the notification handler, then
// iOS kills us.
linphone_core_ensure_registered(LC);
NSString *uuid = [NSString stringWithFormat:@"<urn:uuid:%@>", [LinphoneManager.instance lpConfigStringForKey:@"uuid" inSection:@"misc" withDefault:NULL]];
NSString *sipInstance = [aps objectForKey:@"uuid"];
if (sipInstance && uuid && ![sipInstance isEqualToString:uuid]) {
@ -486,10 +481,20 @@
notification.alertTitle = @"APN Pusher";
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
} else
} else {
[LinphoneManager.instance addPushCallId:callId];
if ([loc_key isEqualToString:@"IC_MSG"] && [CallManager callKitEnabled]) {
// callkit only for call not message.
[CallManager.instance displayIncomingCallWithCallId:callId];
}
}
LOGI(@"Notification [%p] processed", userInfo);
// Tell the core to make sure that we are registered.
// It will initiate socket connections, which seems to be required.
// Indeed it is observed that if no network action is done in the notification handler, then
// iOS kills us.
linphone_core_ensure_registered(LC);
}
- (BOOL)addLongTaskIDforCallID:(NSString *)callId {
@ -588,14 +593,7 @@
if (!callId)
return;
LinphoneCall *call = [LinphoneManager.instance callByCallId:callId];
if (call) {
LinphoneCallAppData *data = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
if (data->timer) {
[data->timer invalidate];
data->timer = nil;
}
}
LinphoneCall *call = [CallManager.instance findCallWithCallId:callId];
if ([response.actionIdentifier isEqual:@"Answer"]) {
// use the standard handler
@ -757,13 +755,6 @@
completionHandler:(void (^)(void))completionHandler {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) {
LinphoneCallAppData *data = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
if (data->timer) {
[data->timer invalidate];
data->timer = nil;
}
}
LOGI(@"%@", NSStringFromSelector(_cmd));
if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_9_0) {
LOGI(@"%@", NSStringFromSelector(_cmd));
@ -805,13 +796,6 @@
completionHandler:(void (^)(void))completionHandler {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) {
LinphoneCallAppData *data = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
if (data->timer) {
[data->timer invalidate];
data->timer = nil;
}
}
if ([notification.category isEqualToString:@"incoming_call"]) {
if ([identifier isEqualToString:@"answer"]) {
// use the standard handler

View file

@ -34,7 +34,8 @@
#include "linphone/linphonecore.h"
#include "bctoolbox/list.h"
#import "OrderedDictionary.h"
#import "ProviderDelegate.h"
#import "linphoneapp-Swift.h"
extern NSString *const LINPHONERC_APPLICATION_KEY;
@ -76,16 +77,6 @@ typedef struct _CallContext {
bool_t cameraIsEnabled;
} CallContext;
@interface LinphoneCallAppData :NSObject {
@public
bool_t batteryWarningShown;
UILocalNotification *notification;
NSMutableDictionary *userInfos;
bool_t videoRequested; /*set when user has requested for video*/
NSTimer* timer;
};
@end
typedef struct _LinphoneManagerSounds {
SystemSoundID vibrate;
} LinphoneManagerSounds;
@ -132,15 +123,12 @@ typedef struct _LinphoneManagerSounds {
- (void)configurePushTokenForProxyConfig: (LinphoneProxyConfig*)cfg;
- (BOOL)popPushCallID:(NSString*) callId;
- (void)acceptCallForCallId:(NSString*)callid;
- (LinphoneCall *)callByCallId:(NSString *)call_id;
- (void)cancelLocalNotifTimerForCallId:(NSString*)callid;
- (void)startPushLongRunningTask:(NSString *)loc_key callId:(NSString *)callId;
+ (BOOL)langageDirectionIsRTL;
- (void)refreshRegisters;
- (bool)allowSpeaker;
- (void)configureVbrCodecs;
+ (BOOL)copyFile:(NSString*)src destination:(NSString*)dst override:(BOOL)override ignore:(BOOL)ignore;
@ -151,11 +139,8 @@ typedef struct _LinphoneManagerSounds {
+ (NSString*)dataFile:(NSString*)file;
+ (NSString*)cacheDirectory;
- (void)acceptCall:(LinphoneCall *)call evenWithVideo:(BOOL)video;
- (void)send:(NSString *)replyText toChatRoom:(LinphoneChatRoom *)room;
- (void)call:(const LinphoneAddress *)address;
- (BOOL)doCall:(const LinphoneAddress *)iaddr;
- (BOOL)doCallWithSas:(const LinphoneAddress *)iaddr isSas:(BOOL)isSas;
+(id)getMessageAppDataForKey:(NSString*)key inMessage:(LinphoneChatMessage*)msg;
+(void)setValueInMessageAppData:(id)value forKey:(NSString*)key inMessage:(LinphoneChatMessage*)msg;
@ -189,8 +174,6 @@ typedef struct _LinphoneManagerSounds {
- (void)shouldPresentLinkPopup;
- (void)setProviderDelegate:(ProviderDelegate *)del;
- (void) setLinphoneManagerAddressBookMap:(OrderedDictionary*) addressBook;
- (OrderedDictionary*) getLinphoneManagerAddressBookMap;
@ -202,7 +185,9 @@ typedef struct _LinphoneManagerSounds {
- (void)loadAvatar;
- (void)migrationPerAccount;
@property ProviderDelegate *providerDelegate;
- (void)setupGSMInteraction;
- (void)setBluetoothEnabled:(BOOL)enable;
- (BOOL)isCTCallCenterExist;
@property (readonly) BOOL isTesting;
@property(readonly, strong) FastAddressBook *fastAddressBook;
@ -214,10 +199,7 @@ typedef struct _LinphoneManagerSounds {
@property(nonatomic, strong) NSData *pushNotificationToken;
@property (readonly) LinphoneManagerSounds sounds;
@property (readonly) NSMutableArray *logs;
@property (nonatomic, assign) BOOL speakerBeforePause;
@property (nonatomic, assign) BOOL speakerEnabled;
@property (nonatomic, assign) BOOL bluetoothAvailable;
@property (nonatomic, assign) BOOL bluetoothEnabled;
@property (readonly) NSString* contactSipField;
@property (readonly,copy) NSString* contactFilter;
@property (copy) void (^silentPushCompletion)(UIBackgroundFetchResult);
@ -225,7 +207,6 @@ typedef struct _LinphoneManagerSounds {
@property (readonly) LpConfig *configDb;
@property(readonly) InAppProductsManager *iapManager;
@property(strong, nonatomic) NSMutableArray *fileTransferDelegates;
@property BOOL nextCallIsTransfer;
@property BOOL conf;
@property NSDictionary *pushDict;
@property(strong, nonatomic) OrderedDictionary *linphoneManagerAddressBookMap;

View file

@ -95,19 +95,6 @@ extern void libmscodec2_init(MSFactory *factory);
NSString *const kLinphoneOldChatDBFilename = @"chat_database.sqlite";
NSString *const kLinphoneInternalChatDBFilename = @"linphone_chats.db";
@implementation LinphoneCallAppData
- (id)init {
if ((self = [super init])) {
batteryWarningShown = FALSE;
notification = nil;
videoRequested = FALSE;
userInfos = [[NSMutableDictionary alloc] init];
}
return self;
}
@end
@interface LinphoneManager ()
@property(strong, nonatomic) AVAudioPlayer *messagePlayer;
@end
@ -255,9 +242,6 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
_logs = [[NSMutableArray alloc] init];
_pushDict = [[NSMutableDictionary alloc] init];
_database = NULL;
_speakerEnabled = FALSE;
_speakerBeforePause = FALSE;
_bluetoothEnabled = FALSE;
_conf = FALSE;
_fileTransferDelegates = [[NSMutableArray alloc] init];
_linphoneManagerAddressBookMap = [[OrderedDictionary alloc] init];
@ -576,340 +560,6 @@ static void linphone_iphone_display_status(struct _LinphoneCore *lc, const char
}
}
- (void)onCall:(LinphoneCall *)call StateChanged:(LinphoneCallState)state withMessage:(const char *)message {
// Handling wrapper
LinphoneCallAppData *data = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
if (!data) {
data = [[LinphoneCallAppData alloc] init];
linphone_call_set_user_data(call, (void *)CFBridgingRetain(data));
}
#pragma deploymate push "ignored-api-availability"
if (_silentPushCompletion) {
// we were woken up by a silent push. Call the completion handler with NEWDATA
// so that the push is notified to the user
LOGI(@"onCall - handler %p", _silentPushCompletion);
_silentPushCompletion(UIBackgroundFetchResultNewData);
_silentPushCompletion = nil;
}
#pragma deploymate pop
const LinphoneAddress *addr = linphone_call_get_remote_address(call);
NSString *address = [FastAddressBook displayNameForAddress:addr];
if (state == LinphoneCallIncomingReceived) {
LinphoneCallLog *callLog = linphone_call_get_call_log(call);
NSString *callId = [NSString stringWithUTF8String:linphone_call_log_get_call_id(callLog)];
int index = [(NSNumber *)[_pushDict objectForKey:callId] intValue] - 1;
LOGI(@"Decrementing index of long running task for call id : %@ with index : %d", callId, index);
[_pushDict setValue:[NSNumber numberWithInt:index] forKey:callId];
BOOL need_bg_task = FALSE;
for (NSString *key in [_pushDict allKeys]) {
int value = [(NSNumber *)[_pushDict objectForKey:key] intValue];
if (value > 0) {
need_bg_task = TRUE;
break;
}
}
if (pushBgTaskCall && !need_bg_task) {
LOGI(@"Call received, stopping call background task for call-id [%@]", callId);
[[UIApplication sharedApplication] endBackgroundTask:pushBgTaskCall];
pushBgTaskCall = 0;
}
/*first step is to re-enable ctcall center*/
CTCallCenter *lCTCallCenter = [[CTCallCenter alloc] init];
/*should we reject this call ?*/
if ([lCTCallCenter currentCalls] != nil &&
floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
char *tmp = linphone_call_get_remote_address_as_string(call);
if (tmp) {
LOGI(@"Mobile call ongoing... rejecting call from [%s]", tmp);
ms_free(tmp);
}
linphone_call_decline(call, LinphoneReasonBusy);
return;
}
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
if (call && (linphone_core_get_calls_nb(LC) < 2)) {
if ([LinphoneManager.instance lpConfigBoolForKey:@"accept_early_media" inSection:@"app"] && [LinphoneManager.instance lpConfigBoolForKey:@"pref_accept_early_media"]) {
[PhoneMainView.instance displayIncomingCall:call];
} else {
#if !TARGET_IPHONE_SIMULATOR
NSString *callId = [NSString stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(call))];
NSUUID *uuid = [NSUUID UUID];
[LinphoneManager.instance.providerDelegate.calls setObject:callId forKey:uuid];
[LinphoneManager.instance.providerDelegate.uuids setObject:uuid forKey:callId];
BOOL video = ([UIApplication sharedApplication].applicationState == UIApplicationStateActive &&
linphone_video_activation_policy_get_automatically_accept(linphone_core_get_video_activation_policy(LC)) &&
linphone_call_params_video_enabled(linphone_call_get_remote_params(call)));
[LinphoneManager.instance.providerDelegate reportIncomingCall:call withUUID:uuid handle:address video:video];
#else
[PhoneMainView.instance displayIncomingCall:call];
#endif
}
} else if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
// Create a UNNotification
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = NSLocalizedString(@"Incoming call", nil);
content.body = address;
content.sound = [UNNotificationSound soundNamed:@"notes_of_the_optimistic.caf"];
content.categoryIdentifier = @"call_cat";
content.userInfo = @{ @"CallId" : callId };
UNNotificationRequest *req =
[UNNotificationRequest requestWithIdentifier:@"call_request" content:content trigger:NULL];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:req
withCompletionHandler:^(NSError *err){
}];
}
} else {
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
// if (![LinphoneManager.instance popPushCallID:callId]) {
// case where a remote notification is not already received
// Create a new local notification
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) {
UIMutableUserNotificationAction *answer = [[UIMutableUserNotificationAction alloc] init];
answer.identifier = @"answer";
answer.title = NSLocalizedString(@"Answer", nil);
answer.activationMode = UIUserNotificationActivationModeForeground;
answer.destructive = NO;
answer.authenticationRequired = YES;
UIMutableUserNotificationAction *decline = [[UIMutableUserNotificationAction alloc] init];
decline.identifier = @"decline";
decline.title = NSLocalizedString(@"Decline", nil);
decline.activationMode = UIUserNotificationActivationModeBackground;
decline.destructive = YES;
decline.authenticationRequired = NO;
NSArray *callactions = @[ decline, answer ];
UIMutableUserNotificationCategory *callcat = [[UIMutableUserNotificationCategory alloc] init];
callcat.identifier = @"incoming_call";
[callcat setActions:callactions forContext:UIUserNotificationActionContextDefault];
[callcat setActions:callactions forContext:UIUserNotificationActionContextMinimal];
NSSet *categories = [NSSet setWithObjects:callcat, nil];
UIUserNotificationSettings *set = [UIUserNotificationSettings
settingsForTypes:(UIUserNotificationTypeAlert | UIUserNotificationTypeBadge |
UIUserNotificationTypeSound)
categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:set];
data->notification = [[UILocalNotification alloc] init];
if (data->notification) {
// iOS8 doesn't need the timer trick for the local notification.
data->notification.category = @"incoming_call";
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8 &&
[self lpConfigBoolForKey:@"repeat_call_notification"] == NO) {
NSString *ring = ([LinphoneManager bundleFile:[self lpConfigStringForKey:@"local_ring"
inSection:@"sound"]
.lastPathComponent]
?: [LinphoneManager bundleFile:@"notes_of_the_optimistic.caf"])
.lastPathComponent;
data->notification.soundName = ring;
} else {
data->notification.soundName = @"shortring.caf";
data->timer = [NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:@selector(localNotifContinue:)
userInfo:data->notification
repeats:TRUE];
}
data->notification.repeatInterval = 0;
data->notification.alertBody =
[NSString stringWithFormat:NSLocalizedString(@"IC_MSG", nil), address];
// data->notification.alertAction = NSLocalizedString(@"Answer", nil);
data->notification.userInfo = @{ @"callId" : callId, @"timer" : [NSNumber numberWithInt:1] };
data->notification.applicationIconBadgeNumber = 1;
UIApplication *app = [UIApplication sharedApplication];
LOGI([app currentUserNotificationSettings].description);
[app presentLocalNotificationNow:data->notification];
if (!incallBgTask) {
incallBgTask =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
LOGW(@"Call cannot ring any more, too late");
[[UIApplication sharedApplication] endBackgroundTask:incallBgTask];
incallBgTask = 0;
}];
if (data->timer) {
[[NSRunLoop currentRunLoop] addTimer:data->timer forMode:NSRunLoopCommonModes];
}
}
}
}
}
}
}
// we keep the speaker auto-enabled state in this static so that we don't
// force-enable it on ICE re-invite if the user disabled it.
static BOOL speaker_already_enabled = FALSE;
// Disable speaker when no more call
if ((state == LinphoneCallEnd || state == LinphoneCallError)) {
[HistoryListTableView saveDataToUserDefaults];
[[UIDevice currentDevice] setProximityMonitoringEnabled:FALSE];
speaker_already_enabled = FALSE;
if (linphone_core_get_calls_nb(theLinphoneCore) == 0) {
[self setSpeakerEnabled:FALSE];
[self removeCTCallCenterCb];
// disable this because I don't find anygood reason for it: _bluetoothAvailable = FALSE;
// furthermore it introduces a bug when calling multiple times since route may not be
// reconfigured between cause leading to bluetooth being disabled while it should not
_bluetoothEnabled = FALSE;
}
if (incallBgTask) {
[[UIApplication sharedApplication] endBackgroundTask:incallBgTask];
incallBgTask = 0;
}
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
if (data->timer) {
[data->timer invalidate];
data->timer = nil;
}
LinphoneCallLog *UNlog = linphone_call_get_call_log(call);
if ((UNlog == NULL
|| linphone_call_log_get_status(UNlog) == LinphoneCallMissed
|| linphone_call_log_get_status(UNlog) == LinphoneCallAborted
|| linphone_call_log_get_status(UNlog) == LinphoneCallEarlyAborted)) {
UNMutableNotificationContent *missed_content = [[UNMutableNotificationContent alloc] init];
missed_content.title = NSLocalizedString(@"Missed call", nil);
missed_content.body = address;
UNNotificationRequest *missed_req = [UNNotificationRequest requestWithIdentifier:@"call_request"
content:missed_content
trigger:NULL];
[UNUserNotificationCenter.currentNotificationCenter addNotificationRequest:missed_req
withCompletionHandler:^(NSError *_Nullable error)
{if (error) LOGD(@"Error while adding notification request : %@", error.description);}];
}
}
LinphoneCallLog *callLog2 = linphone_call_get_call_log(call);
const char *call_id2 = linphone_call_log_get_call_id(callLog2);
NSString *callId2 = call_id2
? [NSString stringWithUTF8String:call_id2]
: @"";
NSUUID *uuid = (NSUUID *)[self.providerDelegate.uuids objectForKey:callId2];
if (uuid) {
LinphoneCall *callKit_call = (LinphoneCall *)linphone_core_get_calls(LC)
? linphone_core_get_calls(LC)->data
: NULL;
const char *callKit_callId = callKit_call
? linphone_call_log_get_call_id(linphone_call_get_call_log(callKit_call))
: NULL;
if (callKit_callId && !_conf) {
// Create a CallKit call because there's not !
NSString *callKit_callIdNS = [NSString stringWithUTF8String:callKit_callId];
NSUUID *callKit_uuid = [NSUUID UUID];
[LinphoneManager.instance.providerDelegate.uuids setObject:callKit_uuid forKey:callKit_callIdNS];
[LinphoneManager.instance.providerDelegate.calls setObject:callKit_callIdNS forKey:callKit_uuid];
NSString *address = [FastAddressBook displayNameForAddress:linphone_call_get_remote_address(callKit_call)];
CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:address];
CXStartCallAction *act = [[CXStartCallAction alloc] initWithCallUUID:callKit_uuid handle:handle];
CXTransaction *tr = [[CXTransaction alloc] initWithAction:act];
[LinphoneManager.instance.providerDelegate.controller requestTransaction:tr completion:^(NSError *err){}];
[LinphoneManager.instance.providerDelegate.provider reportOutgoingCallWithUUID:callKit_uuid startedConnectingAtDate:nil];
[LinphoneManager.instance.providerDelegate.provider reportOutgoingCallWithUUID:callKit_uuid connectedAtDate:nil];
}
CXEndCallAction *act = [[CXEndCallAction alloc] initWithCallUUID:uuid];
CXTransaction *tr = [[CXTransaction alloc] initWithAction:act];
[LinphoneManager.instance.providerDelegate.controller requestTransaction:tr completion:^(NSError *err){}];
LOGI(@"CallKit - clearing CK as call ended on uuid %@",uuid);
[LinphoneManager.instance.providerDelegate.provider reportOutgoingCallWithUUID:uuid connectedAtDate:[NSDate date]];
[self.providerDelegate.uuids removeObjectForKey:callId2];
[self.providerDelegate.calls removeObjectForKey:uuid];
[self.providerDelegate.provider reportCallWithUUID:uuid endedAtDate:[NSDate date] reason:(state == LinphoneCallError ? CXCallEndedReasonFailed : CXCallEndedReasonRemoteEnded)];
} else { // Can happen when Call-ID changes (Replaces header)
if (linphone_core_get_calls_nb(LC) ==0) { // Need to clear all CK calls
for (NSUUID *myUuid in self.providerDelegate.calls) {
[self.providerDelegate.provider reportCallWithUUID:myUuid
endedAtDate:NULL
reason:(state == LinphoneCallError
? CXCallEndedReasonFailed
: CXCallEndedReasonRemoteEnded)];
}
[self.providerDelegate.uuids removeAllObjects];
[self.providerDelegate.calls removeAllObjects];
}
}
} else {
if (data != nil && data->notification != nil) {
LinphoneCallLog *log = linphone_call_get_call_log(call);
// cancel local notif if needed
if (data->timer) {
[data->timer invalidate];
data->timer = nil;
}
[[UIApplication sharedApplication] cancelLocalNotification:data->notification];
data->notification = nil;
if (log == NULL || linphone_call_log_get_status(log) == LinphoneCallMissed) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.repeatInterval = 0;
notification.alertBody = [NSString stringWithFormat:
NSLocalizedString(@"You missed a call from %@",nil), address];
notification.alertAction = NSLocalizedString(@"Show", nil);
notification.userInfo = [NSDictionary dictionaryWithObject: [NSString stringWithUTF8String:linphone_call_log_get_call_id(log)]
forKey:@"callLog"];
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
}
}
if (state == LinphoneCallError)
[PhoneMainView.instance popCurrentView];
}
if (state == LinphoneCallReleased) {
if (data != NULL) {
linphone_call_set_user_data(call, NULL);
CFBridgingRelease((__bridge CFTypeRef)(data));
}
}
// Enable speaker when video
if (state == LinphoneCallIncomingReceived || state == LinphoneCallOutgoingInit ||
state == LinphoneCallConnected || state == LinphoneCallStreamsRunning) {
if (linphone_call_params_video_enabled( linphone_call_get_current_params(call)) && !speaker_already_enabled && !_bluetoothEnabled) {
[self setSpeakerEnabled:TRUE];
speaker_already_enabled = TRUE;
}
}
if (state == LinphoneCallStreamsRunning) {
if (_speakerBeforePause) {
_speakerBeforePause = FALSE;
[self setSpeakerEnabled:TRUE];
speaker_already_enabled = TRUE;
}
}
if (state == LinphoneCallConnected && !mCallCenter) {
/*only register CT call center CB for connected call*/
[self setupGSMInteraction];
[[UIDevice currentDevice] setProximityMonitoringEnabled:!(_speakerEnabled || _bluetoothEnabled)];
}
// Post event
NSDictionary *dict = @{@"call" : [NSValue valueWithPointer:call],
@"state" : [NSNumber numberWithInt:state],
@"message" : [NSString stringWithUTF8String:message]};
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate
object:self
userInfo:dict];
}
static void linphone_iphone_call_state(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState state,
const char *message) {
[(__bridge LinphoneManager *)linphone_core_cbs_get_user_data(linphone_core_get_current_callbacks(lc)) onCall:call StateChanged:state withMessage:message];
}
#pragma mark - Transfert State Functions
static void linphone_iphone_transfer_state_changed(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState state) {
@ -1563,15 +1213,6 @@ static void linphone_iphone_is_composing_received(LinphoneCore *lc, LinphoneChat
[[UIApplication sharedApplication] endBackgroundTask:coreIterateTaskId];
}
- (void)audioSessionInterrupted:(NSNotification *)notification {
int interruptionType = [notification.userInfo[AVAudioSessionInterruptionTypeKey] intValue];
if (interruptionType == AVAudioSessionInterruptionTypeBegan) {
[self beginInterruption];
} else if (interruptionType == AVAudioSessionInterruptionTypeEnded) {
[self endInterruption];
}
}
/** Should be called once per linphone_core_new() */
- (void)finishCoreConfiguration {
//Force keep alive to workaround push notif on chat message
@ -1663,7 +1304,6 @@ static BOOL libStarted = FALSE;
// create linphone core
[self createLinphoneCore];
[self.providerDelegate config];
_iapManager = [[InAppProductsManager alloc] init];
// - Security fix - remove multi transport migration, because it enables tcp or udp, if by factoring settings only
@ -1786,7 +1426,6 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
LinphoneFactory *factory = linphone_factory_get();
LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(factory);
linphone_core_cbs_set_call_state_changed(cbs, linphone_iphone_call_state);
linphone_core_cbs_set_registration_state_changed(cbs,linphone_iphone_registration_state);
linphone_core_cbs_set_notify_presence_received_for_uri_or_tel(cbs, linphone_iphone_notify_presence_received_for_uri_or_tel);
linphone_core_cbs_set_authentication_requested(cbs, linphone_iphone_popup_password_request);
@ -1805,6 +1444,10 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
theLinphoneCore = linphone_factory_create_core_with_config_3(factory, _configDb, NULL);
linphone_core_add_callbacks(theLinphoneCore, cbs);
// Add call changed callback by swift
[CallManager.instance configCallManagerWithCore:theLinphoneCore db:_configDb];
linphone_core_start(theLinphoneCore);
// Let the core handle cbs
@ -1832,10 +1475,6 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
(or skipped).
Wait for this to finish the code configuration */
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(audioSessionInterrupted:)
name:AVAudioSessionInterruptionNotification
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(globalStateChangedNotificationHandler:)
name:kLinphoneGlobalStateUpdate
@ -1896,31 +1535,10 @@ static int comp_call_id(const LinphoneCall *call, const char *callid) {
return strcmp(linphone_call_log_get_call_id(linphone_call_get_call_log(call)), callid);
}
- (LinphoneCall *)callByCallId:(NSString *)call_id {
const bctbx_list_t *calls = linphone_core_get_calls(theLinphoneCore);
if (!calls || !call_id) {
return NULL;
}
bctbx_list_t *call_tmp = bctbx_list_find_custom(calls, (bctbx_compare_func)comp_call_id, [call_id UTF8String]);
if (!call_tmp) {
return NULL;
}
LinphoneCall *call = (LinphoneCall *)call_tmp->data;
return call;
}
- (void)cancelLocalNotifTimerForCallId:(NSString *)callid {
// first, make sure this callid is not already involved in a call
const bctbx_list_t *calls = linphone_core_get_calls(theLinphoneCore);
bctbx_list_t *call = bctbx_list_find_custom(calls, (bctbx_compare_func)comp_call_id, [callid UTF8String]);
if (call != NULL) {
LinphoneCallAppData *data =
(__bridge LinphoneCallAppData *)(linphone_call_get_user_data((LinphoneCall *)call->data));
if (data->timer)
[data->timer invalidate];
data->timer = nil;
return;
}
}
- (void)acceptCallForCallId:(NSString *)callid {
@ -1930,7 +1548,7 @@ static int comp_call_id(const LinphoneCall *call, const char *callid) {
if (call != NULL) {
const LinphoneVideoPolicy *video_policy = linphone_core_get_video_policy(theLinphoneCore);
bool with_video = video_policy->automatically_accept;
[self acceptCall:(LinphoneCall *)call->data evenWithVideo:with_video];
[CallManager.instance acceptCallWithCall:(LinphoneCall *)call->data hasVideo:with_video];
return;
};
}
@ -2198,19 +1816,6 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
[self enableProxyPublish:YES];
}
- (void)beginInterruption {
LinphoneCall *c = linphone_core_get_current_call(theLinphoneCore);
LOGI(@"Sound interruption detected!");
if (c && linphone_call_get_state(c) == LinphoneCallStreamsRunning) {
_speakerBeforePause = _speakerEnabled;
linphone_call_pause(c);
}
}
- (void)endInterruption {
LOGI(@"Sound interruption ended!");
}
- (void)refreshRegisters {
linphone_core_refresh_registers(theLinphoneCore); // just to make sure REGISTRATION is up to date
}
@ -2278,21 +1883,6 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
}
#pragma mark - Audio route Functions
- (bool)allowSpeaker {
if (IPAD)
return true;
bool allow = true;
AVAudioSessionRouteDescription *newRoute = [AVAudioSession sharedInstance].currentRoute;
if (newRoute && newRoute.outputs.count > 0) {
NSString *route = newRoute.outputs[0].portType;
allow = !([route isEqualToString:AVAudioSessionPortLineOut] ||
[route isEqualToString:AVAudioSessionPortHeadphones] ||
[[AudioHelper bluetoothRoutes] containsObject:route]);
}
return allow;
}
- (void)audioRouteChangeListenerCallback:(NSNotification *)notif {
if (IPAD)
return;
@ -2311,12 +1901,12 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
NSString *route = newRoute.outputs[0].portType;
LOGI(@"Current audio route is [%s]", [route UTF8String]);
_speakerEnabled = [route isEqualToString:AVAudioSessionPortBuiltInSpeaker];
if (([[AudioHelper bluetoothRoutes] containsObject:route]) && !_speakerEnabled) {
CallManager.instance.speakerEnabled = [route isEqualToString:AVAudioSessionPortBuiltInSpeaker];
if (([[AudioHelper bluetoothRoutes] containsObject:route]) && !CallManager.instance.speakerEnabled) {
_bluetoothAvailable = TRUE;
_bluetoothEnabled = TRUE;
CallManager.instance.bluetoothEnabled = TRUE;
} else
_bluetoothEnabled = FALSE;
CallManager.instance.bluetoothEnabled = FALSE;
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:_bluetoothAvailable], @"available", nil];
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneBluetoothAvailabilityUpdate
@ -2325,77 +1915,30 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
}
}
- (void)setSpeakerEnabled:(BOOL)enable {
_speakerEnabled = enable;
NSError *err = nil;
if (enable && [self allowSpeaker]) {
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&err];
[[UIDevice currentDevice] setProximityMonitoringEnabled:FALSE];
_bluetoothEnabled = FALSE;
} else {
AVAudioSessionPortDescription *builtinPort = [AudioHelper builtinAudioDevice];
[[AVAudioSession sharedInstance] setPreferredInput:builtinPort error:&err];
[[UIDevice currentDevice] setProximityMonitoringEnabled:(linphone_core_get_calls_nb(LC) > 0)];
}
if (err) {
LOGE(@"Failed to change audio route: err %@", err.localizedDescription);
err = nil;
}
}
- (void)setBluetoothEnabled:(BOOL)enable {
if (_bluetoothAvailable) {
// The change of route will be done in setSpeakerEnabled
_bluetoothEnabled = enable;
if (_bluetoothEnabled) {
CallManager.instance.bluetoothEnabled = enable;
if (CallManager.instance.bluetoothEnabled) {
NSError *err = nil;
AVAudioSessionPortDescription *_bluetoothPort = [AudioHelper bluetoothAudioDevice];
[[AVAudioSession sharedInstance] setPreferredInput:_bluetoothPort error:&err];
// if setting bluetooth failed, it must be because the device is not available
// anymore (disconnected), so deactivate bluetooth.
if (err) {
_bluetoothEnabled = FALSE;
CallManager.instance.bluetoothEnabled = FALSE;
LOGE(@"Failed to enable bluetooth: err %@", err.localizedDescription);
err = nil;
} else {
_speakerEnabled = FALSE;
CallManager.instance.speakerEnabled = FALSE;
return;
}
}
}
[self setSpeakerEnabled:_speakerEnabled];
[CallManager.instance setSpeakerEnabled:CallManager.instance.speakerEnabled];
}
#pragma mark - Call Functions
- (void)acceptCall:(LinphoneCall *)call evenWithVideo:(BOOL)video {
LinphoneCallParams *lcallParams = linphone_core_create_call_params(theLinphoneCore, call);
if (!lcallParams) {
LOGW(@"Could not create call parameters for %p, call has probably already ended.", call);
return;
}
if ([self lpConfigBoolForKey:@"edge_opt_preference"]) {
bool low_bandwidth = self.network == network_2g;
if (low_bandwidth) {
LOGI(@"Low bandwidth mode");
}
linphone_call_params_enable_low_bandwidth(lcallParams, low_bandwidth);
}
linphone_call_params_enable_video(lcallParams, video);
//We set the record file name here because we can't do it after the call is started.
NSString *writablePath = [LinphoneUtils recordingFilePathFromCall:linphone_call_log_get_from_address(linphone_call_get_call_log(call))];
LOGD(@"record file path: %@\n", writablePath);
linphone_call_params_set_record_file(lcallParams, [writablePath cStringUsingEncoding:NSUTF8StringEncoding]);
linphone_call_accept_with_params(call, lcallParams);
linphone_call_params_unref(lcallParams);
}
- (void)send:(NSString *)replyText toChatRoom:(LinphoneChatRoom *)room {
LinphoneChatMessage *msg = linphone_chat_room_create_message(room, replyText.UTF8String);
linphone_chat_message_send(msg);
@ -2443,81 +1986,8 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
return;
}
if (linphone_core_get_calls_nb(theLinphoneCore) < 1 &&
floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max &&
self.providerDelegate.callKitCalls < 1) {
self.providerDelegate.callKitCalls++;
NSUUID *uuid = [NSUUID UUID];
[LinphoneManager.instance.providerDelegate.uuids setObject:uuid forKey:@""];
[LinphoneManager.instance.providerDelegate.calls setObject:@"" forKey:uuid];
LinphoneManager.instance.providerDelegate.pendingAddr = linphone_address_clone(iaddr);
NSString *address = [FastAddressBook displayNameForAddress:iaddr];
CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:address];
CXStartCallAction *act = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:handle];
CXTransaction *tr = [[CXTransaction alloc] initWithAction:act];
[LinphoneManager.instance.providerDelegate.controller requestTransaction:tr
completion:^(NSError *err){
}];
} else {
[self doCall:iaddr];
}
}
- (BOOL)doCall:(const LinphoneAddress *)iaddr {
return [self doCallWithSas:iaddr isSas:false];
}
- (BOOL)doCallWithSas:(const LinphoneAddress *)iaddr isSas:(BOOL)isSas {
LinphoneAddress *addr = linphone_address_clone(iaddr);
NSString *displayName = [FastAddressBook displayNameForAddress:addr];
// Finally we can make the call
LinphoneCallParams *lcallParams = linphone_core_create_call_params(theLinphoneCore, NULL);
if ([self lpConfigBoolForKey:@"edge_opt_preference"] && (self.network == network_2g)) {
LOGI(@"Enabling low bandwidth mode");
linphone_call_params_enable_low_bandwidth(lcallParams, YES);
}
if (displayName != nil) {
linphone_address_set_display_name(addr, displayName.UTF8String);
}
if ([LinphoneManager.instance lpConfigBoolForKey:@"override_domain_with_default_one"]) {
linphone_address_set_domain(
addr, [[LinphoneManager.instance lpConfigStringForKey:@"domain" inSection:@"assistant"] UTF8String]);
}
LinphoneCall *call;
if (LinphoneManager.instance.nextCallIsTransfer) {
char *caddr = linphone_address_as_string(addr);
call = linphone_core_get_current_call(theLinphoneCore);
linphone_call_transfer(call, caddr);
LinphoneManager.instance.nextCallIsTransfer = NO;
ms_free(caddr);
} else {
//We set the record file name here because we can't do it after the call is started.
NSString *writablePath = [LinphoneUtils recordingFilePathFromCall:addr];
LOGD(@"record file path: %@\n", writablePath);
linphone_call_params_set_record_file(lcallParams, [writablePath cStringUsingEncoding:NSUTF8StringEncoding]);
if (isSas)
linphone_call_params_set_media_encryption(lcallParams, LinphoneMediaEncryptionZRTP);
call = linphone_core_invite_address_with_params(theLinphoneCore, addr, lcallParams);
if (call) {
// The LinphoneCallAppData object should be set on call creation with callback
// - (void)onCall:StateChanged:withMessage:. If not, we are in big trouble and expect it to crash
// We are NOT responsible for creating the AppData.
LinphoneCallAppData *data = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
if (data == nil) {
LOGE(@"New call instanciated but app data was not set. Expect it to crash.");
/* will be used later to notify user if video was not activated because of the linphone core*/
} else {
data->videoRequested = linphone_call_params_video_enabled(lcallParams);
}
}
}
linphone_address_destroy(addr);
linphone_call_params_destroy(lcallParams);
return TRUE;
// For OutgoingCall, show CallOutgoingView
[CallManager.instance startCallWithAddr:iaddr isSas:FALSE];
}
#pragma mark - Property Functions
@ -2835,6 +2305,10 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
mCallCenter = nil;
}
- (BOOL)isCTCallCenterExist {
return mCallCenter != nil;
}
- (void)setupGSMInteraction {
[self removeCTCallCenterCb];
@ -2858,7 +2332,7 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
if ([ct currentCalls] != nil) {
if (call) {
LOGI(@"Pausing SIP call because GSM call");
_speakerBeforePause = _speakerEnabled;
CallManager.instance.speakerBeforePause = CallManager.instance.speakerEnabled;
linphone_call_pause(call);
[self startCallPausedLongRunningTask];
} else if (linphone_core_is_in_conference(theLinphoneCore)) {

View file

@ -102,7 +102,7 @@
[self setImage:[UIImage imageNamed:@"call_audio_start_disabled.png"] forState:UIControlStateDisabled];
}
if (LinphoneManager.instance.nextCallIsTransfer) {
if (CallManager.nextCallIsTransfer) {
[self setImage:[UIImage imageNamed:@"call_transfer_default.png"] forState:UIControlStateNormal];
[self setImage:[UIImage imageNamed:@"call_transfer_disabled.png"] forState:UIControlStateDisabled];
} else if (linphone_core_get_calls_nb(LC) > 0) {

View file

@ -55,10 +55,7 @@
- (IBAction)onSecurityCallClick:(id)sender {
const LinphoneAddress *addr = linphone_participant_device_get_address(_device);
if (addr)
[LinphoneManager.instance doCallWithSas:addr isSas:TRUE];
else
LOGE(@"CallKit : No call address");
[CallManager.instance startCallWithAddr:(LinphoneAddress *)addr isSas:TRUE];
}
@end

View file

@ -60,10 +60,7 @@
- (IBAction)onSecurityCallClick:(id)sender {
LinphoneParticipantDevice *device = (LinphoneParticipantDevice *)bctbx_list_nth_data(_devices, 0);
const LinphoneAddress *addr = linphone_participant_device_get_address(device);
if (addr)
[LinphoneManager.instance doCallWithSas:addr isSas:TRUE];
else
LOGE(@"CallKit : No call address");
[CallManager.instance startCallWithAddr:(LinphoneAddress *)addr isSas:TRUE];
}
#pragma mark - TableView

View file

@ -82,7 +82,7 @@
switch (type) {
case UIPauseButtonType_Call: {
if (call != nil) {
LinphoneManager.instance.speakerBeforePause = LinphoneManager.instance.speakerEnabled;
CallManager.instance.speakerBeforePause = CallManager.instance.speakerEnabled;
linphone_call_pause(call);
} else {
LOGW(@"Cannot toggle pause buttton, because no current call");
@ -99,7 +99,7 @@
case UIPauseButtonType_CurrentCall: {
LinphoneCall *currentCall = [UIPauseButton getCall];
if (currentCall != nil) {
LinphoneManager.instance.speakerBeforePause = LinphoneManager.instance.speakerEnabled;
CallManager.instance.speakerBeforePause = CallManager.instance.speakerEnabled;
linphone_call_pause(currentCall);
} else {
LOGW(@"Cannot toggle pause buttton, because no current call");
@ -120,18 +120,6 @@
break;
}
case UIPauseButtonType_Conference: {
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
NSString *key = (NSString *)[LinphoneManager.instance.providerDelegate.uuids allKeys][0];
NSUUID *uuid = (NSUUID *)[LinphoneManager.instance.providerDelegate.uuids objectForKey:key];
if (!uuid) {
return;
}
CXSetHeldCallAction *act = [[CXSetHeldCallAction alloc] initWithCallUUID:uuid onHold:NO];
CXTransaction *tr = [[CXTransaction alloc] initWithAction:act];
[LinphoneManager.instance.providerDelegate.controller requestTransaction:tr
completion:^(NSError *err){
}];
}
linphone_core_enter_conference(LC);
// Fake event
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];

View file

@ -47,16 +47,16 @@ INIT_WITH_COMMON_CF {
}
- (void)onOn {
[LinphoneManager.instance setSpeakerEnabled:TRUE];
[CallManager.instance setSpeakerEnabled:TRUE];
}
- (void)onOff {
[LinphoneManager.instance setSpeakerEnabled:FALSE];
[CallManager.instance setSpeakerEnabled:FALSE];
}
- (bool)onUpdate {
self.enabled = [LinphoneManager.instance allowSpeaker];
return [LinphoneManager.instance speakerEnabled];
self.enabled = [CallManager.instance allowSpeaker];
return CallManager.instance.speakerEnabled;
}
@end

View file

@ -42,9 +42,10 @@ INIT_WITH_COMMON_CF {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) {
LinphoneCallAppData *callAppData = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
callAppData->videoRequested =
TRUE; /* will be used later to notify user if video was not activated because of the linphone core*/
NSString *callId = [NSString stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(call))];
CallAppData *data = [CallManager.instance getAppDataWithCallId:callId];
data.videoRequested = TRUE;/* will be used later to notify user if video was not activated because of the linphone core*/
[CallManager.instance setAppDataWithCallId:callId appData:data];
LinphoneCallParams *call_params = linphone_core_create_call_params(LC,call);
linphone_call_params_enable_video(call_params, TRUE);
linphone_call_update(call, call_params);
@ -58,7 +59,7 @@ INIT_WITH_COMMON_CF {
if (!linphone_core_video_display_enabled(LC))
return;
[LinphoneManager.instance setSpeakerEnabled:FALSE];
[CallManager.instance setSpeakerEnabled:FALSE];
[self setEnabled:FALSE];
[waitView startAnimating];

View file

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "LinphoneManager.h"
#import "linphone/core.h"
#define LOGV(level, ...) [Log log:level file:__FILE__ line:__LINE__ format:__VA_ARGS__]
#define LOGD(...) LOGV(ORTP_DEBUG, __VA_ARGS__)
@ -31,6 +31,7 @@
+ (void)log:(OrtpLogLevel)severity file:(const char *)file line:(int)line format:(NSString *)format, ...;
+ (void)enableLogs:(OrtpLogLevel)level;
+ (void)directLog:(OrtpLogLevel)level text:(NSString *)text;
void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args);
@end

View file

@ -361,6 +361,10 @@ static RootViewManager *rootViewManagerInstance = nil;
switch (state) {
case LinphoneCallIncomingReceived:
if (!CallManager.callKitEnabled) {
[self displayIncomingCall:call];
}
break;
case LinphoneCallIncomingEarlyMedia: {
if (linphone_core_get_calls_nb(LC) > 1 ||
(floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
@ -374,36 +378,15 @@ static RootViewManager *rootViewManagerInstance = nil;
}
case LinphoneCallPausedByRemote:
case LinphoneCallConnected: {
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max && call) {
NSString *callId =
[NSString stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(call))];
NSUUID *uuid = [LinphoneManager.instance.providerDelegate.uuids objectForKey:callId];
if (uuid) {
[LinphoneManager.instance.providerDelegate.provider reportOutgoingCallWithUUID:uuid
startedConnectingAtDate:nil];
}
if (![LinphoneManager.instance isCTCallCenterExist]) {
/*only register CT call center CB for connected call*/
[LinphoneManager.instance setupGSMInteraction];
[[UIDevice currentDevice] setProximityMonitoringEnabled:!(CallManager.instance.speakerEnabled || CallManager.instance.bluetoothEnabled)];
}
break;
}
case LinphoneCallStreamsRunning: {
[self changeCurrentView:CallView.compositeViewDescription];
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max && call) {
NSString *callId =
[NSString stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(call))];
NSUUID *uuid = [LinphoneManager.instance.providerDelegate.uuids objectForKey:callId];
if (uuid) {
[LinphoneManager.instance.providerDelegate.provider reportOutgoingCallWithUUID:uuid
connectedAtDate:nil];
NSString *address = [FastAddressBook displayNameForAddress:linphone_call_get_remote_address(call)];
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:address];
update.supportsGrouping = TRUE;
update.supportsDTMF = TRUE;
update.supportsHolding = TRUE;
update.supportsUngrouping = TRUE;
[LinphoneManager.instance.providerDelegate.provider reportCallWithUUID:uuid updated:update];
}
}
break;
}
case LinphoneCallUpdatedByRemote: {
@ -437,18 +420,6 @@ static RootViewManager *rootViewManagerInstance = nil;
break;
case LinphoneCallOutgoingEarlyMedia:
case LinphoneCallOutgoingProgress: {
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max && call &&
(linphone_core_get_calls_nb(LC) < 2)) {
// Link call ID to UUID
NSString *callId =
[NSString stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(call))];
NSUUID *uuid = [LinphoneManager.instance.providerDelegate.uuids objectForKey:@""];
if (uuid) {
[LinphoneManager.instance.providerDelegate.uuids removeObjectForKey:@""];
[LinphoneManager.instance.providerDelegate.uuids setObject:uuid forKey:callId];
[LinphoneManager.instance.providerDelegate.calls setObject:callId forKey:uuid];
}
}
break;
}
case LinphoneCallOutgoingRinging:
@ -458,19 +429,6 @@ static RootViewManager *rootViewManagerInstance = nil;
case LinphoneCallReleased:
break;
case LinphoneCallResuming: {
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max && call) {
NSUUID *uuid = (NSUUID *)[LinphoneManager.instance.providerDelegate.uuids
objectForKey:[NSString stringWithUTF8String:linphone_call_log_get_call_id(
linphone_call_get_call_log(call))]];
if (!uuid) {
break;
}
CXSetHeldCallAction *act = [[CXSetHeldCallAction alloc] initWithCallUUID:uuid onHold:NO];
CXTransaction *tr = [[CXTransaction alloc] initWithAction:act];
[LinphoneManager.instance.providerDelegate.controller requestTransaction:tr
completion:^(NSError *err){
}];
}
break;
}
case LinphoneCallUpdating:
@ -800,7 +758,7 @@ static RootViewManager *rootViewManagerInstance = nil;
if (callIDFromPush && autoAnswer) {
// accept call automatically
[lm acceptCall:call evenWithVideo:YES];
[CallManager.instance acceptCallWithCall:call hasVideo:YES];
} else {
AudioServicesPlaySystemSound(lm.sounds.vibrate);
CallIncomingView *view = VIEW(CallIncomingView);
@ -818,10 +776,11 @@ static RootViewManager *rootViewManagerInstance = nil;
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) {
LinphoneCallAppData *callData = (__bridge LinphoneCallAppData *)linphone_call_get_user_data(call);
if (callData != nil) {
NSString *callId = [NSString stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(call))];
CallAppData *data = [CallManager.instance getAppDataWithCallId:callId];
if (data != nil) {
if (state == UIDeviceBatteryStateUnplugged) {
if (level <= 0.2f && !callData->batteryWarningShown) {
if (level <= 0.2f && !data.batteryWarningShown) {
LOGI(@"Battery warning");
DTActionSheet *sheet = [[DTActionSheet alloc]
initWithTitle:NSLocalizedString(@"Battery is running low. Stop video ?", nil)];
@ -836,12 +795,13 @@ static RootViewManager *rootViewManagerInstance = nil;
linphone_call_update(call, params);
}];
[sheet showInView:self.view];
callData->batteryWarningShown = TRUE;
data.batteryWarningShown = TRUE;
}
}
if (level > 0.2f) {
callData->batteryWarningShown = FALSE;
data.batteryWarningShown = FALSE;
}
[CallManager.instance setAppDataWithCallId:callId appData:data];
}
}
}
@ -852,7 +812,7 @@ static RootViewManager *rootViewManagerInstance = nil;
}
- (void)incomingCallAccepted:(LinphoneCall *)call evenWithVideo:(BOOL)video {
[LinphoneManager.instance acceptCall:call evenWithVideo:video];
[CallManager.instance acceptCallWithCall:call hasVideo:video];
}
- (void)incomingCallDeclined:(LinphoneCall *)call {

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2010-2019 Belledonne Communications SARL.
*
* 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 <CallKit/CallKit.h>
#ifndef ProviderDelegate_h
#define ProviderDelegate_h
@interface ProviderDelegate : NSObject <CXProviderDelegate, CXCallObserverDelegate>
@property CXProvider *provider;
@property CXCallObserver *observer;
@property CXCallController *controller;
@property NSMutableDictionary *calls;
@property NSMutableDictionary *uuids;
@property(nonatomic) LinphoneCall *pendingCall;
@property LinphoneAddress *pendingAddr;
@property BOOL pendingCallVideo;
@property int callKitCalls;
- (void)reportIncomingCall:(LinphoneCall *) call withUUID:(NSUUID *)uuid handle:(NSString *)handle video:(BOOL)video;
- (void)config;
- (void)configAudioSession:(AVAudioSession *)audioSession;
@end
#endif /* ProviderDelegate_h */

View file

@ -1,301 +0,0 @@
/*
* Copyright (c) 2010-2019 Belledonne Communications SARL.
*
* 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 "ProviderDelegate.h"
#import "LinphoneManager.h"
#import "PhoneMainView.h"
#include "linphone/linphonecore.h"
#import <AVFoundation/AVAudioSession.h>
#import <Foundation/Foundation.h>
@implementation ProviderDelegate
- (instancetype)init {
self = [super init];
self.calls = [[NSMutableDictionary alloc] init];
self.uuids = [[NSMutableDictionary alloc] init];
self.pendingCall = NULL;
self.pendingAddr = NULL;
self.pendingCallVideo = FALSE;
CXCallController *callController = [[CXCallController alloc] initWithQueue:dispatch_get_main_queue()];
[callController.callObserver setDelegate:self queue:dispatch_get_main_queue()];
self.controller = callController;
self.callKitCalls = 0;
if (!self) {
LOGD(@"ProviderDelegate not initialized...");
}
return self;
}
- (void)config {
CXProviderConfiguration *config = [[CXProviderConfiguration alloc]
initWithLocalizedName:[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]];
config.ringtoneSound = @"notes_of_the_optimistic.caf";
config.supportsVideo = FALSE;
config.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:@"callkit_logo"]);
NSArray *ar = @[ [NSNumber numberWithInt:(int)CXHandleTypeGeneric] ];
NSSet *handleTypes = [[NSSet alloc] initWithArray:ar];
[config setSupportedHandleTypes:handleTypes];
[config setMaximumCallGroups:2];
[config setMaximumCallsPerCallGroup:1];
//not show app's calls in tel's history
//config.includesCallsInRecents = NO;
self.provider = [[CXProvider alloc] initWithConfiguration:config];
[self.provider setDelegate:self queue:dispatch_get_main_queue()];
}
- (void)configAudioSession:(AVAudioSession *)audioSession {
NSError *err = nil;
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
mode:AVAudioSessionModeVoiceChat
options:AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP
error:&err];
if (err) {
LOGE(@"Unable to change audio session because: %@", err.localizedDescription);
err = nil;
}
[audioSession setMode:AVAudioSessionModeVoiceChat error:&err];
if (err) {
LOGE(@"Unable to change audio mode because : %@", err.localizedDescription);
err = nil;
}
double sampleRate = 48000.0;
[audioSession setPreferredSampleRate:sampleRate error:&err];
if (err) {
LOGE(@"Unable to change preferred sample rate because : %@", err.localizedDescription);
err = nil;
}
}
- (void)reportIncomingCall:(LinphoneCall *) call withUUID:(NSUUID *)uuid handle:(NSString *)handle video:(BOOL)video; {
// Create update to describe the incoming call and caller
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
update.supportsDTMF = TRUE;
update.supportsHolding = TRUE;
update.supportsGrouping = TRUE;
update.supportsUngrouping = TRUE;
update.hasVideo = _pendingCallVideo = video;
// Report incoming call to system
LOGD(@"CallKit: report new incoming call with call-id: [%@] and UUID: [%@]", [_calls objectForKey:uuid], uuid);
[self.provider reportNewIncomingCallWithUUID:uuid
update:update
completion:^(NSError *error) {
if (error) {
LOGE(@"CallKit: cannot complete incoming call with call-id: [%@] and UUID: [%@] from [%@] caused by [%@]",
[_calls objectForKey:uuid], uuid, handle, [error localizedDescription]);
if ([error code] == CXErrorCodeIncomingCallErrorFilteredByDoNotDisturb ||
[error code] == CXErrorCodeIncomingCallErrorFilteredByBlockList)
linphone_call_decline(call,LinphoneReasonBusy); /*to give a chance for other devices to answer*/
else
linphone_call_decline(call,LinphoneReasonUnknown);
}
}];
}
- (void)setPendingCall:(LinphoneCall *)pendingCall {
if (pendingCall) {
_pendingCall = pendingCall;
if (_pendingCall)
linphone_call_ref(_pendingCall);
} else if (_pendingCall) {
linphone_call_unref(_pendingCall);
_pendingCall = NULL;
}
}
#pragma mark - CXProviderDelegate Protocol
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
NSUUID *uuid = action.callUUID;
NSString *callID = [self.calls objectForKey:uuid]; // first, make sure this callid is not already involved in a call
LOGD(@"CallKit: Answering call with call-id: [%@] and UUID: [%@]", callID, uuid);
[self configAudioSession:[AVAudioSession sharedInstance]];
[action fulfill];
LinphoneCall *call = [LinphoneManager.instance callByCallId:callID];
if (!call)
return;
self.callKitCalls++;
self.pendingCall = call;
}
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
NSUUID *uuid = action.callUUID;
NSString *callID = [self.calls objectForKey:uuid]; // first, make sure this callid is not already involved in a call
LOGD(@"CallKit: Starting Call with call-id: [%@] and UUID: [%@]", callID, uuid);
// To restart Audio Unit
[self configAudioSession:[AVAudioSession sharedInstance]];
[action fulfill];
LinphoneCall *call;
if (![callID isEqualToString:@""]) {
call = linphone_core_get_current_call(LC);
} else {
call = [LinphoneManager.instance callByCallId:callID];
}
if (call != NULL) {
self.callKitCalls++;
self.pendingCall = call;
}
}
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
self.callKitCalls--;
[action fulfill];
if (linphone_core_is_in_conference(LC)) {
LinphoneManager.instance.conf = TRUE;
linphone_core_terminate_conference(LC);
LOGD(@"CallKit: Ending the conference");
} else if (linphone_core_get_calls_nb(LC) > 1) {
LinphoneManager.instance.conf = TRUE;
linphone_core_terminate_all_calls(LC);
LOGD(@"CallKit: Ending all the ongoing calls");
} else {
NSUUID *uuid = action.callUUID;
NSString *callID = [self.calls objectForKey:uuid];
if (callID) {
LOGD(@"CallKit: Ending the call with call-id: [%@] and UUID: [%@]", callID, uuid);
LinphoneCall *call = [LinphoneManager.instance callByCallId:callID];
if (call) {
linphone_call_terminate((LinphoneCall *)call);
}
[self.uuids removeObjectForKey:callID];
[self.calls removeObjectForKey:uuid];
}
}
}
- (void)provider:(CXProvider *)provider performSetMutedCallAction:(nonnull CXSetMutedCallAction *)action {
[action fulfill];
if ([[PhoneMainView.instance currentView] equal:CallView.compositeViewDescription]) {
CallView *view = (CallView *)[PhoneMainView.instance popToView:CallView.compositeViewDescription];
[view.microButton toggle];
}
}
- (void)provider:(CXProvider *)provider performSetHeldCallAction:(nonnull CXSetHeldCallAction *)action {
[action fulfill];
if (linphone_core_is_in_conference(LC) && action.isOnHold) {
linphone_core_leave_conference(LC);
LOGD(@"CallKit: Leaving conference");
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
return;
}
if (linphone_core_get_calls_nb(LC) > 1 && action.isOnHold) {
linphone_core_pause_all_calls(LC);
LOGD(@"CallKit: Pausing all ongoing calls");
return;
}
NSUUID *uuid = action.callUUID;
NSString *callID = [self.calls objectForKey:uuid];
if (!callID) {
return;
}
LOGD(@"CallKit: Call with call-id: [%@] and UUID: [%@] paused status changed to: []", callID, uuid, action.isOnHold ? @"Paused" : @"Resumed");
LinphoneCall *call = [LinphoneManager.instance callByCallId:callID];
if (!call)
return;
if (action.isOnHold) {
LinphoneManager.instance.speakerBeforePause = LinphoneManager.instance.speakerEnabled;
linphone_call_pause((LinphoneCall *)call);
} else {
if (linphone_core_get_conference(LC) && linphone_core_get_calls_nb(LC) > 1) {
linphone_core_enter_conference(LC);
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
} else {
[self configAudioSession:[AVAudioSession sharedInstance]];
self.pendingCall = call;
}
}
}
- (void)provider:(CXProvider *)provider performPlayDTMFCallAction:(CXPlayDTMFCallAction *)action {
[action fulfill];
NSUUID *uuid = action.callUUID;
NSString *callID = [self.calls objectForKey:uuid];
LOGD(@"CallKit: playing DTMF for call with call-id: [%@] and UUID: [%@]", callID, uuid);
LinphoneCall *call = [LinphoneManager.instance callByCallId:callID];
char digit = action.digits.UTF8String[0];
linphone_call_send_dtmf((LinphoneCall *)call, digit);
}
- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession {
LOGD(@"CallKit: Audio session activated");
// Now we can (re)start the call
if (self.pendingCall) {
LinphoneCallState state = linphone_call_get_state(self.pendingCall);
switch (state) {
case LinphoneCallIncomingReceived:
[LinphoneManager.instance acceptCall:(LinphoneCall *)self.pendingCall evenWithVideo:_pendingCallVideo];
break;
case LinphoneCallPaused:
linphone_call_resume((LinphoneCall *)self.pendingCall);
break;
case LinphoneCallStreamsRunning:
// May happen when multiple calls
break;
default:
break;
}
} else {
if (_pendingAddr) {
[LinphoneManager.instance doCall:_pendingAddr];
} else {
LOGE(@"CallKit: No pending call");
}
}
[self setPendingCall:NULL];
if (_pendingAddr)
linphone_address_unref(_pendingAddr);
_pendingAddr = NULL;
_pendingCallVideo = FALSE;
}
- (void)provider:(CXProvider *)provider didDeactivateAudioSession:(nonnull AVAudioSession *)audioSession {
LOGD(@"CallKit : Audio session deactivated");
[self setPendingCall:NULL];
if (_pendingAddr)
linphone_address_unref(_pendingAddr);
_pendingAddr = NULL;
_pendingCallVideo = FALSE;
}
- (void)providerDidReset:(CXProvider *)provider {
LOGD(@"CallKit: Provider reset");
LinphoneManager.instance.conf = TRUE;
linphone_core_terminate_all_calls(LC);
[self.calls removeAllObjects];
[self.uuids removeAllObjects];
}
#pragma mark - CXCallObserverDelegate Protocol
- (void)callObserver:(CXCallObserver *)callObserver callChanged:(CXCall *)call {
LOGD(@"CallKit: Call changed");
}
@end

View file

@ -0,0 +1,244 @@
/*
* Copyright (c) 2010-2019 Belledonne Communications SARL.
*
* 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
class ProviderDelegate: NSObject {
private let provider: CXProvider
private let callController: CXCallController
var uuids: [String : UUID] = [:]
var calls: [UUID : String] = [:]
var connecteds: [UUID : Bool] = [:]
var addrs: [UUID : Address] = [:]
var outgoingUuids: [String : UUID] = [:]
var isSas: [UUID : Bool] = [:]
override init() {
provider = CXProvider(configuration: ProviderDelegate.providerConfiguration)
callController = CXCallController()
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()
providerConfiguration.supportedHandleTypes = [.generic]
providerConfiguration.maximumCallsPerCallGroup = 3
providerConfiguration.maximumCallGroups = 2
//not show app's calls in tel's history
//providerConfiguration.includesCallsInRecents = YES;
return providerConfiguration
}()
func reportIncomingCall(call:Call?, uuid: UUID, handle: String, hasVideo: Bool) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type:.generic, value: handle)
update.hasVideo = hasVideo
let callId = CallManager.instance().providerDelegate.calls[uuid]
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: report new incoming call with call-id: [\(String(describing: callId))] and UUID: [\(uuid.description)]")
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
} 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
if code == CXErrorCodeIncomingCallError.filteredByBlockList.rawValue || code == CXErrorCodeIncomingCallError.filteredByDoNotDisturb.rawValue {
try? call?.decline(reason: Reason.Busy)
} else {
try? call?.decline(reason: Reason.Unknown)
}
}
}
}
func updateCall(uuid: UUID, handle: String, hasVideo: Bool = false) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type:.generic, value:handle)
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)
}
}
// MARK: - CXProviderDelegate
extension ProviderDelegate: CXProviderDelegate {
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
let uuid = action.callUUID
let callId = calls[uuid]
let addr = addrs[uuid]
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call ended with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
// remove call infos first, otherwise CXEndCallAction will be called more than onece
if (addr != nil) {
addrs.removeValue(forKey: uuid)
outgoingUuids.removeValue(forKey: addr!.asStringUriOnly())
isSas.removeValue(forKey: uuid)
}
let call = CallManager.instance().callByCallId(callId: callId)
if (callId != nil) {
uuids.removeValue(forKey: callId!)
}
calls.removeValue(forKey: uuid)
connecteds.removeValue(forKey: uuid)
if (call != nil) {
do {
try call!.terminate()
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call ended \(uuid) failed because \(error)")
}
}
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
let uuid = action.callUUID
let callId = calls[uuid]
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)
if (call == nil) {
// The application is not yet registered, mark the call as connected. The audio session must be configured here.
CallManager.configAudioSession(audioSession: AVAudioSession.sharedInstance())
CallManager.instance().providerDelegate.connecteds.updateValue(true, forKey: uuid)
} else {
CallManager.instance().acceptCall(call: call!, hasVideo: call!.params?.videoEnabled ?? false)
}
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
let uuid = action.callUUID
let callId = calls[uuid]
let call = CallManager.instance().callByCallId(callId: callId)
if (call != nil && UIApplication.shared.applicationState != .active) {
do {
if action.isOnHold {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call paused with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
try call!.pause()
} else {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call resumed with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
try call!.resume()
}
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call set held (paused or resumed) \(uuid) failed because \(error)")
}
}
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
do {
let uuid = action.callUUID
let addr = addrs[uuid]
if (addr == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: can not call a null address!")
action.fail()
}
try CallManager.instance().doCall(addr: addr!, isSas: CallManager.instance().providerDelegate.isSas[uuid] ?? false)
} 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)).")
do {
try CallManager.instance().lc?.addAllToConference()
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call grouped failed because \(error)")
}
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
let uuid = action.callUUID
let callId = calls[uuid]
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
let callId = calls[uuid]
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
let callId = calls[uuid]
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.")
CallManager.instance().lc?.audioSessionActivated(actived: true)
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: audio session deactivated.")
CallManager.instance().lc?.audioSessionActivated(actived: false)
}
}

View file

@ -78,6 +78,10 @@
}
}
+ (void)directLog:(OrtpLogLevel)level text:(NSString *)text {
bctbx_log(BCTBX_LOG_DOMAIN, level, "%s", text.cString);
}
#pragma mark - Logs Functions callbacks
void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args) {

View file

@ -52,7 +52,6 @@ typedef enum {
+ (NSMutableDictionary <NSString *, PHAsset *> *)photoAssetsDictionary;
+ (NSString *)recordingFilePathFromCall:(const LinphoneAddress *)iaddr;
+ (NSArray *)parseRecordingName:(NSString *)filename;
@end

View file

@ -492,30 +492,6 @@
return addr;
}
+ (NSString *)recordingFilePathFromCall:(const LinphoneAddress *)iaddr {
NSString *filepath = @"recording_";
const char *address = linphone_address_get_username(iaddr);
filepath = [filepath stringByAppendingString:address? [NSString stringWithCString:address encoding:NSUTF8StringEncoding] : @"unknown"];
NSDate * now = [NSDate date];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"E-d-MMM-yyyy-HH-mm-ss"];
NSString *date = [dateFormat stringFromDate:now];
filepath = [filepath stringByAppendingString:@"_"];
filepath = [filepath stringByAppendingString:date];
filepath = [filepath stringByAppendingString:@".mkv"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *writablePath = [paths objectAtIndex:0];
writablePath = [writablePath stringByAppendingString:@"/"];
writablePath = [writablePath stringByAppendingString:filepath];
LOGD(@"file path is: %@\n", writablePath);
return writablePath;
//file name is recording_contact-name_dayName-day-monthName-year-hour-minutes-seconds
//The recording prefix is used to identify recordings in the cache directory.
//We will use name_dayName-day-monthName-year to separate recordings by days, then hour-minutes-seconds to order them in each day.
}
+ (NSArray *)parseRecordingName:(NSString *)filename {
NSString *rec = @"recording_"; //key that helps find recordings
NSString *subName = [filename substringFromIndex:[filename rangeOfString:rec].location]; //We remove the parent folders if they exist in the filename

View file

@ -0,0 +1,11 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import <UIKit/UIkit.h>
#import "FastAddressBook.h"
#import "Log.h"
#import "AudioHelper.h"

View file

@ -17,6 +17,7 @@ display_phone_only=0
auto_download_incoming_files_max_size=0
lime_migration_done=0
use_rls_presence=1
use_callkit=1
[in_app_purchase]
#set to 1 if in-app purchases are to be shown in the application

View file

@ -23,6 +23,7 @@ send_logs_include_linphonerc_and_chathistory=0
publish_presence=0
backgroundmode_preference=1
use_rls_presence=1
use_callkit=1
accept_early_media=0