diff --git a/Classes/CallManager.swift b/Classes/CallManager.swift new file mode 100644 index 000000000..9e7622643 --- /dev/null +++ b/Classes/CallManager.swift @@ -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 . +*/ + +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 + ]) + } +} + + diff --git a/Classes/CallOutgoingView.m b/Classes/CallOutgoingView.m index c9d674674..97bfeb68f 100644 --- a/Classes/CallOutgoingView.m +++ b/Classes/CallOutgoingView.m @@ -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) { diff --git a/Classes/CallView.m b/Classes/CallView.m index 07f0aaaee..552f19deb 100644 --- a/Classes/CallView.m +++ b/Classes/CallView.m @@ -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 diff --git a/Classes/LinphoneAppDelegate.h b/Classes/LinphoneAppDelegate.h index c34ad8775..9728720cd 100644 --- a/Classes/LinphoneAppDelegate.h +++ b/Classes/LinphoneAppDelegate.h @@ -21,10 +21,11 @@ #import #import "LinphoneCoreSettingsStore.h" -#import "ProviderDelegate.h" #import #import #import +#import "linphoneapp-Swift.h" + @interface LinphoneAppDelegate : NSObject { @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; diff --git a/Classes/LinphoneAppDelegate.m b/Classes/LinphoneAppDelegate.m index 4d499f9d3..61163d01a 100644 --- a/Classes/LinphoneAppDelegate.m +++ b/Classes/LinphoneAppDelegate.m @@ -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> * _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:@"", [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 diff --git a/Classes/LinphoneManager.h b/Classes/LinphoneManager.h index af065a3bb..ec77c3153 100644 --- a/Classes/LinphoneManager.h +++ b/Classes/LinphoneManager.h @@ -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; diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index 21c011a1f..6762d418e 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -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)) { diff --git a/Classes/LinphoneUI/UICallButton.m b/Classes/LinphoneUI/UICallButton.m index 6d1bab3d6..0e48c16b6 100644 --- a/Classes/LinphoneUI/UICallButton.m +++ b/Classes/LinphoneUI/UICallButton.m @@ -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) { diff --git a/Classes/LinphoneUI/UIDeviceCell.m b/Classes/LinphoneUI/UIDeviceCell.m index e9b2aac38..4161fbaad 100644 --- a/Classes/LinphoneUI/UIDeviceCell.m +++ b/Classes/LinphoneUI/UIDeviceCell.m @@ -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 diff --git a/Classes/LinphoneUI/UIDevicesDetails.m b/Classes/LinphoneUI/UIDevicesDetails.m index 8f528900d..7478204b4 100644 --- a/Classes/LinphoneUI/UIDevicesDetails.m +++ b/Classes/LinphoneUI/UIDevicesDetails.m @@ -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 diff --git a/Classes/LinphoneUI/UIPauseButton.m b/Classes/LinphoneUI/UIPauseButton.m index 480d10d64..4b5da117e 100644 --- a/Classes/LinphoneUI/UIPauseButton.m +++ b/Classes/LinphoneUI/UIPauseButton.m @@ -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]; diff --git a/Classes/LinphoneUI/UISpeakerButton.m b/Classes/LinphoneUI/UISpeakerButton.m index 7b39211b1..3bdd5b70d 100644 --- a/Classes/LinphoneUI/UISpeakerButton.m +++ b/Classes/LinphoneUI/UISpeakerButton.m @@ -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 diff --git a/Classes/LinphoneUI/UIVideoButton.m b/Classes/LinphoneUI/UIVideoButton.m index c0431b6bd..69aa119fb 100644 --- a/Classes/LinphoneUI/UIVideoButton.m +++ b/Classes/LinphoneUI/UIVideoButton.m @@ -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]; diff --git a/Classes/Log.h b/Classes/Log.h index c2f264af2..81b6c2007 100644 --- a/Classes/Log.h +++ b/Classes/Log.h @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#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 \ No newline at end of file diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index 3c421795d..0f4a3687d 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -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 { diff --git a/Classes/ProviderDelegate.h b/Classes/ProviderDelegate.h deleted file mode 100644 index b56968111..000000000 --- a/Classes/ProviderDelegate.h +++ /dev/null @@ -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 . - */ - -#import - -#ifndef ProviderDelegate_h -#define ProviderDelegate_h - -@interface ProviderDelegate : NSObject - -@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 */ diff --git a/Classes/ProviderDelegate.m b/Classes/ProviderDelegate.m deleted file mode 100644 index bd417f6e4..000000000 --- a/Classes/ProviderDelegate.m +++ /dev/null @@ -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 . - */ - -#import "ProviderDelegate.h" -#import "LinphoneManager.h" -#import "PhoneMainView.h" -#include "linphone/linphonecore.h" -#import -#import - -@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 diff --git a/Classes/ProviderDelegate.swift b/Classes/ProviderDelegate.swift new file mode 100644 index 000000000..2945321fd --- /dev/null +++ b/Classes/ProviderDelegate.swift @@ -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 . +*/ + +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) + } +} + diff --git a/Classes/Utils/Log.m b/Classes/Utils/Log.m index fc4b7ff43..43c69275b 100644 --- a/Classes/Utils/Log.m +++ b/Classes/Utils/Log.m @@ -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) { diff --git a/Classes/Utils/Utils.h b/Classes/Utils/Utils.h index ee47e89fd..c2c7f94c5 100644 --- a/Classes/Utils/Utils.h +++ b/Classes/Utils/Utils.h @@ -52,7 +52,6 @@ typedef enum { + (NSMutableDictionary *)photoAssetsDictionary; -+ (NSString *)recordingFilePathFromCall:(const LinphoneAddress *)iaddr; + (NSArray *)parseRecordingName:(NSString *)filename; @end diff --git a/Classes/Utils/Utils.m b/Classes/Utils/Utils.m index 116bb13d1..a15a3f8ac 100644 --- a/Classes/Utils/Utils.m +++ b/Classes/Utils/Utils.m @@ -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 diff --git a/Classes/linphone-Bridging-Header.h b/Classes/linphone-Bridging-Header.h new file mode 100644 index 000000000..a520acc8e --- /dev/null +++ b/Classes/linphone-Bridging-Header.h @@ -0,0 +1,11 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + + + +#import +#import "FastAddressBook.h" +#import "Log.h" +#import "AudioHelper.h" + diff --git a/Resources/linphonerc b/Resources/linphonerc index e65093c7f..ffc700895 100644 --- a/Resources/linphonerc +++ b/Resources/linphonerc @@ -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 diff --git a/Resources/linphonerc-factory b/Resources/linphonerc-factory index 38eedc0c5..0299da623 100644 --- a/Resources/linphonerc-factory +++ b/Resources/linphonerc-factory @@ -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