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

#ifdef __APPLE__
#include "TargetConditionals.h"
#endif
#if TARGET_OS_IPHONE

#include <Foundation/Foundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/CaptiveNetwork.h>
28
#include <CoreLocation/CoreLocation.h>
29
#include <notify_keys.h>
30 31 32 33
#include <belr/grammarbuilder.h>

#include "linphone/utils/general.h"
#include "linphone/utils/utils.h"
34
#include "c-wrapper/c-wrapper.h"
35 36 37 38 39 40 41

#include "logger/logger.h"
#include "platform-helpers.h"

// TODO: Remove me
#include "private.h"

42
#include "core/app/ios-app-delegate.h"
43 44

#import <AVFoundation/AVFoundation.h>
45 46 47 48 49 50 51 52
// =============================================================================

using namespace std;

LINPHONE_BEGIN_NAMESPACE

class IosPlatformHelpers : public GenericPlatformHelpers {
public:
Nicolas Michon's avatar
Nicolas Michon committed
53
	IosPlatformHelpers (std::shared_ptr<LinphonePrivate::Core> core, void *systemContext);
54 55 56
	~IosPlatformHelpers (){
		[mAppDelegate dealloc];
	}
57 58 59 60 61 62 63 64 65 66 67 68 69 70

	void acquireWifiLock () override {}
	void releaseWifiLock () override {}
	void acquireMcastLock () override {}
	void releaseMcastLock () override {}
	void acquireCpuLock () override;
	void releaseCpuLock () override;

	string getConfigPath () const override { return ""; }
	string getDataPath () const override { return ""; }
	string getDataResource (const string &filename) const override;
	string getImageResource (const string &filename) const override;
	string getRingResource (const string &filename) const override;
	string getSoundResource (const string &filename) const override;
71
	void * getPathContext () override;
72 73 74 75 76 77 78

	string getWifiSSID() override;
	void setWifiSSID(const string &ssid) override;

	void setVideoPreviewWindow (void *windowId) override {}
	string getDownloadPath () override {return Utils::getEmptyConstRefObject<string>();}
	void setVideoWindow (void *windowId) override {}
79
	void resizeVideoPreview (int width, int height) override {}
80 81

	void onWifiOnlyEnabled (bool enabled) override;
82
	bool isActiveNetworkWifiOnlyCompliant () const override;
83 84
	void setDnsServers () override;
	void setHttpProxy (const string &host, int port) override;
Simon Morlat's avatar
Simon Morlat committed
85
	bool startNetworkMonitoring() override;
86 87 88 89 90
	void stopNetworkMonitoring() override;

	void onLinphoneCoreStart (bool monitoringEnabled) override;
	void onLinphoneCoreStop () override;

91 92
	void startAudioForEchoTestOrCalibration () override;
	void stopAudioForEchoTestOrCalibration () override;
DanmeiChen's avatar
DanmeiChen committed
93 94 95
	
	void start (std::shared_ptr<LinphonePrivate::Core> core) override;
	void stop (void) override;
96

97 98 99 100 101 102
	//IosHelper specific
	bool isReachable(SCNetworkReachabilityFlags flags);
	void networkChangeCallback(void);
	void onNetworkChanged(bool reachable, bool force);

private:
Nicolas Michon's avatar
Nicolas Michon committed
103
	string toUTF8String(CFStringRef str);
104 105 106 107 108 109 110
	void kickOffConnectivity();
	void getHttpProxySettings(void);

	void bgTaskTimeout ();
	static void sBgTaskTimeout (void *data);
	static string getResourceDirPath (const string &framework, const string &resource);
	static string getResourcePath (const string &framework, const string &resource);
111
    
112 113 114
	long int mCpuLockTaskId;
	int mCpuLockCount;
	SCNetworkReachabilityRef reachabilityRef = NULL;
Simon Morlat's avatar
Simon Morlat committed
115 116
	SCNetworkReachabilityFlags mCurrentFlags = 0;
	bool mNetworkMonitoringEnabled = false;
117
	static const string Framework;
118
	IosHandler *mHandler = NULL;
DanmeiChen's avatar
DanmeiChen committed
119 120 121 122
	IosAppDelegate *mAppDelegate = NULL; /* auto didEnterBackground/didEnterForeground and other callbacks */
	bool mStart = false; /* generic platformhelper's funcs only work when mStart is true */
	bool mUseAppDelgate = false; /* app delegate is only used by main core*/
	NSTimer* mIterateTimer = NULL;
123 124 125 126 127 128 129 130
};

static void sNetworkChangeCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo);

// =============================================================================

const string IosPlatformHelpers::Framework = "org.linphone.linphone";

Nicolas Michon's avatar
Nicolas Michon committed
131
IosPlatformHelpers::IosPlatformHelpers (std::shared_ptr<LinphonePrivate::Core> core, void *systemContext) : GenericPlatformHelpers(core) {
DanmeiChen's avatar
DanmeiChen committed
132
	mUseAppDelgate = core->getCCore()->is_main_core && !linphone_config_get_int(core->getCCore()->config, "tester", "test_env", false);
DanmeiChen's avatar
DanmeiChen committed
133
	if (mUseAppDelgate) {
134
		mAppDelegate = [[IosAppDelegate alloc] initWithCore:core];
DanmeiChen's avatar
DanmeiChen committed
135 136 137 138 139
	}
	ms_message("IosPlatformHelpers is fully initialised");
}

void IosPlatformHelpers::start (std::shared_ptr<LinphonePrivate::Core> core) {
140 141
	mCpuLockCount = 0;
	mCpuLockTaskId = 0;
jehan's avatar
jehan committed
142
	mNetworkReachable = 0; // wait until monitor to give a status;
143
	mSharedCoreHelpers = createIosSharedCoreHelpers(core);
144
	mHandler = [[IosHandler alloc] initWithCore:core];
DanmeiChen's avatar
DanmeiChen committed
145 146 147
	if (mUseAppDelgate) {
		[mAppDelegate configure:core];
	}
148 149 150 151 152 153 154

	string cpimPath = getResourceDirPath(Framework, "cpim_grammar");
	if (!cpimPath.empty())
		belr::GrammarLoader::get().addPath(cpimPath);
	else
		ms_error("IosPlatformHelpers did not find cpim grammar resource directory...");

155 156 157 158 159 160
	string identityPath = getResourceDirPath(Framework, "identity_grammar");
	if (!identityPath.empty())
		belr::GrammarLoader::get().addPath(identityPath);
	else
		ms_error("IosPlatformHelpers did not find identity grammar resource directory...");

161 162 163 164 165 166 167
#ifdef VCARD_ENABLED
	string vcardPath = getResourceDirPath("org.linphone.belcard", "vcard_grammar");
	if (!vcardPath.empty())
		belr::GrammarLoader::get().addPath(vcardPath);
	else
		ms_message("IosPlatformHelpers did not find vcard grammar resource directory...");
#endif
168

DanmeiChen's avatar
DanmeiChen committed
169 170 171 172 173 174
	ms_message("IosPlatformHelpers is fully started");
	mStart = true;
}

void IosPlatformHelpers::stop () {
	mStart = false;
175
	[mHandler dealloc];
DanmeiChen's avatar
DanmeiChen committed
176
	ms_message("IosPlatformHelpers is fully stopped");
177 178
}

Nicolas Michon's avatar
Nicolas Michon committed
179 180 181 182
//Safely get an UTF-8 string from the given CFStringRef
string IosPlatformHelpers::toUTF8String(CFStringRef str) {
	string ret;

183
	if (str == NULL) {
Nicolas Michon's avatar
Nicolas Michon committed
184
		return ret;
185 186 187 188
	}
	CFIndex length = CFStringGetLength(str);
	CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
	char *buffer = (char *) malloc((size_t) maxSize);
Nicolas Michon's avatar
Nicolas Michon committed
189 190 191 192 193
	if (buffer) {
		if (CFStringGetCString(str, buffer, maxSize, kCFStringEncodingUTF8)) {
			ret = buffer;
		}
		free(buffer);
194
	}
Nicolas Michon's avatar
Nicolas Michon committed
195
	return ret;
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
}

// -----------------------------------------------------------------------------

void IosPlatformHelpers::bgTaskTimeout () {
	ms_error("IosPlatformHelpers: the system requests that the cpu lock is released now.");
	if (mCpuLockTaskId != 0) {
		belle_sip_end_background_task(static_cast<unsigned long>(mCpuLockTaskId));
		mCpuLockTaskId = 0;
	}
}

void IosPlatformHelpers::sBgTaskTimeout (void *data) {
	IosPlatformHelpers *zis = static_cast<IosPlatformHelpers *>(data);
	zis->bgTaskTimeout();
}

// -----------------------------------------------------------------------------

void IosPlatformHelpers::acquireCpuLock () {
DanmeiChen's avatar
DanmeiChen committed
216 217
	if (!mStart) return;

218 219 220 221 222 223 224 225
	// on iOS, cpu lock is implemented by a long running task and it is abstracted by belle-sip, so let's use belle-sip directly.
	if (mCpuLockCount == 0)
		mCpuLockTaskId = static_cast<long>(belle_sip_begin_background_task("Liblinphone cpu lock", sBgTaskTimeout, this));

	mCpuLockCount++;
}

void IosPlatformHelpers::releaseCpuLock () {
DanmeiChen's avatar
DanmeiChen committed
226 227
	if (!mStart) return;

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
	mCpuLockCount--;
	if (mCpuLockCount != 0)
		return;

	if (mCpuLockTaskId == 0) {
		ms_error("IosPlatformHelpers::releaseCpuLock(): too late, the lock has been released already by the system.");
		return;
	}

	belle_sip_end_background_task(static_cast<unsigned long>(mCpuLockTaskId));
	mCpuLockTaskId = 0;
}

// -----------------------------------------------------------------------------

string IosPlatformHelpers::getDataResource (const string &filename) const {
	return getResourcePath(Framework, filename);
}

string IosPlatformHelpers::getImageResource (const string &filename) const {
	return getResourcePath(Framework, filename);
}

string IosPlatformHelpers::getRingResource (const string &filename) const {
	return getResourcePath(Framework, filename);
}

string IosPlatformHelpers::getSoundResource (const string &filename) const {
	return getResourcePath(Framework, filename);
}

// -----------------------------------------------------------------------------

string IosPlatformHelpers::getResourceDirPath (const string &framework, const string &resource) {
	CFStringEncoding encodingMethod = CFStringGetSystemEncoding();
	CFStringRef cfFramework = CFStringCreateWithCString(nullptr, framework.c_str(), encodingMethod);
	CFStringRef cfResource = CFStringCreateWithCString(nullptr, resource.c_str(), encodingMethod);
	CFBundleRef bundle = CFBundleGetBundleWithIdentifier(cfFramework);
	CFURLRef resourceUrl = CFBundleCopyResourceURL(bundle, cfResource, nullptr, nullptr);
	string path("");
	if (resourceUrl) {
		CFURLRef resourceUrlDirectory = CFURLCreateCopyDeletingLastPathComponent(nullptr, resourceUrl);
		CFStringRef resourcePath = CFURLCopyFileSystemPath(resourceUrlDirectory, kCFURLPOSIXPathStyle);
		path = CFStringGetCStringPtr(resourcePath, encodingMethod);
		CFRelease(resourcePath);
		CFRelease(resourceUrlDirectory);
		CFRelease(resourceUrl);
	}

	CFRelease(cfResource);
	CFRelease(cfFramework);
	return path;
}

string IosPlatformHelpers::getResourcePath (const string &framework, const string &resource) {
	return getResourceDirPath(framework, resource) + "/" + resource;
}

286 287 288 289
void *IosPlatformHelpers::getPathContext () {
	return getSharedCoreHelpers()->getPathContext();
}

290
void IosPlatformHelpers::onLinphoneCoreStart(bool monitoringEnabled) {
DanmeiChen's avatar
DanmeiChen committed
291 292
	if (!mStart) return;

293 294 295
	mNetworkMonitoringEnabled = monitoringEnabled;
	if (monitoringEnabled) {
		startNetworkMonitoring();
DanmeiChen's avatar
DanmeiChen committed
296 297
	}

298 299 300 301
	if (mUseAppDelgate && linphone_core_is_auto_iterate_enabled(getCore()->getCCore())) {
		if (mIterateTimer && mIterateTimer.valid) {
			ms_message("[IosPlatformHelpers] core.iterate() is already scheduled");
			return;
DanmeiChen's avatar
DanmeiChen committed
302
		}
303 304 305 306 307 308
		mIterateTimer = [NSTimer timerWithTimeInterval:0.02 target:mAppDelegate selector:@selector(iterate) userInfo:nil repeats:YES];
		// NSTimer runs only in the main thread correctly. Since there may not be a current thread loop.
		[[NSRunLoop mainRunLoop] addTimer:mIterateTimer forMode:NSDefaultRunLoopMode];
		ms_message("[IosPlatformHelpers] Call to core.iterate() scheduled every 20ms");
	} else {
		ms_warning("[IosPlatformHelpers] Auto core.iterate() isn't enabled, ensure you do it in your application!");
309 310 311 312
	}
}

void IosPlatformHelpers::onLinphoneCoreStop() {
DanmeiChen's avatar
DanmeiChen committed
313 314
	if (!mStart) return;

315 316
	if (mNetworkMonitoringEnabled) {
		stopNetworkMonitoring();
DanmeiChen's avatar
DanmeiChen committed
317 318 319 320 321 322 323 324
	}

	if (mUseAppDelgate && linphone_core_is_auto_iterate_enabled(getCore()->getCCore())) {
		if (mIterateTimer) {
			[mIterateTimer invalidate];
			mIterateTimer = NULL;
		}
		ms_message("[IosPlatformHelpers] Auto core.iterate() stopped");
325
	}
326 327

	getSharedCoreHelpers()->onLinphoneCoreStop();
328 329
}

330 331 332 333 334 335 336
void IosPlatformHelpers::startAudioForEchoTestOrCalibration () {

}

void IosPlatformHelpers::stopAudioForEchoTestOrCalibration () {
	
}
337 338

void IosPlatformHelpers::onWifiOnlyEnabled(bool enabled) {
DanmeiChen's avatar
DanmeiChen committed
339 340
	if (!mStart) return;

341
	mWifiOnly = enabled;
jehan's avatar
jehan committed
342
	if (isNetworkReachable()) {
343 344 345 346 347 348 349 350 351 352 353 354 355
		//Nothing to do if we have no connection
		if (enabled && (mCurrentFlags & kSCNetworkReachabilityFlagsIsWWAN)) {
			onNetworkChanged(false, true);
		}
	}
}

void IosPlatformHelpers::setDnsServers () {
	//Nothing to do here, already handled by core for IOS platforms
}

//Set proxy settings on core
void IosPlatformHelpers::setHttpProxy (const string &host, int port) {
DanmeiChen's avatar
DanmeiChen committed
356 357
	if (!mStart) return;

Nicolas Michon's avatar
Nicolas Michon committed
358 359
	linphone_core_set_http_proxy_host(getCore()->getCCore(), host.c_str());
	linphone_core_set_http_proxy_port(getCore()->getCCore(), port);
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
}

//Get global proxy settings from system and set variables mHttpProxy{Host,Port,Enabled}.
void IosPlatformHelpers::getHttpProxySettings(void) {
	CFDictionaryRef proxySettings = CFNetworkCopySystemProxySettings();

	if (proxySettings) {
		CFNumberRef enabled = (CFNumberRef) CFDictionaryGetValue(proxySettings, kCFNetworkProxiesHTTPEnable);
		if (enabled != NULL) {
			int val = 0;
			CFNumberGetValue(enabled, kCFNumberIntType, &val);
			mHttpProxyEnabled = !!val;
		}
		if (mHttpProxyEnabled) {
			CFStringRef proxyHost = (CFStringRef) CFDictionaryGetValue(proxySettings, kCFNetworkProxiesHTTPProxy);
			if (proxyHost != NULL) {
Nicolas Michon's avatar
Nicolas Michon committed
376 377
				mHttpProxyHost = toUTF8String(proxyHost);
				if (mHttpProxyHost.empty()) {
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
					mHttpProxyEnabled = false;
				}
			} else {
				mHttpProxyEnabled = false;
			}
		}
		if (mHttpProxyEnabled) {
			CFNumberRef proxyPort = (CFNumberRef) CFDictionaryGetValue(proxySettings, kCFNetworkProxiesHTTPPort);
			if (proxyPort != NULL) {
				if (!CFNumberGetValue(proxyPort, kCFNumberIntType, &mHttpProxyPort)) {
					mHttpProxyEnabled = false;
				}
			} else {
				mHttpProxyEnabled = false;
			}
		}
		CFRelease(proxySettings);
	}
	if (!mHttpProxyEnabled) {
		mHttpProxyPort = 0;
		mHttpProxyHost.clear();
	}
}

static void showNetworkFlags(SCNetworkReachabilityFlags flags) {
	ms_message("Network connection flags:");

	if (flags == 0)
		ms_message("no flags.");
	if (flags & kSCNetworkReachabilityFlagsTransientConnection)
		ms_message("kSCNetworkReachabilityFlagsTransientConnection, ");
	if (flags & kSCNetworkReachabilityFlagsReachable)
		ms_message("kSCNetworkReachabilityFlagsReachable, ");
	if (flags & kSCNetworkReachabilityFlagsConnectionRequired)
		ms_message("kSCNetworkReachabilityFlagsConnectionRequired, ");
	if (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic)
		ms_message("kSCNetworkReachabilityFlagsConnectionOnTraffic, ");
	if (flags & kSCNetworkReachabilityFlagsConnectionOnDemand)
		ms_message("kSCNetworkReachabilityFlagsConnectionOnDemand, ");
	if (flags & kSCNetworkReachabilityFlagsIsLocalAddress)
		ms_message("kSCNetworkReachabilityFlagsIsLocalAddress, ");
	if (flags & kSCNetworkReachabilityFlagsIsDirect)
		ms_message("kSCNetworkReachabilityFlagsIsDirect, ");
	if (flags & kSCNetworkReachabilityFlagsIsWWAN)
		ms_message("kSCNetworkReachabilityFlagsIsWWAN, ");
	ms_message("\n");
}

bool IosPlatformHelpers::startNetworkMonitoring(void) {
DanmeiChen's avatar
DanmeiChen committed
427 428
	if (!mStart) return false;

429 430 431 432 433 434
	if (reachabilityRef != NULL) {
		stopNetworkMonitoring();
	}
	//Trying to reach captive.apple.com by default.	Apple uses it already to check if wifi is a captive network
	//See /Library/Preferences/SystemConfiguration/CaptiveNetworkSupport/Settings.plist for more URL examples
	string reachabilityTargetHost = "captive.apple.com";
Nicolas Michon's avatar
Nicolas Michon committed
435
	LinphoneProxyConfig *proxy = linphone_core_get_default_proxy_config(getCore()->getCCore());
436 437 438 439 440 441 442 443 444 445

	//Try to reach default proxy config server if defined
	if (proxy) {
		reachabilityTargetHost = linphone_proxy_config_get_server_addr(proxy);
	}

	reachabilityRef = SCNetworkReachabilityCreateWithName(NULL, reachabilityTargetHost.c_str());
	if (reachabilityRef == NULL) {
		return false;
	}
jehan's avatar
jehan committed
446
	CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (void *) this, sNetworkChangeCallback, CFSTR(kNotifySCNetworkChange), NULL, CFNotificationSuspensionBehaviorDeliverImmediately /*Ignored*/);
447 448 449 450 451 452 453

	//Load and trigger initial state
	networkChangeCallback();
	return true;
}

void IosPlatformHelpers::stopNetworkMonitoring(void) {
DanmeiChen's avatar
DanmeiChen committed
454 455
	if (!mStart) return;

456 457 458 459
	if (reachabilityRef) {
		CFRelease(reachabilityRef);
		reachabilityRef = NULL;
	}
Paul Cartier's avatar
Paul Cartier committed
460
	CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), (void *) this, CFSTR(kNotifySCNetworkChange), NULL);
461 462 463 464 465 466 467 468
}

//This callback keeps tracks of wifi SSID changes
static void sNetworkChangeCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
	if (!observer) {
		return;
	}
	IosPlatformHelpers *iosHelper = (IosPlatformHelpers *) observer;
Paul Cartier's avatar
Paul Cartier committed
469 470

	iosHelper->getCore()->doLater([iosHelper] () {
471 472 473 474 475
		iosHelper->networkChangeCallback();
	});
}

void IosPlatformHelpers::networkChangeCallback() {
DanmeiChen's avatar
DanmeiChen committed
476 477
	if (!mStart) return;

478 479 480
	//We receive this notification multiple time for possibly unknown reasons
	//So take actions only if state changed of our internal network-related properties

jehan's avatar
jehan committed
481
	bool reachable = isNetworkReachable(), changed = false, force = false;
482 483 484 485 486 487

	SCNetworkReachabilityFlags flags;
	if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) {
		showNetworkFlags(flags);

		reachable = isReachable(flags);
jehan's avatar
jehan committed
488
		if (flags != mCurrentFlags || reachable != isNetworkReachable()) {
489
			changed = true;
490 491 492 493
			if (mCurrentFlags == 0
				||
				/*check if moving from Wifi to cellular*/
				(mCurrentFlags & kSCNetworkReachabilityFlagsIsWWAN) != (flags & kSCNetworkReachabilityFlagsIsWWAN)) {
494 495 496 497 498 499
				//Force reinit after network down
				force = true;
			}
		}
		mCurrentFlags = flags;
	}
500
	if (!(flags & kSCNetworkReachabilityFlagsIsWWAN)) {
501 502 503 504 505 506 507
/*
 * CHECK_SSID is undefined by default. Previously we are monitoring SSID changes to notify liblinphone of possible network changes.
 * Since iOS 12, it requires an annoying location permission. For that reason, the default algorithm just monitors
 * the default local ip addresses for changes. A bit less reliable since we can change from wifi network but by change obtain the same ip address.
 * But much less annoying because it doesn't require to ask a permission to the user.
 */
#ifdef CHECK_SSID
508 509 510 511 512 513 514 515 516
		//Only check for wifi changes if current connection type is Wifi
		string newSSID = getWifiSSID();
		if (newSSID.empty() || newSSID.compare(mCurrentSSID) != 0) {
			setWifiSSID(newSSID);
			changed = true;
			//We possibly changed network, force reset of transports
			force = true;
			ms_message("New Wifi SSID detected: %s", mCurrentSSID.empty()?"[none]":mCurrentSSID.c_str());
		}
517
#else
518
        if (reachable && !force){
519 520 521
            force = checkIpAddressChanged();
        }
#endif
522 523 524 525 526 527 528
	}
	getHttpProxySettings();
	if (mHttpProxyEnabled) {
		ms_message("Update HTTP proxy settings: using [%s:%d]", mHttpProxyHost.c_str(), mHttpProxyPort);
	} else {
		ms_message("Updated HTTP proxy settings: no proxy");
	}
Nicolas Michon's avatar
Nicolas Michon committed
529
	const char *currentProxyHostCstr = linphone_core_get_http_proxy_host(getCore()->getCCore());
530
	string currentProxyHost;
Nicolas Michon's avatar
Nicolas Michon committed
531
	int currentProxyPort = linphone_core_get_http_proxy_port(getCore()->getCCore());
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
	if (currentProxyHostCstr) {
		currentProxyHost = currentProxyHostCstr;
	}
	if (mHttpProxyEnabled == currentProxyHost.empty() || currentProxyPort != mHttpProxyPort || currentProxyHost.compare(mHttpProxyHost)) {
		//Empty host is considered as no proxy
		changed = true;
		force = true;
	}
	if (changed) {
		onNetworkChanged(reachable, force);
	}
}

//Get reachability state from given flags
bool IosPlatformHelpers::isReachable(SCNetworkReachabilityFlags flags) {
	if (flags) {
		if (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) {
			//Assume unreachable for now. Wait for kickoff and for later network change events
			kickOffConnectivity();
			return false;
		}
		if (flags & (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsInterventionRequired)) {
			return false;
		}
		if (!(flags & kSCNetworkReachabilityFlagsReachable)) {
			return false;
		}
Nicolas Michon's avatar
Nicolas Michon committed
559
		bool isWifiOnly = linphone_core_wifi_only_enabled(getCore()->getCCore());
560 561 562 563 564 565 566 567 568
		if (isWifiOnly && (flags & kSCNetworkReachabilityFlagsIsWWAN)) {
			return false;
		}
	} else {
		return false;
	}
	return true;
}

569 570 571 572
bool IosPlatformHelpers::isActiveNetworkWifiOnlyCompliant() const {
	return false;
}

573 574
//Method called when we detected actual network changes in callbacks
void IosPlatformHelpers::onNetworkChanged(bool reachable, bool force) {
jehan's avatar
jehan committed
575
	if (reachable != isNetworkReachable() || force) {
576 577
		ms_message("Global network status changed: reachable: [%d].", (int) reachable);
		setHttpProxy(mHttpProxyHost, mHttpProxyPort);
jehan's avatar
jehan committed
578 579 580 581
		if (force && reachable){
			//mandatory to  trigger action from the core in case of switch from 3G to wifi (both up)
			setNetworkReachable(FALSE);
		}
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
		setNetworkReachable(reachable);
	}
}

//In case reachability flag "kSCNetworkReachabilityFlagsConnectionOnDemand" is set, we have to use CFStream APIs for the connection to be opened
//Start asynchronously the connection kickoff and do not change reachability status.
//We assume we will be notified via callbacks if connection changes state afterwards
//192.168.0.200	is just an arbitrary address. It should	still activate the on-demand connection even if not routable
void IosPlatformHelpers::kickOffConnectivity() {
	static bool in_progress = false;
	if (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 useconds_t 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) {
				ms_error("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 = false;
					break;
				}
				loop++;
			}

			if (status == kCFStreamStatusOpen) {
				CFWriteStreamWrite(writeStream, (const UInt8 *)buff, (CFIndex) strlen(buff));
			} else if (!timeout_reached) {
				CFErrorRef error = CFWriteStreamCopyError(writeStream);
				CFRelease(error);
			} else if (timeout_reached) {
				ms_message("CFStream timeout reached");
			}
			CFWriteStreamClose(writeStream);
			CFRelease(writeStream);
			in_progress = false;
		});
}

void IosPlatformHelpers::setWifiSSID(const string &ssid) {
	mCurrentSSID = ssid;
}

string IosPlatformHelpers::getWifiSSID(void) {
#if TARGET_IPHONE_SIMULATOR
	return "Sim_err_SSID_NotSupported";
#else
	string ssid;
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
	bool shallGetWifiInfo = true;

	if (@available(iOS 13.0, *)) {
		//Starting from IOS13 we need to check for authorization to get wifi information.
		//User permission is asked in the main app
		CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
		if (status != kCLAuthorizationStatusAuthorizedAlways &&
		    status != kCLAuthorizationStatusAuthorizedWhenInUse) {
			shallGetWifiInfo = false;
			ms_warning("User has not given authorization to access Wifi information (Authorization: [%d])", (int) status);
		}
	}
	if (shallGetWifiInfo) {
		CFArrayRef ifaceNames = CNCopySupportedInterfaces();
		if (ifaceNames) {
			CFIndex	i;
			for (i = 0; i < CFArrayGetCount(ifaceNames); ++i) {
				CFStringRef iface = (CFStringRef) CFArrayGetValueAtIndex(ifaceNames, i);
				CFDictionaryRef ifaceInfo = CNCopyCurrentNetworkInfo(iface);

				if (ifaceInfo != NULL && CFDictionaryGetCount(ifaceInfo) > 0) {
					CFStringRef ifaceSSID = (CFStringRef) CFDictionaryGetValue(ifaceInfo, kCNNetworkInfoKeySSID);
					if (ifaceSSID != NULL) {
						ssid = toUTF8String(ifaceSSID);
						if (!ssid.empty()) {
							CFRelease(ifaceInfo);
							break;
						}
681
					}
682
					CFRelease(ifaceInfo);
683 684 685
				}
			}
		}
686
		CFRelease(ifaceNames);
687 688 689 690 691 692 693
	}
	return ssid;
#endif
}

// -----------------------------------------------------------------------------

Nicolas Michon's avatar
Nicolas Michon committed
694 695
PlatformHelpers *createIosPlatformHelpers(std::shared_ptr<LinphonePrivate::Core> core, void *systemContext) {
	return new IosPlatformHelpers(core, systemContext);
696 697
}

698

699 700 701
LINPHONE_END_NAMESPACE

#endif