/* LinphoneManager.h * * Copyright (C) 2011 Belledonne Comunications, Grenoble, France * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #import #import #import #import #import #import #import #import #import #import "LinphoneManager.h" #import #include "linphone/linphonecore_utils.h" #include "linphone/lpconfig.h" #include "mediastreamer2/mscommon.h" //#import "Log.h" //#import "Utils.h" #import static LinphoneCore *theLinphoneCore = nil; static LinphoneManager *theLinphoneManager = nil; NSString *const LINPHONERC_APPLICATION_KEY = @"app"; NSString *const kLinphoneCoreUpdate = @"LinphoneCoreUpdate"; NSString *const kLinphoneDisplayStatusUpdate = @"LinphoneDisplayStatusUpdate"; NSString *const kLinphoneMessageReceived = @"LinphoneMessageReceived"; NSString *const kLinphoneTextComposeEvent = @"LinphoneTextComposeStarted"; NSString *const kLinphoneCallUpdate = @"LinphoneCallUpdate"; NSString *const kLinphoneRegistrationUpdate = @"LinphoneRegistrationUpdate"; NSString *const kLinphoneAddressBookUpdate = @"LinphoneAddressBookUpdate"; NSString *const kLinphoneMainViewChange = @"LinphoneMainViewChange"; NSString *const kLinphoneLogsUpdate = @"LinphoneLogsUpdate"; NSString *const kLinphoneSettingsUpdate = @"LinphoneSettingsUpdate"; NSString *const kLinphoneBluetoothAvailabilityUpdate = @"LinphoneBluetoothAvailabilityUpdate"; NSString *const kLinphoneConfiguringStateUpdate = @"LinphoneConfiguringStateUpdate"; NSString *const kLinphoneGlobalStateUpdate = @"LinphoneGlobalStateUpdate"; NSString *const kLinphoneNotifyReceived = @"LinphoneNotifyReceived"; NSString *const kLinphoneNotifyPresenceReceivedForUriOrTel = @"LinphoneNotifyPresenceReceivedForUriOrTel"; NSString *const kLinphoneCallEncryptionChanged = @"LinphoneCallEncryptionChanged"; NSString *const kLinphoneFileTransferSendUpdate = @"LinphoneFileTransferSendUpdate"; NSString *const kLinphoneFileTransferRecvUpdate = @"LinphoneFileTransferRecvUpdate"; const int kLinphoneAudioVbrCodecDefaultBitrate = 36; /*you can override this from linphonerc or linphonerc-factory*/ extern void libmsamr_init(MSFactory *factory); extern void libmsx264_init(MSFactory *factory); extern void libmsopenh264_init(MSFactory *factory); extern void libmssilk_init(MSFactory *factory); extern void libmsbcg729_init(MSFactory *factory); extern void libmswebrtc_init(MSFactory *factory); #define FRONT_CAM_NAME \ "AV Capture: com.apple.avfoundation.avcapturedevice.built-in_video:1" /*"AV Capture: Front Camera"*/ #define BACK_CAM_NAME \ "AV Capture: com.apple.avfoundation.avcapturedevice.built-in_video:0" /*"AV Capture: Back Camera"*/ 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 ProviderDelegate *providerDelegate; @end @implementation LinphoneManager @synthesize connectivity; struct codec_name_pref_table { const char *name; int rate; const char *prefname; }; struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_preference"}, {"speex", 16000, "speex_16k_preference"}, {"silk", 24000, "silk_24k_preference"}, {"silk", 16000, "silk_16k_preference"}, {"amr", 8000, "amr_preference"}, {"gsm", 8000, "gsm_preference"}, {"ilbc", 8000, "ilbc_preference"}, {"isac", 16000, "isac_preference"}, {"pcmu", 8000, "pcmu_preference"}, {"pcma", 8000, "pcma_preference"}, {"g722", 8000, "g722_preference"}, {"g729", 8000, "g729_preference"}, {"mp4v-es", 90000, "mp4v-es_preference"}, {"h264", 90000, "h264_preference"}, {"vp8", 90000, "vp8_preference"}, {"mpeg4-generic", 16000, "aaceld_16k_preference"}, {"mpeg4-generic", 22050, "aaceld_22k_preference"}, {"mpeg4-generic", 32000, "aaceld_32k_preference"}, {"mpeg4-generic", 44100, "aaceld_44k_preference"}, {"mpeg4-generic", 48000, "aaceld_48k_preference"}, {"opus", 48000, "opus_preference"}, {"BV16", 8000, "bv16_preference"}, {NULL, 0, Nil} }; + (NSString *)getPreferenceForCodec:(const char *)name withRate:(int)rate { int i; for (i = 0; codec_pref_table[i].name != NULL; ++i) { if (strcasecmp(codec_pref_table[i].name, name) == 0 && codec_pref_table[i].rate == rate) return [NSString stringWithUTF8String:codec_pref_table[i].prefname]; } return Nil; } + (NSSet *)unsupportedCodecs { NSMutableSet *set = [NSMutableSet set]; for (int i = 0; codec_pref_table[i].name != NULL; ++i) { LinphonePayloadType *available = linphone_core_get_payload_type( theLinphoneCore, codec_pref_table[i].name, codec_pref_table[i].rate, LINPHONE_FIND_PAYLOAD_IGNORE_CHANNELS); if ((available == NULL) // these two codecs should not be hidden, even if not supported && strcmp(codec_pref_table[i].prefname, "h264_preference") != 0 && strcmp(codec_pref_table[i].prefname, "mp4v-es_preference") != 0) { [set addObject:[NSString stringWithUTF8String:codec_pref_table[i].prefname]]; } } return set; } + (BOOL)isCodecSupported:(const char *)codecName { return (codecName != NULL) && (NULL != linphone_core_get_payload_type(theLinphoneCore, codecName, LINPHONE_FIND_PAYLOAD_IGNORE_RATE, LINPHONE_FIND_PAYLOAD_IGNORE_CHANNELS)); } + (BOOL)runningOnIpad { return ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad); } + (BOOL)isRunningTests { NSDictionary *environment = [[NSProcessInfo processInfo] environment]; NSString *injectBundle = environment[@"XCInjectBundle"]; return [[injectBundle pathExtension] isEqualToString:@"xctest"]; } + (LinphoneManager *)instance { @synchronized(self) { if (theLinphoneManager == nil) { theLinphoneManager = [[LinphoneManager alloc] init]; } } return theLinphoneManager; } #pragma mark - Lifecycle Functions - (id)init { if ((self = [super init])) { [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil]; _sounds.vibrate = kSystemSoundID_Vibrate; _logs = [[NSMutableArray alloc] init]; _pushDict = [[NSMutableDictionary alloc] init]; _database = NULL; _speakerEnabled = FALSE; _bluetoothEnabled = FALSE; _conf = FALSE; _fileTransferDelegates = [[NSMutableArray alloc] init]; pushCallIDs = [[NSMutableArray alloc] init]; _photoLibrary = [[ALAssetsLibrary alloc] init]; _isTesting = [LinphoneManager isRunningTests]; [self renameDefaultSettings]; [self copyDefaultSettings]; [self overrideDefaultSettings]; // set default values for first boot if ([self lpConfigStringForKey:@"debugenable_preference"] == nil) { #ifdef DEBUG [self lpConfigSetInt:1 forKey:@"debugenable_preference"]; #else [self lpConfigSetInt:0 forKey:@"debugenable_preference"]; #endif } // by default if handle_content_encoding is not set, we use plain text for debug purposes only if ([self lpConfigStringForKey:@"handle_content_encoding" inSection:@"misc"] == nil) { #ifdef DEBUG [self lpConfigSetString:@"none" forKey:@"handle_content_encoding" inSection:@"misc"]; #else [self lpConfigSetString:@"conflate" forKey:@"handle_content_encoding" inSection:@"misc"]; #endif } [self migrateFromUserPrefs]; } return self; } - (void)dealloc { [NSNotificationCenter.defaultCenter removeObserver:self]; } #pragma deploymate push "ignored-api-availability" //静默推送 - (void)silentPushFailed:(NSTimer *)timer { if (_silentPushCompletion) { NSLog(@"silentPush failed, silentPushCompletion block: %p", _silentPushCompletion); _silentPushCompletion(UIBackgroundFetchResultNoData); _silentPushCompletion = nil; } } #pragma deploymate pop #pragma mark - Migration - (void)migrationAllPost { [self migrationLinphoneSettings]; [self migratePushNotificationPerAccount]; } - (void)migrationAllPre { // migrate xmlrpc URL if needed if ([self lpConfigBoolForKey:@"migration_xmlrpc"] == NO) { [self lpConfigSetString:@"https://subscribe.linphone.org:444/wizard.php" forKey:@"xmlrpc_url" inSection:@"assistant"]; [self lpConfigSetString:@"sip:rls@sip.linphone.org" forKey:@"rls_uri" inSection:@"sip"]; [self lpConfigSetBool:YES forKey:@"migration_xmlrpc"]; } [self lpConfigSetBool:NO forKey:@"store_friends" inSection:@"misc"]; //so far, storing friends in files is not needed. may change in the future. } #pragma mark 检查是否转移图片 static int check_should_migrate_images(void *data, int argc, char **argv, char **cnames) { *((BOOL *)data) = TRUE; return 0; } #pragma mark 数据库移植 - (BOOL)migrateChatDBIfNeeded:(LinphoneCore *)lc { sqlite3 *newDb; char *errMsg; NSError *error; NSString *oldDbPath = [LinphoneManager documentFile:kLinphoneOldChatDBFilename]; NSString *newDbPath = [LinphoneManager documentFile:kLinphoneInternalChatDBFilename]; BOOL shouldMigrate = [[NSFileManager defaultManager] fileExistsAtPath:oldDbPath]; BOOL shouldMigrateImages = FALSE; const char *identity = NULL; BOOL migrated = FALSE; char *attach_stmt = NULL; LinphoneProxyConfig *default_proxy = linphone_core_get_default_proxy_config(lc); if (sqlite3_open([newDbPath UTF8String], &newDb) != SQLITE_OK) { NSLog(@"Can't open \"%@\" sqlite3 database.", newDbPath); return FALSE; } const char *check_appdata = "SELECT url,message FROM history WHERE url LIKE 'assets-library%' OR message LIKE 'assets-library%' LIMIT 1;"; // will set "needToMigrateImages to TRUE if a result comes by sqlite3_exec(newDb, check_appdata, check_should_migrate_images, &shouldMigrateImages, NULL); if (!shouldMigrate && !shouldMigrateImages) { sqlite3_close(newDb); return FALSE; } NSLog(@"Starting migration procedure"); if (shouldMigrate) { // attach old database to the new one: attach_stmt = sqlite3_mprintf("ATTACH DATABASE %Q AS oldchats", [oldDbPath UTF8String]); if (sqlite3_exec(newDb, attach_stmt, NULL, NULL, &errMsg) != SQLITE_OK) { NSLog(@"Can't attach old chat table, error[%s] ", errMsg); sqlite3_free(errMsg); goto exit_dbmigration; } // migrate old chats to the new db. The iOS stores timestamp in UTC already, so we can directly put it in the // 'utc' field and set 'time' to -1 const char *migration_statement = "INSERT INTO history (localContact,remoteContact,direction,message,utc,read,status,time) " "SELECT localContact,remoteContact,direction,message,time,read,state,'-1' FROM oldchats.chat"; if (sqlite3_exec(newDb, migration_statement, NULL, NULL, &errMsg) != SQLITE_OK) { NSLog(@"DB migration failed, error[%s] ", errMsg); sqlite3_free(errMsg); goto exit_dbmigration; } // invert direction of old messages, because iOS was storing the direction flag incorrectly const char *invert_direction = "UPDATE history SET direction = NOT direction"; if (sqlite3_exec(newDb, invert_direction, NULL, NULL, &errMsg) != SQLITE_OK) { NSLog(@"Inverting direction failed, error[%s]", errMsg); sqlite3_free(errMsg); goto exit_dbmigration; } // replace empty from: or to: by the current identity. if (default_proxy) { identity = linphone_proxy_config_get_identity_address(default_proxy); } if (!identity) { identity = "sip:unknown@sip.linphone.org"; } char *from_conversion = sqlite3_mprintf("UPDATE history SET localContact = %Q WHERE localContact = ''", identity); if (sqlite3_exec(newDb, from_conversion, NULL, NULL, &errMsg) != SQLITE_OK) { NSLog(@"FROM conversion failed, error[%s] ", errMsg); sqlite3_free(errMsg); } sqlite3_free(from_conversion); char *to_conversion = sqlite3_mprintf("UPDATE history SET remoteContact = %Q WHERE remoteContact = ''", identity); if (sqlite3_exec(newDb, to_conversion, NULL, NULL, &errMsg) != SQLITE_OK) { NSLog(@"DB migration failed, error[%s] ", errMsg); sqlite3_free(errMsg); } sqlite3_free(to_conversion); } // local image paths were stored in the 'message' field historically. They were // very temporarily stored in the 'url' field, and now we migrated them to a JSON- // encoded field. These are the migration steps to migrate them. // move already stored images from the messages to the appdata JSON field const char *assetslib_migration = "UPDATE history SET appdata='{\"localimage\":\"'||message||'\"}' , message='' " "WHERE message LIKE 'assets-library%'"; if (sqlite3_exec(newDb, assetslib_migration, NULL, NULL, &errMsg) != SQLITE_OK) { NSLog(@"Assets-history migration for MESSAGE failed, error[%s] ", errMsg); sqlite3_free(errMsg); } // move already stored images from the url to the appdata JSON field const char *assetslib_migration_fromurl = "UPDATE history SET appdata='{\"localimage\":\"'||url||'\"}' , url='' WHERE url LIKE 'assets-library%'"; if (sqlite3_exec(newDb, assetslib_migration_fromurl, NULL, NULL, &errMsg) != SQLITE_OK) { NSLog(@"Assets-history migration for URL failed, error[%s] ", errMsg); sqlite3_free(errMsg); } // We will lose received messages with remote url, they will be displayed in plain. We can't do much for them.. migrated = TRUE; exit_dbmigration: if (attach_stmt) sqlite3_free(attach_stmt); sqlite3_close(newDb); // in any case, we should remove the old chat db if (shouldMigrate && ![[NSFileManager defaultManager] removeItemAtPath:oldDbPath error:&error]) { NSLog(@"Could not remove old chat DB: %@", error); } NSLog(@"Message storage migration finished: success = %@", migrated ? @"TRUE" : @"FALSE"); return migrated; } #pragma mark 转移linphone UserpRefs - (void)migrateFromUserPrefs { static NSString *migration_flag = @"userpref_migration_done"; if (_configDb == nil) return; if ([self lpConfigIntForKey:migration_flag withDefault:0]) { return; } NSDictionary *defaults = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; NSArray *defaults_keys = [defaults allKeys]; NSDictionary *values = @{ @"backgroundmode_preference" : @YES, @"debugenable_preference" : @NO, @"start_at_boot_preference" : @YES }; BOOL shouldSync = FALSE; NSLog(@"%lu user prefs", (unsigned long)[defaults_keys count]); for (NSString *userpref in values) { if ([defaults_keys containsObject:userpref]) { NSLog(@"Migrating %@ from user preferences: %d", userpref, [[defaults objectForKey:userpref] boolValue]); [self lpConfigSetBool:[[defaults objectForKey:userpref] boolValue] forKey:userpref]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:userpref]; shouldSync = TRUE; } else if ([self lpConfigStringForKey:userpref] == nil) { // no default value found in our linphonerc, we need to add them [self lpConfigSetBool:[[values objectForKey:userpref] boolValue] forKey:userpref]; } } if (shouldSync) { NSLog(@"Synchronizing..."); [[NSUserDefaults standardUserDefaults] synchronize]; } // don't get back here in the future [self lpConfigSetBool:YES forKey:migration_flag]; } #pragma mark 转移linphone设置 - (void)migrationLinphoneSettings { // we need to proceed to the migration *after* the chat database was opened, so that we know it is in consistent // state NSString *chatDBFileName = [LinphoneManager documentFile:kLinphoneInternalChatDBFilename]; if ([self migrateChatDBIfNeeded:theLinphoneCore]) { // if a migration was performed, we should reinitialize the chat database linphone_core_set_chat_database_path(theLinphoneCore, [chatDBFileName UTF8String]); } /* AVPF migration */ if ([self lpConfigBoolForKey:@"avpf_migration_done"] == FALSE) { const MSList *proxies = linphone_core_get_proxy_config_list(theLinphoneCore); while (proxies) { LinphoneProxyConfig *proxy = (LinphoneProxyConfig *)proxies->data; const char *addr = linphone_proxy_config_get_addr(proxy); // we want to enable AVPF for the proxies if (addr && strstr(addr, [LinphoneManager.instance lpConfigStringForKey:@"domain_name" inSection:@"app" withDefault:@"sip.linphone.org"] .UTF8String) != 0) { NSLog(@"Migrating proxy config to use AVPF"); linphone_proxy_config_set_avpf_mode(proxy, TRUE); } proxies = proxies->next; } [self lpConfigSetBool:TRUE forKey:@"avpf_migration_done"]; } /* Quality Reporting migration */ if ([self lpConfigBoolForKey:@"quality_report_migration_done"] == FALSE) { const MSList *proxies = linphone_core_get_proxy_config_list(theLinphoneCore); while (proxies) { LinphoneProxyConfig *proxy = (LinphoneProxyConfig *)proxies->data; const char *addr = linphone_proxy_config_get_addr(proxy); // we want to enable quality reporting for the proxies that are on linphone.org if (addr && strstr(addr, [LinphoneManager.instance lpConfigStringForKey:@"domain_name" inSection:@"app" withDefault:@"sip.linphone.org"] .UTF8String) != 0) { NSLog(@"Migrating proxy config to send quality report"); linphone_proxy_config_set_quality_reporting_collector( proxy, "sip:voip-metrics@sip.linphone.org;transport=tls"); linphone_proxy_config_set_quality_reporting_interval(proxy, 180); linphone_proxy_config_enable_quality_reporting(proxy, TRUE); } proxies = proxies->next; } [self lpConfigSetBool:TRUE forKey:@"quality_report_migration_done"]; } /* File transfer migration */ if ([self lpConfigBoolForKey:@"file_transfer_migration_done"] == FALSE) { const char *newURL = "https://www.linphone.org:444/lft.php"; NSLog(@"Migrating sharing server url from %s to %s", linphone_core_get_file_transfer_server(LC), newURL); linphone_core_set_file_transfer_server(LC, newURL); [self lpConfigSetBool:TRUE forKey:@"file_transfer_migration_done"]; } } #pragma mark 转移推送通知当前用户 - (void)migratePushNotificationPerAccount { NSString *s = [self lpConfigStringForKey:@"pushnotification_preference"]; if (s && s.boolValue) { NSLog(@"Migrating push notification per account, enabling for ALL"); [self lpConfigSetBool:NO forKey:@"pushnotification_preference"]; const MSList *proxies = linphone_core_get_proxy_config_list(LC); while (proxies) { linphone_proxy_config_set_ref_key(proxies->data, "push_notification"); [self configurePushTokenForProxyConfig:proxies->data]; proxies = proxies->next; } } } #pragma mark 转移向导到助手 static void migrateWizardToAssistant(const char *entry, void *user_data) { LinphoneManager *thiz = (__bridge LinphoneManager *)(user_data); NSString *key = [NSString stringWithUTF8String:entry]; [thiz lpConfigSetString:[thiz lpConfigStringForKey:key inSection:@"wizard"] forKey:key inSection:@"assistant"]; } static OSStatus extracted(CFStringRef *newRoute, UInt32 *newRouteSize) { return AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, newRouteSize, newRoute); } - (OSStatus)extracted:(CFStringRef *)newRoute newRouteSize:(UInt32 *)newRouteSize { return extracted(newRoute, newRouteSize); } - (void)extracted:(NSDictionary *)dict { [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneBluetoothAvailabilityUpdate object:self userInfo:dict]; } - (void)audioRouteChangeListenerCallback:(NSNotification *)notif { if (IPAD) return; // there is at least one bug when you disconnect an audio bluetooth headset // since we only get notification of route having changed, we cannot tell if that is due to: // -bluetooth headset disconnected or // -user wanted to use earpiece // the only thing we can assume is that when we lost a device, it must be a bluetooth one (strong hypothesis though) if ([[notif.userInfo valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue] == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { _bluetoothAvailable = NO; } CFStringRef newRoute = CFSTR("Unknown"); UInt32 newRouteSize = sizeof(newRoute); #warning deprecated OSStatus status = [self extracted:&newRoute newRouteSize:&newRouteSize]; if (!status && newRouteSize > 0) { NSString *route = (__bridge NSString *)newRoute; NSLog(@"Current audio route is [%s]", [route UTF8String]); _speakerEnabled = [route isEqualToString:@"Speaker"] || [route isEqualToString:@"SpeakerAndMicrophone"]; if ([route isEqualToString:@"HeadsetBT"] && !_speakerEnabled) { _bluetoothAvailable = TRUE; _bluetoothEnabled = TRUE; } else { _bluetoothEnabled = FALSE; } NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:_bluetoothAvailable], @"available", nil]; [self extracted:dict]; CFRelease(newRoute); } } #pragma mark - Audio route Functions - (bool)allowSpeaker { if (IPAD) return true; bool allow = true; CFStringRef lNewRoute = CFSTR("Unknown"); UInt32 lNewRouteSize = sizeof(lNewRoute); #warning deprecated OSStatus lStatus = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &lNewRouteSize, &lNewRoute); if (!lStatus && lNewRouteSize > 0) { NSString *route = (__bridge NSString *)lNewRoute; allow = ![route containsString:@"Heads"] && ![route isEqualToString:@"Lineout"]; CFRelease(lNewRoute); } return allow; } - (void)setSpeakerEnabled:(BOOL)enable { OSStatus ret; _speakerEnabled = enable; UInt32 override = kAudioSessionUnspecifiedError; if (!enable && _bluetoothAvailable) { UInt32 bluetoothInputOverride = _bluetoothEnabled; #warning deprecated ret = AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryEnableBluetoothInput, sizeof(bluetoothInputOverride), &bluetoothInputOverride); // if setting bluetooth failed, it must be because the device is not available // anymore (disconnected), so deactivate bluetooth. if (ret != kAudioSessionNoError) { _bluetoothAvailable = _bluetoothEnabled = FALSE; } } if (override != kAudioSessionNoError) { if (enable && [self allowSpeaker]) { override = kAudioSessionOverrideAudioRoute_Speaker; #warning deprecated ret = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(override), &override); _bluetoothEnabled = FALSE; } else { override = kAudioSessionOverrideAudioRoute_None; #warning deprecated ret = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof(override), &override); } } // if (ret != kAudioSessionNoError) { // NSLog(@"Failed to change audio route: err %d", ret); // } } - (void)setBluetoothEnabled:(BOOL)enable { if (_bluetoothAvailable) { // The change of route will be done in setSpeakerEnabled _bluetoothEnabled = enable; [self setSpeakerEnabled:!_bluetoothEnabled && _speakerEnabled]; } } - (void)overrideDefaultSettings { NSString *factory = [LinphoneManager bundleFile:@"linphonerc-factory"]; NSString *factoryIpad = [LinphoneManager bundleFile:@"linphonerc-factory~ipad"]; if (IPAD && [[NSFileManager defaultManager] fileExistsAtPath:factoryIpad]) { factory = factoryIpad; } NSString *confiFileName = [LinphoneManager documentFile:@"linphonerc"]; _configDb = lp_config_new_with_factory([confiFileName UTF8String], [factory UTF8String]); } - (void)copyDefaultSettings { NSString *src = [LinphoneManager bundleFile:@"linphonerc"]; NSString *srcIpad = [LinphoneManager bundleFile:@"linphonerc~ipad"]; if (IPAD && [[NSFileManager defaultManager] fileExistsAtPath:srcIpad]) { src = srcIpad; } NSString *dst = [LinphoneManager documentFile:@"linphonerc"]; [LinphoneManager copyFile:src destination:dst override:FALSE]; } #pragma mark - Linphone Core Functions + (LinphoneCore *)getLc { if (theLinphoneCore == nil) { @throw([NSException exceptionWithName:@"LinphoneCoreException" reason:@"Linphone core not initialized yet" userInfo:nil]); } return theLinphoneCore; } #pragma mark Debug functions + (void)dumpLcConfig { if (theLinphoneCore) { LpConfig *conf = LinphoneManager.instance.configDb; char *config = lp_config_dump(conf); NSLog(@"\n%s", config); ms_free(config); } } #pragma mark - Logs Functions handlers static void linphone_iphone_log_user_info(struct _LinphoneCore *lc, const char *message) { // linphone_core_set_log_handler(NULL, ORTP_MESSAGE, message, NULL); } static void linphone_iphone_log_user_warning(struct _LinphoneCore *lc, const char *message) { // linphone_iphone_log_handler(NULL, ORTP_WARNING, message, NULL); } #pragma mark - Display Status Functions - (void)displayStatus:(NSString *)message { // Post event [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneDisplayStatusUpdate object:self userInfo:@{ @"message" : message }]; } static void linphone_iphone_display_status(struct _LinphoneCore *lc, const char *message) { NSString *status = [[NSString alloc] initWithCString:message encoding:[NSString defaultCStringEncoding]]; [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) displayStatus:status]; } #pragma mark - Call State Functions - (void)localNotifContinue:(NSTimer *)timer { UILocalNotification *notif = [timer userInfo]; if (notif) { NSLog(@"cancelling/presenting local notif"); [[UIApplication sharedApplication] cancelAllLocalNotifications]; [[UIApplication sharedApplication] presentLocalNotificationNow:notif]; } } - (void)userNotifContinue:(NSTimer *)timer { if (@available(iOS 10.0, *)) { UNNotificationContent *content = [timer userInfo]; if (content && [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { NSLog(@"cancelling/presenting user notif"); UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:@"call_request" content:content trigger:NULL]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:req withCompletionHandler:^(NSError *_Nullable error) { // Enable or disable features based on authorization. if (error) { NSLog(@"Error while adding notification request :"); NSLog(@"%@", error.description); } }]; } } else { // Fallback on earlier versions } } - (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 NSLog(@"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) { // TESTING !! // linphone_call_accept_early_media(call); 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; [_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 (pushBgTask && !need_bg_task) { NSLog(@"Call received, stopping background task"); [[UIApplication sharedApplication] endBackgroundTask:pushBgTask]; pushBgTask = 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) { NSLog(@"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 && call && (linphone_core_get_calls_nb(LC) < 2)) { #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 = FALSE; // video = (([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) && // linphone_core_get_video_policy(LC)->automatically_accept && // linphone_call_params_video_enabled(linphone_call_get_remote_params(call))); // [LinphoneManager.instance.providerDelegate reportIncomingCallwithUUID:uuid handle:address video:video]; #else // [PhoneMainView.instance displayIncomingCall:call]; #endif } else if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { // Create a UNNotification if (@available(iOS 10.0, *)) { 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 { // Fallback on earlier versions } } if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { // 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]; NSLog(@"%@", [app currentUserNotificationSettings].description); [app presentLocalNotificationNow:data->notification]; if (!incallBgTask) { incallBgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ NSLog(@"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)) { 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; /*IOS specific*/ linphone_core_start_dtmf_stream(theLinphoneCore); } if (incallBgTask) { [[UIApplication sharedApplication] endBackgroundTask:incallBgTask]; incallBgTask = 0; } if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) { if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { 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) { if (@available(iOS 10.0, *)) { 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) { // Enable or disable features based on authorization. if (error) { NSLog(@"Error while adding notification request :"); NSLog(@"%@", error.description); } }]; } else { // Fallback on earlier versions } } linphone_core_set_network_reachable(LC, FALSE); LinphoneManager.instance.connectivity = none; } // LinphoneCallLog *callLog2 = linphone_call_get_call_log(call); // NSString *callId2 = [NSString stringWithUTF8String:linphone_call_log_get_call_id(callLog2)]; // NSUUID *uuid = (NSUUID *)[self.providerDelegate.uuids objectForKey:callId2]; // if (uuid) { // // For security reasons do not display name // // CXCallUpdate *update = [[CXCallUpdate alloc] init]; // // update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:@"Unknown"]; // //[LinphoneManager.instance.providerDelegate.provider reportCallWithUUID:uuid updated:update]; // // if (linphone_core_get_calls_nb(LC) > 0 && !_conf) { // // Create a CallKit call because there's not ! // _conf = FALSE; // LinphoneCall *callKit_call = (LinphoneCall *)linphone_core_get_calls(LC)->data; // NSString *callKit_callId = [NSString // stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(callKit_call))]; // NSUUID *callKit_uuid = [NSUUID UUID]; // [LinphoneManager.instance.providerDelegate.uuids setObject:callKit_uuid forKey:callKit_callId]; // [LinphoneManager.instance.providerDelegate.calls setObject:callKit_callId 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]; // } // // [self.providerDelegate.uuids removeObjectForKey:callId2]; // [self.providerDelegate.calls removeObjectForKey:uuid]; // CXEndCallAction *act = [[CXEndCallAction alloc] initWithCallUUID:uuid]; // CXTransaction *tr = [[CXTransaction alloc] initWithAction:act]; // [LinphoneManager.instance.providerDelegate.controller requestTransaction:tr // completion:^(NSError *err){ // }]; // } } 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) { [self setSpeakerEnabled:TRUE]; speaker_already_enabled = TRUE; } } if (state == LinphoneCallConnected && !mCallCenter) { /*only register CT call center CB for connected call*/ [self setupGSMInteraction]; } // 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_get_user_data(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) { } #pragma mark - Global state change static void linphone_iphone_global_state_changed(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message) { [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) onGlobalStateChanged:gstate withMessage:message]; } - (void)onGlobalStateChanged:(LinphoneGlobalState)state withMessage:(const char *)message { NSLog(@"onGlobalStateChanged: %d (message: %s)", state, message); NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:state], @"state", [NSString stringWithUTF8String:message ? message : ""], @"message", nil]; // dispatch the notification asynchronously dispatch_async(dispatch_get_main_queue(), ^(void) { [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneGlobalStateUpdate object:self userInfo:dict]; }); } - (void)globalStateChangedNotificationHandler:(NSNotification *)notif { if ((LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneGlobalOn) { [self finishCoreConfiguration]; } } #pragma mark - Configuring status changed static void linphone_iphone_configuring_status_changed(LinphoneCore *lc, LinphoneConfiguringState status, const char *message) { [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) onConfiguringStatusChanged:status withMessage:message]; } - (void)onConfiguringStatusChanged:(LinphoneConfiguringState)status withMessage:(const char *)message { NSLog(@"onConfiguringStatusChanged: %s %@", linphone_configuring_state_to_string(status), message ? [NSString stringWithFormat:@"(message: %s)", message] : @""); NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:status], @"state", [NSString stringWithUTF8String:message ? message : ""], @"message", nil]; // dispatch the notification asynchronously dispatch_async(dispatch_get_main_queue(), ^(void) { [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneConfiguringStateUpdate object:self userInfo:dict]; }); } - (void)configuringStateChangedNotificationHandler:(NSNotification *)notif { _wasRemoteProvisioned = ((LinphoneConfiguringState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneConfiguringSuccessful); if (_wasRemoteProvisioned) { LinphoneProxyConfig *cfg = linphone_core_get_default_proxy_config(LC); if (cfg) { [self configurePushTokenForProxyConfig:cfg]; } } } #pragma mark - Registration State Functions - (void)onRegister:(LinphoneCore *)lc cfg:(LinphoneProxyConfig *)cfg state:(LinphoneRegistrationState)state message:(const char *)cmessage { NSLog(@"New registration state: %s (message: %s)", linphone_registration_state_to_string(state), cmessage); LinphoneReason reason = linphone_proxy_config_get_error(cfg); NSString *message = nil; switch (reason) { case LinphoneReasonBadCredentials: message = NSLocalizedString(@"Bad credentials, check your account settings", nil); break; case LinphoneReasonNoResponse: message = NSLocalizedString(@"No response received from remote", nil); break; case LinphoneReasonUnsupportedContent: message = NSLocalizedString(@"Unsupported content", nil); break; case LinphoneReasonIOError: message = NSLocalizedString( @"Cannot reach the server: either it is an invalid address or it may be temporary down.", nil); break; case LinphoneReasonUnauthorized: message = NSLocalizedString(@"Operation is unauthorized because missing credential", nil); break; case LinphoneReasonNoMatch: message = NSLocalizedString(@"Operation could not be executed by server or remote client because it " @"didn't have any context for it", nil); break; case LinphoneReasonMovedPermanently: message = NSLocalizedString(@"Resource moved permanently", nil); break; case LinphoneReasonGone: message = NSLocalizedString(@"Resource no longer exists", nil); break; case LinphoneReasonTemporarilyUnavailable: message = NSLocalizedString(@"Temporarily unavailable", nil); break; case LinphoneReasonAddressIncomplete: message = NSLocalizedString(@"Address incomplete", nil); break; case LinphoneReasonNotImplemented: message = NSLocalizedString(@"Not implemented", nil); break; case LinphoneReasonBadGateway: message = NSLocalizedString(@"Bad gateway", nil); break; case LinphoneReasonServerTimeout: message = NSLocalizedString(@"Server timeout", nil); break; case LinphoneReasonNotAcceptable: case LinphoneReasonDoNotDisturb: case LinphoneReasonDeclined: case LinphoneReasonNotFound: case LinphoneReasonNotAnswered: case LinphoneReasonBusy: case LinphoneReasonNone: case LinphoneReasonUnknown: message = NSLocalizedString(@"Unknown error", nil); break; } // Post event NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:state], @"state", [NSValue valueWithPointer:cfg], @"cfg", message, @"message", nil]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneRegistrationUpdate object:self userInfo:dict]; } static void linphone_iphone_registration_state(LinphoneCore *lc, LinphoneProxyConfig *cfg, LinphoneRegistrationState state, const char *message) { [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) onRegister:lc cfg:cfg state:state message:message]; } #pragma mark - Auth info Function static void linphone_iphone_popup_password_request(LinphoneCore *lc, const char *realmC, const char *usernameC, const char *domainC) { } #pragma mark - Text Received Functions //IM消息接收 - (void)onMessageReceived:(LinphoneCore *)lc room:(LinphoneChatRoom *)room message:(LinphoneChatMessage *)msg { } static void linphone_iphone_message_received(LinphoneCore *lc, LinphoneChatRoom *room, LinphoneChatMessage *message) { [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) onMessageReceived:lc room:room message:message]; } static void linphone_iphone_message_received_unable_decrypt(LinphoneCore *lc, LinphoneChatRoom *room, LinphoneChatMessage *message) { NSString *msgId = [NSString stringWithUTF8String:linphone_chat_message_get_custom_header(message, "Call-ID")]; int index = [(NSNumber *)[LinphoneManager.instance.pushDict objectForKey:msgId] intValue] - 1; [LinphoneManager.instance.pushDict setValue:[NSNumber numberWithInt:index] forKey:msgId]; BOOL need_bg_task = FALSE; for (NSString *key in [LinphoneManager.instance.pushDict allKeys]) { int value = [(NSNumber *)[LinphoneManager.instance.pushDict objectForKey:key] intValue]; if (value > 0) { need_bg_task = TRUE; break; } } if (theLinphoneManager->pushBgTask && !need_bg_task) { NSLog(@"Message received, stopping background task"); [[UIApplication sharedApplication] endBackgroundTask:theLinphoneManager->pushBgTask]; theLinphoneManager->pushBgTask = 0; } // const LinphoneAddress *address = linphone_chat_message_get_peer_address(message); // NSString *strAddr = [FastAddressBook displayNameForAddress:address]; NSString *title = NSLocalizedString(@"LIME warning", nil); // NSString *body = [NSString // stringWithFormat:NSLocalizedString(@"You have received an encrypted message you are unable to decrypt from " // @"%@.\nYou need to call your correspondant in order to exchange your ZRTP " // @"keys if you want to decrypt the future messages you will receive.", // nil), // strAddr]; // NSString *action = NSLocalizedString(@"Call", nil); if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) { if (@available(iOS 10.0, *)) { UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; content.title = title; // content.body = body; UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:@"decrypt_request" content:content trigger:NULL]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:req withCompletionHandler:^(NSError *_Nullable error) { // Enable or disable features based on authorization. if (error) { NSLog(@"Error while adding notification request :"); NSLog(@"%@", error.description); } }]; } else { // Fallback on earlier versions } } else { UILocalNotification *notification = [[UILocalNotification alloc] init]; notification.repeatInterval = 0; if (@available(iOS 8.2, *)) { notification.alertTitle = title; } else { // Fallback on earlier versions } // notification.alertBody = body; [[UIApplication sharedApplication] presentLocalNotificationNow:notification]; } } else { } } - (LinphoneAddress *)normalizeSipOrPhoneAddress:(NSString *)value { if (!value) { return NULL; } LinphoneProxyConfig *cfg = linphone_core_get_default_proxy_config(LC); const char * normvalue; if (linphone_proxy_config_is_phone_number(cfg, value.UTF8String)) { normvalue = linphone_proxy_config_normalize_phone_number(cfg, value.UTF8String); } else { normvalue = value.UTF8String; } LinphoneAddress *addr = linphone_proxy_config_normalize_sip_uri(cfg, normvalue); // first try to find a friend with the given address // since user wants to escape plus, we assume it expects to have phone numbers by default if (addr && cfg && (linphone_proxy_config_get_dial_escape_plus(cfg))) { linphone_address_set_username(addr, normvalue); } else { linphone_address_set_username(addr, value.UTF8String); } return addr; } - (void)onNotifyReceived:(LinphoneCore *)lc event:(LinphoneEvent *)lev notifyEvent:(const char *)notified_event content:(const LinphoneContent *)body { // Post event NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:[NSValue valueWithPointer:lev] forKey:@"event"]; [dict setObject:[NSString stringWithUTF8String:notified_event] forKey:@"notified_event"]; if (body != NULL) { [dict setObject:[NSValue valueWithPointer:body] forKey:@"content"]; } [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneNotifyReceived object:self userInfo:dict]; } static void linphone_iphone_notify_received(LinphoneCore *lc, LinphoneEvent *lev, const char *notified_event, const LinphoneContent *body) { [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) onNotifyReceived:lc event:lev notifyEvent:notified_event content:body]; } - (void)onNotifyPresenceReceivedForUriOrTel:(LinphoneCore *)lc friend:(LinphoneFriend *)lf uri:(const char *)uri presenceModel:(const LinphonePresenceModel *)model { // Post event NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:[NSValue valueWithPointer:lf] forKey:@"friend"]; [dict setObject:[NSValue valueWithPointer:uri] forKey:@"uri"]; [dict setObject:[NSValue valueWithPointer:model] forKey:@"presence_model"]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneNotifyPresenceReceivedForUriOrTel object:self userInfo:dict]; } static void linphone_iphone_notify_presence_received_for_uri_or_tel(LinphoneCore *lc, LinphoneFriend *lf, const char *uri_or_tel, const LinphonePresenceModel *presence_model) { [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) onNotifyPresenceReceivedForUriOrTel:lc friend:lf uri:uri_or_tel presenceModel:presence_model]; } static void linphone_iphone_call_encryption_changed(LinphoneCore *lc, LinphoneCall *call, bool_t on, const char *authentication_token) { [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) onCallEncryptionChanged:lc call:call on:on token:authentication_token]; } - (void)onCallEncryptionChanged:(LinphoneCore *)lc call:(LinphoneCall *)call on:(BOOL)on token:(const char *)authentication_token { // Post event NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:[NSValue valueWithPointer:call] forKey:@"call"]; [dict setObject:[NSNumber numberWithBool:on] forKey:@"on"]; if (authentication_token) { [dict setObject:[NSString stringWithUTF8String:authentication_token] forKey:@"token"]; } [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallEncryptionChanged object:self userInfo:dict]; } - (void)onMessageComposeReceived:(LinphoneCore *)core forRoom:(LinphoneChatRoom *)room { [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneTextComposeEvent object:self userInfo:@{ @"room" : [NSValue valueWithPointer:room] }]; } static void linphone_iphone_is_composing_received(LinphoneCore *lc, LinphoneChatRoom *room) { [(__bridge LinphoneManager *)linphone_core_get_user_data(lc) onMessageComposeReceived:lc forRoom:room]; } #pragma mark - Network Functions - (SCNetworkReachabilityRef)getProxyReachability { return proxyReachability; } + (void)kickOffNetworkConnection { static BOOL in_progress = FALSE; if (in_progress) { NSLog(@"Connection kickoff already in progress"); return; } in_progress = TRUE; /* start a new thread to avoid blocking the main ui in case of peer host failure */ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static int sleep_us = 10000; static int timeout_s = 5; BOOL timeout_reached = FALSE; int loop = 0; CFWriteStreamRef writeStream; CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef) @"192.168.0.200" /*"linphone.org"*/, 15000, nil, &writeStream); BOOL res = CFWriteStreamOpen(writeStream); const char *buff = "hello"; time_t start = time(NULL); time_t loop_time; if (res == FALSE) { NSLog(@"Could not open write stream, backing off"); CFRelease(writeStream); in_progress = FALSE; return; } // check stream status and handle timeout CFStreamStatus status = CFWriteStreamGetStatus(writeStream); while (status != kCFStreamStatusOpen && status != kCFStreamStatusError) { usleep(sleep_us); status = CFWriteStreamGetStatus(writeStream); loop_time = time(NULL); if (loop_time - start >= timeout_s) { timeout_reached = TRUE; break; } loop++; } if (status == kCFStreamStatusOpen) { CFWriteStreamWrite(writeStream, (const UInt8 *)buff, strlen(buff)); } else if (!timeout_reached) { CFErrorRef error = CFWriteStreamCopyError(writeStream); NSLog(@"CFStreamError: %@", error); CFRelease(error); } else if (timeout_reached) { NSLog(@"CFStream timeout reached"); } CFWriteStreamClose(writeStream); CFRelease(writeStream); in_progress = FALSE; }); } + (NSString *)getCurrentWifiSSID { #if TARGET_IPHONE_SIMULATOR return @"Sim_err_SSID_NotSupported"; #else NSString *data = nil; CFDictionaryRef dict = CNCopyCurrentNetworkInfo((CFStringRef) @"en0"); if (dict) { NSLog(@"AP Wifi: %@", dict); data = [NSString stringWithString:(NSString *)CFDictionaryGetValue(dict, @"SSID")]; CFRelease(dict); } return data; #endif } static void showNetworkFlags(SCNetworkReachabilityFlags flags) { NSMutableString *log = [[NSMutableString alloc] initWithString:@"Network connection flags: "]; if (flags == 0) [log appendString:@"no flags."]; if (flags & kSCNetworkReachabilityFlagsTransientConnection) [log appendString:@"kSCNetworkReachabilityFlagsTransientConnection, "]; if (flags & kSCNetworkReachabilityFlagsReachable) [log appendString:@"kSCNetworkReachabilityFlagsReachable, "]; if (flags & kSCNetworkReachabilityFlagsConnectionRequired) [log appendString:@"kSCNetworkReachabilityFlagsConnectionRequired, "]; if (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) [log appendString:@"kSCNetworkReachabilityFlagsConnectionOnTraffic, "]; if (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) [log appendString:@"kSCNetworkReachabilityFlagsConnectionOnDemand, "]; if (flags & kSCNetworkReachabilityFlagsIsLocalAddress) [log appendString:@"kSCNetworkReachabilityFlagsIsLocalAddress, "]; if (flags & kSCNetworkReachabilityFlagsIsDirect) [log appendString:@"kSCNetworkReachabilityFlagsIsDirect, "]; if (flags & kSCNetworkReachabilityFlagsIsWWAN) [log appendString:@"kSCNetworkReachabilityFlagsIsWWAN, "]; NSLog(@"%@", log); } //This callback keeps tracks of wifi SSID changes. static void networkReachabilityNotification(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { LinphoneManager *mgr = LinphoneManager.instance; SCNetworkReachabilityFlags flags; // for an unknown reason, we are receiving multiple time the notification, so // we will skip each time the SSID did not change NSString *newSSID = [LinphoneManager getCurrentWifiSSID]; if ([newSSID compare:mgr.SSID] == NSOrderedSame) return; if (newSSID != Nil && newSSID.length > 0 && mgr.SSID != Nil && newSSID.length > 0){ if (SCNetworkReachabilityGetFlags([mgr getProxyReachability], &flags)) { NSLog(@"Wifi SSID changed, resesting transports."); mgr.connectivity=none; //this will trigger a connectivity change in networkReachabilityCallback. networkReachabilityCallBack([mgr getProxyReachability], flags, nil); } } mgr.SSID = newSSID; } void networkReachabilityCallBack(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *nilCtx) { showNetworkFlags(flags); LinphoneManager *lm = LinphoneManager.instance; SCNetworkReachabilityFlags networkDownFlags = kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand; if (theLinphoneCore != nil) { LinphoneProxyConfig *proxy = linphone_core_get_default_proxy_config(theLinphoneCore); struct NetworkReachabilityContext *ctx = nilCtx ? ((struct NetworkReachabilityContext *)nilCtx) : 0; if ((flags == 0) || (flags & networkDownFlags)) { linphone_core_set_network_reachable(theLinphoneCore, false); lm.connectivity = none; [LinphoneManager kickOffNetworkConnection]; } else { Connectivity newConnectivity; BOOL isWifiOnly = [lm lpConfigBoolForKey:@"wifi_only_preference" withDefault:FALSE]; if (!ctx || ctx->testWWan) newConnectivity = flags & kSCNetworkReachabilityFlagsIsWWAN ? wwan : wifi; else newConnectivity = wifi; if (newConnectivity == wwan && proxy && isWifiOnly && (lm.connectivity == newConnectivity || lm.connectivity == none)) { linphone_proxy_config_expires(proxy, 0); } else if (proxy) { NSInteger defaultExpire = [lm lpConfigIntForKey:@"default_expires"]; if (defaultExpire >= 0) linphone_proxy_config_expires(proxy, (int)defaultExpire); // else keep default value from linphonecore } if (lm.connectivity != newConnectivity) { // connectivity has changed linphone_core_set_network_reachable(theLinphoneCore, false); if (newConnectivity == wwan && proxy && isWifiOnly) { linphone_proxy_config_expires(proxy, 0); } linphone_core_set_network_reachable(theLinphoneCore, true); linphone_core_iterate(theLinphoneCore); NSLog(@"Network connectivity changed to type [%s]", (newConnectivity == wifi ? "wifi" : "wwan")); lm.connectivity = newConnectivity; } } if (ctx && ctx->networkStateChanged) { (*ctx->networkStateChanged)(lm.connectivity); } } } #pragma mark - 设置网络状态回调 - (void)setupNetworkReachabilityCallback { SCNetworkReachabilityContext *ctx = NULL; // any internet cnx struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); zeroAddress.sin_family = AF_INET; if (proxyReachability) { NSLog(@"Cancelling old network reachability"); SCNetworkReachabilityUnscheduleFromRunLoop(proxyReachability, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); CFRelease(proxyReachability); proxyReachability = nil; } // This notification is used to detect SSID change (switch of Wifi network). The ReachabilityCallback is // not triggered when switching between 2 private Wifi... // Since we cannot be sure we were already observer, remove ourself each time... to be improved _SSID = [LinphoneManager getCurrentWifiSSID]; CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), CFSTR("com.apple.system.config.network_change"), NULL); CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), networkReachabilityNotification, CFSTR("com.apple.system.config.network_change"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); proxyReachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress); if (!SCNetworkReachabilitySetCallback(proxyReachability, (SCNetworkReachabilityCallBack)networkReachabilityCallBack, ctx)) { NSLog(@"Cannot register reachability cb: %s", SCErrorString(SCError())); return; } if (!SCNetworkReachabilityScheduleWithRunLoop(proxyReachability, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) { NSLog(@"Cannot register schedule reachability cb: %s", SCErrorString(SCError())); return; } // this check is to know network connectivity right now without waiting for a change. Don'nt remove it unless you // have good reason. Jehan SCNetworkReachabilityFlags flags; if (SCNetworkReachabilityGetFlags(proxyReachability, &flags)) { networkReachabilityCallBack(proxyReachability, flags, nil); } } - (NetworkType)network { if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) { UIApplication *app = [UIApplication sharedApplication]; NSArray *subviews = [[[app valueForKey:@"statusBar"] valueForKey:@"foregroundView"] subviews]; NSNumber *dataNetworkItemView = nil; for (id subview in subviews) { if ([subview isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) { dataNetworkItemView = subview; break; } } NSNumber *number = (NSNumber *)[dataNetworkItemView valueForKey:@"dataNetworkType"]; return [number intValue]; } else { #pragma deploymate push "ignored-api-availability" CTTelephonyNetworkInfo *info = [[CTTelephonyNetworkInfo alloc] init]; NSString *currentRadio = info.currentRadioAccessTechnology; if ([currentRadio isEqualToString:CTRadioAccessTechnologyEdge]) { return network_2g; } else if ([currentRadio isEqualToString:CTRadioAccessTechnologyLTE]) { return network_4g; } #pragma deploymate pop return network_3g; } } #pragma mark - VTable static LinphoneCoreVTable linphonec_vtable = { .call_state_changed = (LinphoneCoreCallStateChangedCb)linphone_iphone_call_state, .registration_state_changed = linphone_iphone_registration_state, .notify_presence_received_for_uri_or_tel = linphone_iphone_notify_presence_received_for_uri_or_tel, .auth_info_requested = linphone_iphone_popup_password_request, .message_received = linphone_iphone_message_received, .message_received_unable_decrypt = linphone_iphone_message_received_unable_decrypt, .transfer_state_changed = linphone_iphone_transfer_state_changed, .is_composing_received = linphone_iphone_is_composing_received, .configuring_status = linphone_iphone_configuring_status_changed, .global_state_changed = linphone_iphone_global_state_changed, .notify_received = linphone_iphone_notify_received, .call_encryption_changed = linphone_iphone_call_encryption_changed, }; #pragma mark - - (void)audioSessionInterrupted:(NSNotification *)notification { int interruptionType = [notification.userInfo[AVAudioSessionInterruptionTypeKey] intValue]; if (interruptionType == AVAudioSessionInterruptionTypeBegan) { [self beginInterruption]; } else if (interruptionType == AVAudioSessionInterruptionTypeEnded) { [self endInterruption]; } } static BOOL libStarted = FALSE; // scheduling loop - (void)iterate { linphone_core_iterate(theLinphoneCore); } /** Should be called once per linphone_core_new() */ - (void)finishCoreConfiguration { // get default config from bundle NSString *zrtpSecretsFileName = [LinphoneManager documentFile:@"zrtp_secrets"]; NSString *chatDBFileName = [LinphoneManager documentFile:kLinphoneInternalChatDBFilename]; NSString *device = [[NSMutableString alloc] initWithString:[NSString stringWithFormat:@"%@_iOS%@", [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"], UIDevice.currentDevice.systemVersion]]; device = [device stringByReplacingOccurrencesOfString:@"," withString:@"."]; device = [device stringByReplacingOccurrencesOfString:@" " withString:@"."]; linphone_core_set_user_agent(theLinphoneCore, device.UTF8String, "3.16-122-g79a8bb2"); _contactSipField = [self lpConfigStringForKey:@"contact_im_type_value" withDefault:@"SIP"]; // if (_fastAddressBook == nil) { // _fastAddressBook = [[FastAddressBook alloc] init]; // } linphone_core_set_zrtp_secrets_file(theLinphoneCore, [zrtpSecretsFileName UTF8String]); linphone_core_set_chat_database_path(theLinphoneCore, [chatDBFileName UTF8String]); linphone_core_set_call_logs_database_path(theLinphoneCore, [chatDBFileName UTF8String]); [self setupNetworkReachabilityCallback]; NSString *path = [LinphoneManager bundleFile:@"nowebcamCIF.jpg"]; if (path) { const char *imagePath = [path UTF8String]; NSLog(@"Using '%s' as source image for no webcam", imagePath); linphone_core_set_static_picture(theLinphoneCore, imagePath); } /*DETECT cameras*///检测摄像头 _frontCamId = _backCamId = nil; char **camlist = (char **)linphone_core_get_video_devices(theLinphoneCore); if (camlist) { for (char *cam = *camlist; *camlist != NULL; cam = *++camlist) { if (strcmp(FRONT_CAM_NAME, cam) == 0) { _frontCamId = cam; // great set default cam to front NSLog(@"Setting default camera [%s]", _frontCamId); linphone_core_set_video_device(theLinphoneCore, _frontCamId); } if (strcmp(BACK_CAM_NAME, cam) == 0) { _backCamId = cam; } } } else { NSLog(@"No camera detected!"); } [self enableProxyPublish:([UIApplication sharedApplication].applicationState == UIApplicationStateActive)]; NSLog(@"Linphone [%s] started on [%s]", linphone_core_get_version(), [[UIDevice currentDevice].model UTF8String]); // Post event NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:theLinphoneCore] forKey:@"core"]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCoreUpdate object:LinphoneManager.instance userInfo:dict]; } - (void)startLinphoneCore { if (libStarted) { NSLog(@"Liblinphone is already initialized!"); return; } libStarted = TRUE; connectivity = none; signal(SIGPIPE, SIG_IGN); // 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 // tls is enabled. This is a problem for new installations. // linphone_core_migrate_to_multi_transport(theLinphoneCore); // init audio session (just getting the instance will init) AVAudioSession *audioSession = [AVAudioSession sharedInstance]; BOOL bAudioInputAvailable = audioSession.inputAvailable; NSError *err; if (![audioSession setActive:NO error:&err] && err) { NSLog(@"audioSession setActive failed: %@", [err description]); } if (!bAudioInputAvailable) { UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"No microphone", nil) message:NSLocalizedString(@"You need to plug a microphone to your device to use the application.", nil) preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [errView addAction:defaultAction]; // [PhoneMainView.instance presentViewController:errView animated:YES completion:nil]; } // Disable notify policy LinphoneImNotifPolicy *im_notif_policy; im_notif_policy = linphone_core_get_im_notif_policy(theLinphoneCore); if (im_notif_policy != NULL) { /* The IM notification policy can be NULL at this point in case of remote provisioning. */ linphone_im_notif_policy_clear(im_notif_policy); linphone_im_notif_policy_set_send_is_composing(im_notif_policy, TRUE); linphone_im_notif_policy_set_recv_is_composing(im_notif_policy, TRUE); } if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { // go directly to bg mode [self enterBackgroundMode]; } } - (BOOL)enterBackgroundMode { LinphoneProxyConfig *proxyCfg = linphone_core_get_default_proxy_config(theLinphoneCore); BOOL shouldEnterBgMode = FALSE; // disable presence [self enableProxyPublish:NO]; // handle proxy config if any if (proxyCfg) { const char *refkey = proxyCfg ? linphone_proxy_config_get_ref_key(proxyCfg) : NULL; BOOL pushNotifEnabled = (refkey && strcmp(refkey, "push_notification") == 0); if ([LinphoneManager.instance lpConfigBoolForKey:@"backgroundmode_preference"] || pushNotifEnabled) { if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { // For registration register [self refreshRegisters]; } } if ([LinphoneManager.instance lpConfigBoolForKey:@"backgroundmode_preference"]) { // register keepalive if ([[UIApplication sharedApplication] setKeepAliveTimeout:600 /*(NSTimeInterval)linphone_proxy_config_get_expires(proxyCfg)*/ handler:^{ NSLog(@"keepalive handler"); mLastKeepAliveDate = [NSDate date]; if (theLinphoneCore == nil) { NSLog(@"It seems that Linphone BG mode was deactivated, just skipping"); return; } // [_iapManager check]; if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { // For registration register [self refreshRegisters]; } linphone_core_iterate(theLinphoneCore); }]) { NSLog(@"keepalive handler succesfully registered"); } else { NSLog(@"keepalive handler cannot be registered"); } shouldEnterBgMode = TRUE; } } LinphoneCall *currentCall = linphone_core_get_current_call(theLinphoneCore); const bctbx_list_t *callList = linphone_core_get_calls(theLinphoneCore); if (!currentCall // no active call && callList // at least one call in a non active state && bctbx_list_find_custom(callList, (bctbx_compare_func)comp_call_state_paused, NULL)) { [self startCallPausedLongRunningTask]; } if (callList) { /*if at least one call exist, enter normal bg mode */ shouldEnterBgMode = TRUE; } /*stop the video preview*/ if (theLinphoneCore) { linphone_core_enable_video_preview(theLinphoneCore, FALSE); linphone_core_iterate(theLinphoneCore); } linphone_core_stop_dtmf_stream(theLinphoneCore); NSLog(@"Entering [%s] bg mode", shouldEnterBgMode ? "normal" : "lite"); if (!shouldEnterBgMode && floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { const char *refkey = proxyCfg ? linphone_proxy_config_get_ref_key(proxyCfg) : NULL; BOOL pushNotifEnabled = (refkey && strcmp(refkey, "push_notification") == 0); if (pushNotifEnabled) { NSLog(@"Keeping lc core to handle push"); /*destroy voip socket if any and reset connectivity mode*/ connectivity = none; linphone_core_set_network_reachable(theLinphoneCore, FALSE); return YES; } return NO; } else return YES; } - (void)refreshRegisters { if (connectivity == none) { // don't trust ios when he says there is no network. Create a new reachability context, the previous one might // be mis-functionning. NSLog(@"None connectivity"); [self setupNetworkReachabilityCallback]; } NSLog(@"Network reachability callback setup"); if (theLinphoneCore) { linphone_core_refresh_registers(theLinphoneCore); // just to make sure REGISTRATION is up to date } } - (void)becomeActive { // enable presence if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max || self.connectivity == none) { [self refreshRegisters]; } if (pausedCallBgTask) { [[UIApplication sharedApplication] endBackgroundTask:pausedCallBgTask]; pausedCallBgTask = 0; } if (incallBgTask) { [[UIApplication sharedApplication] endBackgroundTask:incallBgTask]; incallBgTask = 0; } /*IOS specific*/ linphone_core_start_dtmf_stream(theLinphoneCore); [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted){ }]; /*start the video preview in case we are in the main view*/ if (linphone_core_video_display_enabled(theLinphoneCore) && [self lpConfigBoolForKey:@"preview_preference"]) { linphone_core_enable_video_preview(theLinphoneCore, TRUE); } /*check last keepalive handler date*/ if (mLastKeepAliveDate != Nil) { NSDate *current = [NSDate date]; if ([current timeIntervalSinceDate:mLastKeepAliveDate] > 700) { NSString *datestr = [mLastKeepAliveDate description]; NSLog(@"keepalive handler was called for the last time at %@", datestr); } } [self enableProxyPublish:YES]; } - (void)createLinphoneCore { [self migrationAllPre]; if (theLinphoneCore != nil) { NSLog(@"linphonecore is already created"); return; } // [Log enableLogs:[self lpConfigIntForKey:@"debugenable_preference"]]; //日志输出相关 [self enableLogs:ORTP_DEBUG]; connectivity = none; // Set audio assets NSString *ring = ([LinphoneManager bundleFile:[self lpConfigStringForKey:@"local_ring" inSection:@"sound"].lastPathComponent] ?: [LinphoneManager bundleFile:@"notes_of_the_optimistic.caf"]) .lastPathComponent; NSString *ringback = ([LinphoneManager bundleFile:[self lpConfigStringForKey:@"remote_ring" inSection:@"sound"].lastPathComponent] ?: [LinphoneManager bundleFile:@"ringback.wav"]) .lastPathComponent; NSString *hold = ([LinphoneManager bundleFile:[self lpConfigStringForKey:@"hold_music" inSection:@"sound"].lastPathComponent] ?: [LinphoneManager bundleFile:@"hold.mkv"]) .lastPathComponent; [self lpConfigSetString:[LinphoneManager bundleFile:ring] forKey:@"local_ring" inSection:@"sound"]; [self lpConfigSetString:[LinphoneManager bundleFile:ringback] forKey:@"remote_ring" inSection:@"sound"]; [self lpConfigSetString:[LinphoneManager bundleFile:hold] forKey:@"hold_music" inSection:@"sound"]; theLinphoneCore = linphone_core_new_with_config(&linphonec_vtable, _configDb, (__bridge void *)(self)); NSLog(@"Create linphonecore %p", theLinphoneCore); // Load plugins if available in the linphone SDK - otherwise these calls will do nothing MSFactory *f = linphone_core_get_ms_factory(theLinphoneCore); libmssilk_init(f); libmsamr_init(f); libmsx264_init(f); libmsopenh264_init(f); // libmsbcg729_init(f); libmswebrtc_init(f); linphone_core_reload_ms_plugins(theLinphoneCore, NULL); [self migrationAllPost]; //设置CA证书 /* set the CA file no matter what, since the remote provisioning could be hitting an HTTPS server */ linphone_core_set_root_ca(theLinphoneCore, [LinphoneManager bundleFile:@"rootca.pem"].UTF8String); linphone_core_set_user_certificates_path(theLinphoneCore, [LinphoneManager cacheDirectory].UTF8String); /* The core will call the linphone_iphone_configuring_status_changed callback when the remote provisioning is loaded (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 object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(configuringStateChangedNotificationHandler:) name:kLinphoneConfiguringStateUpdate object:nil]; /*call iterate once immediately in order to initiate background connections with sip server or remote provisioning * grab, if any */ linphone_core_iterate(theLinphoneCore); // start scheduler mIterateTimer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(iterate) userInfo:nil repeats:YES]; } - (void)resetLinphoneCore { [self destroyLinphoneCore]; [self createLinphoneCore]; // reload friends // [self.fastAddressBook reload]; // reset network state to trigger a new network connectivity assessment linphone_core_set_network_reachable(theLinphoneCore, FALSE); } static int comp_call_id(const LinphoneCall *call, const char *callid) { if (linphone_call_log_get_call_id(linphone_call_get_call_log(call)) == nil) { ms_error("no callid for call [%p]", call); return 1; } 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) { 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; } } #pragma mark - LOG - (void)enableLogs:(OrtpLogLevel)level { BOOL enabled = (level >= ORTP_DEBUG && level < ORTP_ERROR); static BOOL stderrInUse = NO; if (!stderrInUse) { asl_add_log_file(NULL, STDERR_FILENO); stderrInUse = YES; } // linphone_core_set_log_collection_path([self cacheDirectory].UTF8String); // linphone_core_enable_logs_with_cb(linphone_iphone_log_handler); // linphone_core_enable_log_collection(enabled); if (level == 0) { linphone_core_set_log_level(ORTP_FATAL); ortp_set_log_level("ios", ORTP_FATAL); NSLog(@"I/%s/Disabling all logs", ORTP_LOG_DOMAIN); } else { NSLog(@"I/%s/Enabling %s logs", ORTP_LOG_DOMAIN, (enabled ? "all" : "application only")); linphone_core_set_log_level(level); ortp_set_log_level("ios", level == ORTP_DEBUG ? ORTP_DEBUG : ORTP_MESSAGE); } } - (void)acceptCallForCallId:(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) { 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]; return; }; } - (void)addPushCallId:(NSString *)callid { // first, make sure this callid is not already involved in a call const bctbx_list_t *calls = linphone_core_get_calls(theLinphoneCore); if (bctbx_list_find_custom(calls, (bctbx_compare_func)comp_call_id, [callid UTF8String])) { NSLog(@"Call id [%@] already handled", callid); return; }; if ([pushCallIDs count] > 10 /*max number of pending notif*/) [pushCallIDs removeObjectAtIndex:0]; [pushCallIDs addObject:callid]; } - (BOOL)popPushCallID:(NSString *)callId { for (NSString *pendingNotif in pushCallIDs) { if ([pendingNotif compare:callId] == NSOrderedSame) { [pushCallIDs removeObject:pendingNotif]; return TRUE; } } return FALSE; } - (BOOL)resignActive { linphone_core_stop_dtmf_stream(theLinphoneCore); return YES; } #pragma mark - Call Functions - (void)acceptCall:(LinphoneCall *)call evenWithVideo:(BOOL)video { LinphoneCallParams *lcallParams = linphone_core_create_call_params(theLinphoneCore, call); if (!lcallParams) { NSLog(@"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) { NSLog(@"Low bandwidth mode"); } linphone_call_params_enable_low_bandwidth(lcallParams, low_bandwidth); } linphone_call_params_enable_video(lcallParams, video); linphone_call_accept_with_params(call, lcallParams); } - (void)call:(const LinphoneAddress *)iaddr { // First verify that network is available, abort otherwise. if (!linphone_core_is_network_reachable(theLinphoneCore)) { NSLog(@"Network Error", nil); return; } // Then check that no GSM calls are in progress, abort otherwise. CTCallCenter *callCenter = [[CTCallCenter alloc] init]; if ([callCenter currentCalls] != nil && floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { NSLog(@"GSM call in progress, cancelling outgoing SIP call request"); return; } // Then check that the supplied address is valid if (!iaddr) { NSLog(@"Invalid SIP address"); return; } if (linphone_core_get_calls_nb(theLinphoneCore) < 1 && floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max ){ /* self.providerDelegate.callKitCalls++; NSUUID *uuid = [NSUUID UUID]; [LinphoneManager.instance.providerDelegate.uuids setObject:uuid forKey:@""]; 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){ }]; */ [self doCall:iaddr]; } else { [self doCall:iaddr]; } } - (BOOL)doCall:(const LinphoneAddress *)iaddr { 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)) { NSLog(@"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 { 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) { NSLog(@"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_core_invite(LC, linphone_address_as_string(iaddr)); } } } linphone_address_unref(addr); linphone_call_params_unref(lcallParams); return TRUE; } #pragma mark - Property Functions - (void)setPushNotificationToken:(NSData *)apushNotificationToken { if (apushNotificationToken == _pushNotificationToken) { return; } _pushNotificationToken = apushNotificationToken; @try { const MSList *proxies = linphone_core_get_proxy_config_list(LC); while (proxies) { [self configurePushTokenForProxyConfig:proxies->data]; proxies = proxies->next; } } @catch (NSException* e) { NSLog(@"%s: linphone core not ready yet, ignoring push token", __FUNCTION__); } } - (void)configurePushTokenForProxyConfig:(LinphoneProxyConfig *)proxyCfg { linphone_proxy_config_edit(proxyCfg); NSData *tokenData = _pushNotificationToken; const char *refkey = linphone_proxy_config_get_ref_key(proxyCfg); BOOL pushNotifEnabled = (refkey && strcmp(refkey, "push_notification") == 0); if (tokenData != nil && pushNotifEnabled) { const unsigned char *tokenBuffer = [tokenData bytes]; NSMutableString *tokenString = [NSMutableString stringWithCapacity:[tokenData length] * 2]; for (int i = 0; i < [tokenData length]; ++i) { [tokenString appendFormat:@"%02X", (unsigned int)tokenBuffer[i]]; } // NSLocalizedString(@"IC_MSG", nil); // Fake for genstrings // NSLocalizedString(@"IM_MSG", nil); // Fake for genstrings // NSLocalizedString(@"IM_FULLMSG", nil); // Fake for genstrings #ifdef DEBUG #define APPMODE_SUFFIX @"dev" #else #define APPMODE_SUFFIX @"prod" #endif NSString *ring = ([LinphoneManager bundleFile:[self lpConfigStringForKey:@"local_ring" inSection:@"sound"].lastPathComponent] ?: [LinphoneManager bundleFile:@"notes_of_the_optimistic.caf"]) .lastPathComponent; NSString * notif_type; if (floor(NSFoundationVersionNumber) >= NSFoundationVersionNumber_iOS_8_0) { //IOS 8 and more notif_type = @".voip"; } else { // IOS 7 and below notif_type = @""; } NSString *timeout; if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) { timeout = @";pn-timeout=0"; } else { timeout = @""; } NSString *silent; if (floor(NSFoundationVersionNumber) >= NSFoundationVersionNumber_iOS_8_0) { silent = @";pn-silent=1"; } else { silent = @""; } NSString *params = [NSString stringWithFormat:@"app-id=%@%@.%@;pn-type=apple;pn-tok=%@;pn-msg-str=IM_MSG;pn-call-str=IC_MSG;pn-" @"call-snd=%@;pn-msg-snd=msg.caf%@%@", [[NSBundle mainBundle] bundleIdentifier], notif_type, APPMODE_SUFFIX, tokenString, ring, timeout, silent]; // NSLog(@"Proxy config %s configured for push notifications with contact: %@", // linphone_proxy_config_get_identity_address(proxyCfg), params); linphone_proxy_config_set_contact_uri_parameters(proxyCfg, [params UTF8String]); linphone_proxy_config_set_contact_parameters(proxyCfg, NULL); } else { // NSLog(@"Proxy config %s NOT configured for push notifications", linphone_proxy_config_get_identity_address(proxyCfg)); // no push token: linphone_proxy_config_set_contact_uri_parameters(proxyCfg, NULL); linphone_proxy_config_set_contact_parameters(proxyCfg, NULL); } linphone_proxy_config_done(proxyCfg); } #pragma mark - Misc Functions + (NSString *)bundleFile:(NSString *)file { return [[NSBundle mainBundle] pathForResource:[file stringByDeletingPathExtension] ofType:[file pathExtension]]; } + (NSString *)documentFile:(NSString *)file { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsPath = [paths objectAtIndex:0]; return [documentsPath stringByAppendingPathComponent:file]; } + (NSString *)cacheDirectory { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *cachePath = [paths objectAtIndex:0]; BOOL isDir = NO; NSError *error; // cache directory must be created if not existing if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath isDirectory:&isDir] && isDir == NO) { [[NSFileManager defaultManager] createDirectoryAtPath:cachePath withIntermediateDirectories:NO attributes:nil error:&error]; } return cachePath; } + (int)unreadMessageCount { int count = 0; const MSList *rooms = linphone_core_get_chat_rooms(LC); const MSList *item = rooms; while (item) { LinphoneChatRoom *room = (LinphoneChatRoom *)item->data; if (room) { count += linphone_chat_room_get_unread_messages_count(room); } item = item->next; } return count; } + (BOOL)copyFile:(NSString *)src destination:(NSString *)dst override:(BOOL)override { NSFileManager *fileManager = NSFileManager.defaultManager; NSError *error = nil; if ([fileManager fileExistsAtPath:src] == NO) { NSLog(@"Can't find \"%@\": %@", src, [error localizedDescription]); return FALSE; } if ([fileManager fileExistsAtPath:dst] == YES) { if (override) { [fileManager removeItemAtPath:dst error:&error]; if (error != nil) { NSLog(@"Can't remove \"%@\": %@", dst, [error localizedDescription]); return FALSE; } } else { NSLog(@"\"%@\" already exists", dst); return FALSE; } } [fileManager copyItemAtPath:src toPath:dst error:&error]; if (error != nil) { NSLog(@"Can't copy \"%@\" to \"%@\": %@", src, dst, [error localizedDescription]); return FALSE; } return TRUE; } - (void)configureVbrCodecs { PayloadType *pt; int bitrate = lp_config_get_int( _configDb, "audio", "codec_bitrate_limit", kLinphoneAudioVbrCodecDefaultBitrate); /*default value is in linphonerc or linphonerc-factory*/ const MSList *audio_codecs = linphone_core_get_audio_codecs(theLinphoneCore); const MSList *codec = audio_codecs; while (codec) { pt = codec->data; if (linphone_payload_type_is_vbr(pt)) { linphone_core_set_payload_type_bitrate(theLinphoneCore, pt, bitrate); } codec = codec->next; } } + (id)getMessageAppDataForKey:(NSString *)key inMessage:(LinphoneChatMessage *)msg { if (msg == nil) return nil; id value = nil; const char *appData = linphone_chat_message_get_appdata(msg); if (appData) { NSDictionary *appDataDict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithBytes:appData length:strlen(appData)] options:0 error:nil]; value = [appDataDict objectForKey:key]; } return value; } + (void)setValueInMessageAppData:(id)value forKey:(NSString *)key inMessage:(LinphoneChatMessage *)msg { NSMutableDictionary *appDataDict = [NSMutableDictionary dictionary]; const char *appData = linphone_chat_message_get_appdata(msg); if (appData) { appDataDict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithBytes:appData length:strlen(appData)] options:NSJSONReadingMutableContainers error:nil]; } [appDataDict setValue:value forKey:key]; NSData *data = [NSJSONSerialization dataWithJSONObject:appDataDict options:0 error:nil]; NSString *appdataJSON = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; linphone_chat_message_set_appdata(msg, [appdataJSON UTF8String]); } #pragma mark - LPConfig Functions - (void)lpConfigSetString:(NSString *)value forKey:(NSString *)key { [self lpConfigSetString:value forKey:key inSection:LINPHONERC_APPLICATION_KEY]; } - (void)lpConfigSetString:(NSString *)value forKey:(NSString *)key inSection:(NSString *)section { if (!key) return; lp_config_set_string(_configDb, [section UTF8String], [key UTF8String], value ? [value UTF8String] : NULL); } - (NSString *)lpConfigStringForKey:(NSString *)key { return [self lpConfigStringForKey:key withDefault:nil]; } - (NSString *)lpConfigStringForKey:(NSString *)key withDefault:(NSString *)defaultValue { return [self lpConfigStringForKey:key inSection:LINPHONERC_APPLICATION_KEY withDefault:defaultValue]; } - (NSString *)lpConfigStringForKey:(NSString *)key inSection:(NSString *)section { return [self lpConfigStringForKey:key inSection:section withDefault:nil]; } - (NSString *)lpConfigStringForKey:(NSString *)key inSection:(NSString *)section withDefault:(NSString *)defaultValue { if (!key) return defaultValue; const char *value = lp_config_get_string(_configDb, [section UTF8String], [key UTF8String], NULL); return value ? [NSString stringWithUTF8String:value] : defaultValue; } - (void)lpConfigSetInt:(int)value forKey:(NSString *)key { [self lpConfigSetInt:value forKey:key inSection:LINPHONERC_APPLICATION_KEY]; } - (void)lpConfigSetInt:(int)value forKey:(NSString *)key inSection:(NSString *)section { if (!key) return; lp_config_set_int(_configDb, [section UTF8String], [key UTF8String], (int)value); } - (int)lpConfigIntForKey:(NSString *)key { return [self lpConfigIntForKey:key withDefault:-1]; } - (int)lpConfigIntForKey:(NSString *)key withDefault:(int)defaultValue { return [self lpConfigIntForKey:key inSection:LINPHONERC_APPLICATION_KEY withDefault:defaultValue]; } - (int)lpConfigIntForKey:(NSString *)key inSection:(NSString *)section { return [self lpConfigIntForKey:key inSection:section withDefault:-1]; } - (int)lpConfigIntForKey:(NSString *)key inSection:(NSString *)section withDefault:(int)defaultValue { if (!key) return defaultValue; return lp_config_get_int(_configDb, [section UTF8String], [key UTF8String], (int)defaultValue); } - (void)lpConfigSetBool:(BOOL)value forKey:(NSString *)key { [self lpConfigSetBool:value forKey:key inSection:LINPHONERC_APPLICATION_KEY]; } - (void)lpConfigSetBool:(BOOL)value forKey:(NSString *)key inSection:(NSString *)section { [self lpConfigSetInt:(int)(value == TRUE) forKey:key inSection:section]; } - (BOOL)lpConfigBoolForKey:(NSString *)key { return [self lpConfigBoolForKey:key withDefault:FALSE]; } - (BOOL)lpConfigBoolForKey:(NSString *)key withDefault:(BOOL)defaultValue { return [self lpConfigBoolForKey:key inSection:LINPHONERC_APPLICATION_KEY withDefault:defaultValue]; } - (BOOL)lpConfigBoolForKey:(NSString *)key inSection:(NSString *)section { return [self lpConfigBoolForKey:key inSection:section withDefault:FALSE]; } - (BOOL)lpConfigBoolForKey:(NSString *)key inSection:(NSString *)section withDefault:(BOOL)defaultValue { if (!key) return defaultValue; int val = [self lpConfigIntForKey:key inSection:section withDefault:-1]; return (val != -1) ? (val == 1) : defaultValue; } #pragma mark - GSM management - (void)removeCTCallCenterCb { if (mCallCenter != nil) { NSLog(@"Removing CT call center listener [%p]", mCallCenter); mCallCenter.callEventHandler = NULL; } mCallCenter = nil; } static int comp_call_state_paused(const LinphoneCall *call, const void *param) { return linphone_call_get_state(call) != LinphoneCallPaused; } - (void)startPushLongRunningTask:(BOOL)msg { [[UIApplication sharedApplication] endBackgroundTask:pushBgTask]; pushBgTask = 0; pushBgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { if (msg) { NSLog(@"Incomming message couldn't be received"); if (@available(iOS 10.0, *)) { UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; content.title = NSLocalizedString(@"Message received", nil); content.body = NSLocalizedString(@"You have received a message.", nil); content.categoryIdentifier = @"push_msg"; UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:@"push_msg" content:content trigger:NULL]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:req withCompletionHandler:^(NSError *_Nullable error) { // Enable or disable features based on authorization. if (error) { NSLog(@"Error while adding notification request :"); NSLog(@"%@", error.description); } }]; } else { // Fallback on earlier versions } } else { NSLog(@"Incomming call couldn't be received"); if (@available(iOS 10.0, *)) { UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; content.title = NSLocalizedString(@"Missed call", nil); content.body = NSLocalizedString(@"You have missed a call.", nil); content.categoryIdentifier = @"push_call"; UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:@"push_call" content:content trigger:NULL]; [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:req withCompletionHandler:^(NSError *_Nullable error) { // Enable or disable features based on authorization. if (error) { NSLog(@"Error while adding notification request :"); NSLog(@"%@", error.description); } }]; } else { // Fallback on earlier versions } } } for (NSString *key in [LinphoneManager.instance.pushDict allKeys]) { [LinphoneManager.instance.pushDict setValue:[NSNumber numberWithInt:0] forKey:key]; } [[UIApplication sharedApplication] endBackgroundTask:pushBgTask]; pushBgTask = 0; }]; NSLog(@"Long running task started, remaining [%g s] because a push has been received", [[UIApplication sharedApplication] backgroundTimeRemaining]); } - (void)destroyLinphoneCore { [mIterateTimer invalidate]; // just in case [self removeCTCallCenterCb]; if (theLinphoneCore != nil) { // just in case application terminate before linphone core initialization linphone_core_unref(theLinphoneCore); NSLog(@"Destroy linphonecore %p", theLinphoneCore); theLinphoneCore = nil; // Post event NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:theLinphoneCore] forKey:@"core"]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCoreUpdate object:LinphoneManager.instance userInfo:dict]; SCNetworkReachabilityUnscheduleFromRunLoop(proxyReachability, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); if (proxyReachability) CFRelease(proxyReachability); proxyReachability = nil; } libStarted = FALSE; [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - - (void)removeAllAccounts { linphone_core_clear_proxy_config(LC); linphone_core_clear_all_auth_info(LC); } + (BOOL)isMyself:(const LinphoneAddress *)addr { if (!addr) return NO; const MSList *it = linphone_core_get_proxy_config_list(LC); while (it) { if (linphone_address_weak_equal(addr, linphone_proxy_config_get_identity_address(it->data))) { return YES; } it = it->next; } return NO; } //通话被打断 - (void)beginInterruption { LinphoneCall *c = linphone_core_get_current_call(theLinphoneCore); NSLog(@"Sound interruption detected!"); if (c && linphone_call_get_state(c) == LinphoneCallStreamsRunning) { linphone_call_pause(c); } } //被截断状态结束 - (void)endInterruption { NSLog(@"Sound interruption ended!"); } - (void)enableProxyPublish:(BOOL)enabled { if (linphone_core_get_global_state(LC) != LinphoneGlobalOn || !linphone_core_get_default_friend_list(LC)) { NSLog(@"Not changing presence configuration because linphone core not ready yet"); return; } if ([self lpConfigBoolForKey:@"publish_presence"]) { // set present to "tv", because "available" does not work yet if (enabled) { linphone_core_set_presence_model( LC, linphone_core_create_presence_model_with_activity(LC, LinphonePresenceActivityTV, NULL)); } const MSList *proxies = linphone_core_get_proxy_config_list(LC); while (proxies) { LinphoneProxyConfig *cfg = proxies->data; linphone_proxy_config_edit(cfg); linphone_proxy_config_enable_publish(cfg, enabled); linphone_proxy_config_done(cfg); proxies = proxies->next; } // force registration update first, then update friend list subscription linphone_core_iterate(theLinphoneCore); } linphone_friend_list_enable_subscriptions(linphone_core_get_default_friend_list(LC), enabled && [LinphoneManager.instance lpConfigBoolForKey:@"use_rls_presence"]); } - (void)setupGSMInteraction { [self removeCTCallCenterCb]; mCallCenter = [[CTCallCenter alloc] init]; NSLog(@"Adding CT call center listener [%p]", mCallCenter); __block __weak LinphoneManager *weakSelf = self; __block __weak CTCallCenter *weakCCenter = mCallCenter; mCallCenter.callEventHandler = ^(CTCall *call) { // post on main thread [weakSelf performSelectorOnMainThread:@selector(handleGSMCallInteration:) withObject:weakCCenter waitUntilDone:YES]; }; } - (NSString *)contactFilter { NSString *filter = @"*"; if ([self lpConfigBoolForKey:@"contact_filter_on_default_domain"]) { LinphoneProxyConfig *proxy_cfg = linphone_core_get_default_proxy_config(theLinphoneCore); if (proxy_cfg && linphone_proxy_config_get_addr(proxy_cfg)) { return [NSString stringWithCString:linphone_proxy_config_get_domain(proxy_cfg) encoding:[NSString defaultCStringEncoding]]; } } return filter; } #pragma mark- 被电话中断了 - (void)handleGSMCallInteration:(id)cCenter { if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { CTCallCenter *ct = (CTCallCenter *)cCenter; // pause current call, if any LinphoneCall *call = linphone_core_get_current_call(theLinphoneCore); if ([ct currentCalls] != nil) { if (call) { NSLog(@"Pausing SIP call because GSM call"); linphone_call_pause(call); [self startCallPausedLongRunningTask]; } else if (linphone_core_is_in_conference(theLinphoneCore)) { NSLog(@"Leaving conference call because GSM call"); linphone_core_leave_conference(theLinphoneCore); [self startCallPausedLongRunningTask]; } } // else nop, keep call in paused state } } - (void)startCallPausedLongRunningTask { pausedCallBgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ NSLog(@"Call cannot be paused any more, too late"); [[UIApplication sharedApplication] endBackgroundTask:pausedCallBgTask]; }]; NSLog(@"Long running task started, remaining [%g s] because at least one call is paused", [[UIApplication sharedApplication] backgroundTimeRemaining]); } #pragma mark - GSM management - (void)renameDefaultSettings { // rename .linphonerc to linphonerc to ease debugging: when downloading // containers from MacOSX, Finder do not display hidden files leading // to useless painful operations to display the .linphonerc file NSString *src = [LinphoneManager documentFile:@".linphonerc"]; NSString *dst = [LinphoneManager documentFile:@"linphonerc"]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *fileError = nil; if ([fileManager fileExistsAtPath:src]) { if ([fileManager fileExistsAtPath:dst]) { [fileManager removeItemAtPath:src error:&fileError]; NSLog(@"%@ already exists, simply removing %@ %@", dst, src, fileError ? fileError.localizedDescription : @"successfully"); } else { [fileManager moveItemAtPath:src toPath:dst error:&fileError]; NSLog(@"%@ moving to %@ %@", dst, src, fileError ? fileError.localizedDescription : @"successfully"); } } } // ugly hack to export symbol from liblinphone so that they are available for the linphoneTests target // linphoneTests target do not link with liblinphone but instead dynamically link with ourself which is // statically linked with liblinphone, so we must have exported required symbols from the library to // have them available in linphoneTests // DO NOT INVOKE THIS METHOD - (void)exportSymbolsForUITests { linphone_address_set_header(NULL, NULL, NULL); } @end #pragma mark - -------categary @implementation NSString (md5) - (BOOL)containsSubstring:(NSString *)str { if (UIDevice.currentDevice.systemVersion.doubleValue >= 8.0) { #pragma deploymate push "ignored-api-availability" return [self containsString:str]; #pragma deploymate pop } return ([self rangeOfString:str].location != NSNotFound); } @end