From 5c8d7f3077b81d039b60a71801f689b13ec689ef Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 8 Aug 2019 16:09:59 -0700 Subject: [PATCH 01/18] draft --- packages/connectivity/README.md | 28 +++++++--- .../example/ios/Runner/Info.plist | 4 ++ .../example/ios/Runner/Runner.entitlements | 8 +++ .../ios/Classes/ConnectivityPlugin.m | 51 ++++++++++++++++--- 4 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 packages/connectivity/example/ios/Runner/Runner.entitlements diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index 215f8991a756..d796eb9650b2 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -60,17 +60,33 @@ var wifiIP = await (Connectivity().getWifiIP());network var wifiName = await (Connectivity().getWifiName());wifi network ``` -### Known Issues +### iOS 12 -#### iOS 13 +To use `.getWifiBSSID()` and `.getWifiName()` on iOS >= 12, the `Access WiFi information capability` in XCode must be enabled. Otherwise, both method will return null. + +### iOS 13 The methods `.getWifiBSSID()` and `.getWifiName()` utilize the [CNCopyCurrentNetworkInfo](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo) function on iOS. -As of iOS 13, Apple announced that these APIs will no longer return valid information by default and will instead return the following: -> SSID: "Wi-Fi" or "WLAN" ("WLAN" will be returned for the China SKU) -> BSSID: "00:00:00:00:00:00" +As of iOS 13, Apple announced that these APIs will no longer return valid information. +An App linked against iOS 12 or earlier receives pseudo-values such as: + + * SSID: "Wi-Fi" or "WLAN" ("WLAN" will be returned for the China SKU). + + * BSSID: "00:00:00:00:00:00" + +An App linked against iOS 13 or later receives null. + +The [CNCopyCurrentNetworkInfo] will work for Apps that: + + * The app uses Core Location, and has the user’s authorization to use location information. + + * The app uses the NEHotspotConfiguration API to configure the current Wi-Fi network. + + * The app has active VPN configurations installed. -You can follow issue [#37804](https://github.com/flutter/flutter/issues/37804) for the changes required to return valid SSID and BSSID values with iOS 13. +If your app falls into the last two categories, it will work as it is. If your app doesn't fall into the last two categories, +and you still need to access the wifi information, you should request user's authorization to use location information. ## Getting Started diff --git a/packages/connectivity/example/ios/Runner/Info.plist b/packages/connectivity/example/ios/Runner/Info.plist index d76382b40acf..615657477506 100644 --- a/packages/connectivity/example/ios/Runner/Info.plist +++ b/packages/connectivity/example/ios/Runner/Info.plist @@ -22,6 +22,10 @@ 1 LSRequiresIPhoneOS + NSLocationAlwaysAndWhenInUseUsageDescription + Location service + NSLocationWhenInUseUsageDescription + locaiton service in use UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/packages/connectivity/example/ios/Runner/Runner.entitlements b/packages/connectivity/example/ios/Runner/Runner.entitlements new file mode 100644 index 000000000000..ba21fbdaf290 --- /dev/null +++ b/packages/connectivity/example/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.networking.wifi-info + + + diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index e92fff220a2e..175622ff51b5 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -7,12 +7,18 @@ #import "Reachability/Reachability.h" #import "SystemConfiguration/CaptiveNetwork.h" +#import + #include #include -@interface FLTConnectivityPlugin () +@interface FLTConnectivityPlugin () + +@property (strong, nonatomic) CLLocationManager* locationManager; +@property (copy, nonatomic) FlutterResult result; +@property (strong, nonatomic) NSString *key; @end @implementation FLTConnectivityPlugin { @@ -46,12 +52,29 @@ - (NSString*)findNetworkInfo:(NSString*)key { return info; } -- (NSString*)getWifiName { - return [self findNetworkInfo:@"SSID"]; +- (void)requetNetworkInfo:(NSString *)key result:(FlutterResult)result { + + CLAuthorizationStatus status = CLLocationManager.authorizationStatus; + switch (status) { + case kCLAuthorizationStatusNotDetermined: { + self.key = key; + self.result = result; + [self.locationManager requestAlwaysAuthorization]; + return; + } + default: { + result([self findNetworkInfo:key]); + break; + } + } +} + +- (void)getWifiNameWithResult:(FlutterResult)result { + [self requetNetworkInfo:@"SSID" result:result]; } -- (NSString*)getBSSID { - return [self findNetworkInfo:@"BSSID"]; +- (void)getBSSIDWithResult:(FlutterResult)result { + [self requetNetworkInfo:@"BSSID" result:result]; } - (NSString*)getWifiIP { @@ -106,9 +129,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { // gets more involved. So for now, this will do. result([self statusFromReachability:[Reachability reachabilityForInternetConnection]]); } else if ([call.method isEqualToString:@"wifiName"]) { - result([self getWifiName]); + [self getWifiNameWithResult:result]; } else if ([call.method isEqualToString:@"wifiBSSID"]) { - result([self getBSSID]); + [self getBSSIDWithResult:result]; } else if ([call.method isEqualToString:@"wifiIPAddress"]) { result([self getWifiIP]); } else { @@ -140,4 +163,18 @@ - (FlutterError*)onCancelWithArguments:(id)arguments { return nil; } +#pragma mark - CLLocationManagerDelegate + +- (CLLocationManager *)locationManager { + if (!_locationManager) { + _locationManager = [CLLocationManager new]; + _locationManager.delegate = self; + } + return _locationManager; +} + +- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { + self.result([self findNetworkInfo:self.key]); +} + @end From 7201c6f58a503d7a2942b899814f6bf93de51a21 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 21 Aug 2019 15:27:56 -0700 Subject: [PATCH 02/18] add a method to request location --- packages/connectivity/README.md | 6 ++ packages/connectivity/example/lib/main.dart | 25 +++++- .../ios/Classes/ConnectivityPlugin.m | 69 +++++++++------ packages/connectivity/lib/connectivity.dart | 83 +++++++++++++++++++ .../connectivity/test/connectivity_test.dart | 16 ++++ 5 files changed, 173 insertions(+), 26 deletions(-) diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index d796eb9650b2..5ba27940aeaf 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -88,6 +88,12 @@ The [CNCopyCurrentNetworkInfo] will work for Apps that: If your app falls into the last two categories, it will work as it is. If your app doesn't fall into the last two categories, and you still need to access the wifi information, you should request user's authorization to use location information. +There is a helper method provided in this plugin to request the location authorization: `requestLocationServiceAuthorizationIfUndetermined`. +To request location authorization, make sure to add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: + +* `NSLocationAlwaysAndWhenInUseUsageDescription` - describe why the app needs access to the user’s location information all the time (foreground and background). _Privacy - Location Always and When In Use Usage Description_ in the visual editor. +* `NSLocationWhenInUseUsageDescription` - describe why the app needs access to the user’s location information when the app is running in the foreground. This is called _Privacy - Location When In Use Usage Description_ in the visual editor. + ## Getting Started For help getting started with Flutter, view our online diff --git a/packages/connectivity/example/lib/main.dart b/packages/connectivity/example/lib/main.dart index 3784a22fc241..4d296726b668 100644 --- a/packages/connectivity/example/lib/main.dart +++ b/packages/connectivity/example/lib/main.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -90,14 +91,34 @@ class _MyHomePageState extends State { String wifiName, wifiBSSID, wifiIP; try { - wifiName = await _connectivity.getWifiName(); + if (Platform.isIOS) { + LocationAuthorizationStatus status = await _connectivity.requestLocationServiceAuthorizationIfUndetermined(); + if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) { + wifiBSSID = await _connectivity.getWifiName(); + } else { + print('location service is not authorized, the data might not be correct'); + wifiBSSID = await _connectivity.getWifiName(); + } + } else { + wifiBSSID = await _connectivity.getWifiName(); + } } on PlatformException catch (e) { print(e.toString()); wifiName = "Failed to get Wifi Name"; } try { - wifiBSSID = await _connectivity.getWifiBSSID(); + if (Platform.isIOS) { + LocationAuthorizationStatus status = await _connectivity.requestLocationServiceAuthorizationIfUndetermined(); + if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) { + wifiBSSID = await _connectivity.getWifiBSSID(); + } else { + print('location service is not authorized, the data might not be correct'); + wifiBSSID = await _connectivity.getWifiBSSID(); + } + } else { + wifiBSSID = await _connectivity.getWifiBSSID(); + } } on PlatformException catch (e) { print(e.toString()); wifiBSSID = "Failed to get Wifi BSSID"; diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index 175622ff51b5..cc1bffb5fd81 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -52,29 +52,12 @@ - (NSString*)findNetworkInfo:(NSString*)key { return info; } -- (void)requetNetworkInfo:(NSString *)key result:(FlutterResult)result { - - CLAuthorizationStatus status = CLLocationManager.authorizationStatus; - switch (status) { - case kCLAuthorizationStatusNotDetermined: { - self.key = key; - self.result = result; - [self.locationManager requestAlwaysAuthorization]; - return; - } - default: { - result([self findNetworkInfo:key]); - break; - } - } -} - -- (void)getWifiNameWithResult:(FlutterResult)result { - [self requetNetworkInfo:@"SSID" result:result]; +- (NSString*)getWifiName { + return [self findNetworkInfo:@"SSID"]; } -- (void)getBSSIDWithResult:(FlutterResult)result { - [self requetNetworkInfo:@"BSSID" result:result]; +- (NSString*)getBSSID { + return [self findNetworkInfo:@"BSSID"]; } - (NSString*)getWifiIP { @@ -120,6 +103,41 @@ - (NSString*)statusFromReachability:(Reachability*)reachability { } } +- (void)requestLocationServiceAuthorizationIfUndetermined:(FlutterResult)result always:(NSNumber *)always { + CLAuthorizationStatus status = CLLocationManager.authorizationStatus; + switch (status) { + case kCLAuthorizationStatusNotDetermined: { + self.result = result; + if (always.boolValue) { + [self.locationManager requestAlwaysAuthorization]; + } else { + [self.locationManager requestWhenInUseAuthorization]; + } + return; + } + case kCLAuthorizationStatusRestricted: { + result(@"restricted"); + break; + } + case kCLAuthorizationStatusDenied: { + result(@"denied"); + break; + } + case kCLAuthorizationStatusAuthorizedAlways: { + result(@"authorizedAlways"); + break; + } + case kCLAuthorizationStatusAuthorizedWhenInUse: { + result(@"authorizedWhenInUse"); + break; + } + default: { + result(@"unknown"); + break; + } + } +} + - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([call.method isEqualToString:@"check"]) { // This is supposed to be quick. Another way of doing this would be to @@ -129,11 +147,14 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { // gets more involved. So for now, this will do. result([self statusFromReachability:[Reachability reachabilityForInternetConnection]]); } else if ([call.method isEqualToString:@"wifiName"]) { - [self getWifiNameWithResult:result]; + result([self getWifiName]); } else if ([call.method isEqualToString:@"wifiBSSID"]) { - [self getBSSIDWithResult:result]; + result([self getBSSID]); } else if ([call.method isEqualToString:@"wifiIPAddress"]) { result([self getWifiIP]); + } else if ([call.method isEqualToString:@"requestLocationServiceAuthorizationIfUndetermined"]) { + NSArray *arguments = call.arguments; + [self requestLocationServiceAuthorizationIfUndetermined:result always:arguments.firstObject]; } else { result(FlutterMethodNotImplemented); } @@ -174,7 +195,7 @@ - (CLLocationManager *)locationManager { } - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { - self.result([self findNetworkInfo:self.key]); + [self requestLocationServiceAuthorizationIfUndetermined:self.result always:nil]; } @end diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index a1fd21cb1668..888ad7fe9c28 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; @@ -93,6 +94,69 @@ class Connectivity { Future getWifiIP() async { return await methodChannel.invokeMethod('wifiIPAddress'); } + + /// Request to authorize the location service. Only on iOS. + /// + /// Returns a [LocationAuthorizationStatus] if the location service has already been authorized or user authorized the location on + /// This request. + /// + /// if the location information needs to be accessible all the time, set `requestAlwaysLocationUsage` to true. Note that the status + /// returned might not be [LocationAuthorizationStatus.authorizedAlways] even you requested it. User might have already chosen a location authorization + /// to this app. + /// + /// It will show a platform standard window of requesting a location service. + /// + /// If the user declined the location service, it will never show the window to request the authorization again. + /// The user has to go to the setting app in the phone to enable authorization. + /// + /// This method is a helper to get the location authorization that is necessary for certain functionalities in this plugin. + /// It can be replaced with other permission handling code/plugin if preferred. + /// To request location authorization, make sure to add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: + /// * `NSLocationAlwaysAndWhenInUseUsageDescription` - describe why the app needs access to the user’s location information + /// all the time (foreground and background). This is called _Privacy - Location Always and When In Use Usage Description_ in the visual editor. + /// * `NSLocationWhenInUseUsageDescription` - describe why the app needs access to the user’s location information when the app is + /// running in the foreground. This is called _Privacy - Location When In Use Usage Description_ in the visual editor. + /// + /// Starting from iOS 13, `getWifiBSSID` and `getWifiIP` will only work properly if: + /// + /// * The app uses Core Location, and has the user’s authorization to use location information. + /// * The app uses the NEHotspotConfiguration API to configure the current Wi-Fi network. + /// * The app has active VPN configurations installed. + /// + /// If the app falls into the first category, call this method before calling `getWifiBSSID` or `getWifiIP`. + /// For example, + /// ```dart + /// if (Platform.isIOS) { + /// LocationAuthorizationStatus status = await _connectivity.requestLocationServiceAuthorizationIfUndetermined(); + /// if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) { + /// wifiBSSID = await _connectivity.getWifiBSSID(); + /// } else { + /// print('location service is not authorized'); + /// } + /// } + /// ``` + /// This method will throw on Android. + Future requestLocationServiceAuthorizationIfUndetermined({bool requestAlwaysLocationUsage = false}) async { + //Just `assert(Platform.isIOS)` will disable us to do dart side unit testing. + if (Platform.isAndroid) { + throw UnsupportedError( + 'The method requestLocationServiceIfUndetermined is not supported on android'); + } + final String result = await methodChannel + .invokeMethod('requestLocationServiceAuthorizationIfUndetermined', [requestAlwaysLocationUsage]); + switch (result) { + case 'restricted': + return LocationAuthorizationStatus.restricted; + case 'denied': + return LocationAuthorizationStatus.denied; + case 'authorizedAlways': + return LocationAuthorizationStatus.authorizedAlways; + case 'authorizedWhenInUse': + return LocationAuthorizationStatus.authorizedWhenInUse; + default: + return LocationAuthorizationStatus.unknown; + } + } } ConnectivityResult _parseConnectivityResult(String state) { @@ -106,3 +170,22 @@ ConnectivityResult _parseConnectivityResult(String state) { return ConnectivityResult.none; } } + +/// The status of the location service authorization. +enum LocationAuthorizationStatus { + + /// This app is not authorized to use location. + restricted, + + /// User explicitly denied the location service. + denied, + + /// User authorized the app to access the location at any time. + authorizedAlways, + + /// User authorized the app to access the location when the app is visible to them. + authorizedWhenInUse, + + /// Status unknown. + unknown +} \ No newline at end of file diff --git a/packages/connectivity/test/connectivity_test.dart b/packages/connectivity/test/connectivity_test.dart index 4159130b3190..29e59fdf2206 100644 --- a/packages/connectivity/test/connectivity_test.dart +++ b/packages/connectivity/test/connectivity_test.dart @@ -19,6 +19,8 @@ void main() { return 'c0:ff:33:c0:d3:55'; case 'wifiIPAddress': return '127.0.0.1'; + case 'requestLocationServiceAuthorizationIfUndetermined': + return 'authorizedAlways'; default: return null; } @@ -92,6 +94,20 @@ void main() { ); }); + test('requestLocationServiceAuthorizationIfUndetermined', () async { + final LocationAuthorizationStatus result = await Connectivity().requestLocationServiceAuthorizationIfUndetermined(); + expect(result, LocationAuthorizationStatus.authorizedAlways); + expect( + log, + [ + isMethodCall( + 'requestLocationServiceAuthorizationIfUndetermined', + arguments: null, + ), + ], + ); + }); + test('checkConnectivity', () async { final ConnectivityResult result = await Connectivity().checkConnectivity(); From bb199d4c3bc8833f2b1e1235622381814b97c040 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 21 Aug 2019 15:30:20 -0700 Subject: [PATCH 03/18] cleanup --- packages/connectivity/ios/Classes/ConnectivityPlugin.m | 2 +- packages/connectivity/lib/connectivity.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index cc1bffb5fd81..eecb1fa013f9 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -18,7 +18,7 @@ @interface FLTConnectivityPlugin () Date: Wed, 21 Aug 2019 15:38:41 -0700 Subject: [PATCH 04/18] update version --- packages/connectivity/CHANGELOG.md | 4 ++++ packages/connectivity/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/connectivity/CHANGELOG.md b/packages/connectivity/CHANGELOG.md index 4115a1be4aae..93d19058a2f4 100644 --- a/packages/connectivity/CHANGELOG.md +++ b/packages/connectivity/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.4 + +* Add `requestLocationServiceAuthorizationIfUndetermined` to get location information on iOS. + ## 0.4.3+7 * Update README with the updated information about CNCopyCurrentNetworkInfo on iOS 13. diff --git a/packages/connectivity/pubspec.yaml b/packages/connectivity/pubspec.yaml index b91741f2f916..7ec5d31bcc42 100644 --- a/packages/connectivity/pubspec.yaml +++ b/packages/connectivity/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for discovering the state of the network (WiFi & mobile/cellular) connectivity on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity -version: 0.4.3+7 +version: 0.4.4 flutter: plugin: From 17f69cfae06b0aaaa095abea9d99d2dbee66cf67 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 22 Aug 2019 14:45:37 -0700 Subject: [PATCH 05/18] assert --- packages/connectivity/lib/connectivity.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index 14475804bfca..d35f14a14d50 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -138,10 +138,7 @@ class Connectivity { /// This method will throw on Android. Future requestLocationServiceAuthorizationIfUndetermined({bool requestAlwaysLocationUsage = false}) async { //Just `assert(Platform.isIOS)` will disable us to do dart side unit testing. - if (Platform.isAndroid) { - throw UnsupportedError( - 'The method requestLocationServiceIfUndetermined is not supported on android'); - } + assert(!Platform.isAndroid); final String result = await methodChannel .invokeMethod('requestLocationServiceAuthorizationIfUndetermined', [requestAlwaysLocationUsage]); switch (result) { From 69ab075d76087a8f3e1350bfc6ea9c99aed0ff5b Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:32:56 -0700 Subject: [PATCH 06/18] Update packages/connectivity/README.md Co-Authored-By: Collin Jackson --- packages/connectivity/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index 5ba27940aeaf..dbebed977e36 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -62,7 +62,7 @@ var wifiName = await (Connectivity().getWifiName());wifi network ### iOS 12 -To use `.getWifiBSSID()` and `.getWifiName()` on iOS >= 12, the `Access WiFi information capability` in XCode must be enabled. Otherwise, both method will return null. +To use `.getWifiBSSID()` and `.getWifiName()` on iOS >= 12, the `Access WiFi information capability` in XCode must be enabled. Otherwise, both methods will return null. ### iOS 13 From 50a0ffbd0975bc40e7e34aacbb75ff351fb26ddc Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:33:12 -0700 Subject: [PATCH 07/18] Update packages/connectivity/README.md Co-Authored-By: Collin Jackson --- packages/connectivity/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index dbebed977e36..85a9c40e0366 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -75,7 +75,7 @@ An App linked against iOS 12 or earlier receives pseudo-values such as: * BSSID: "00:00:00:00:00:00" -An App linked against iOS 13 or later receives null. +An app linked against iOS 13 or later receives `null`. The [CNCopyCurrentNetworkInfo] will work for Apps that: From 169c5e6a70ce124ea6fe32acaac9370dbf609599 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:33:19 -0700 Subject: [PATCH 08/18] Update packages/connectivity/README.md Co-Authored-By: Collin Jackson --- packages/connectivity/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index 85a9c40e0366..f9f4e61af2cb 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -69,7 +69,7 @@ To use `.getWifiBSSID()` and `.getWifiName()` on iOS >= 12, the `Access WiFi inf The methods `.getWifiBSSID()` and `.getWifiName()` utilize the [CNCopyCurrentNetworkInfo](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo) function on iOS. As of iOS 13, Apple announced that these APIs will no longer return valid information. -An App linked against iOS 12 or earlier receives pseudo-values such as: +An app linked against iOS 12 or earlier receives pseudo-values such as: * SSID: "Wi-Fi" or "WLAN" ("WLAN" will be returned for the China SKU). From 782c472b4dbcfcffabdcdbf1ef976fd9c2202ea0 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:33:30 -0700 Subject: [PATCH 09/18] Update packages/connectivity/README.md Co-Authored-By: Collin Jackson --- packages/connectivity/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index f9f4e61af2cb..e6654490a791 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -77,7 +77,7 @@ An app linked against iOS 12 or earlier receives pseudo-values such as: An app linked against iOS 13 or later receives `null`. -The [CNCopyCurrentNetworkInfo] will work for Apps that: +The `CNCopyCurrentNetworkInfo` will work for Apps that: * The app uses Core Location, and has the user’s authorization to use location information. From 0634d62f8c15c05d82bc14250dd902a787cb0f3e Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:36:38 -0700 Subject: [PATCH 10/18] Update packages/connectivity/lib/connectivity.dart Co-Authored-By: Collin Jackson --- packages/connectivity/lib/connectivity.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index d35f14a14d50..c4ac6ad20407 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -107,7 +107,7 @@ class Connectivity { /// It will show a platform standard window of requesting a location service. /// /// If the user declined the location service, it will never show the window to request the authorization again. - /// The user has to go to the setting app in the phone to enable authorization. + /// The user has to go to the settings app in the phone to enable authorization. /// /// This method is a helper to get the location authorization that is necessary for certain functionalities in this plugin. /// It can be replaced with other permission handling code/plugin if preferred. From c7666d64d63eb7a3ca08d76566659062d325caab Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:36:55 -0700 Subject: [PATCH 11/18] Update packages/connectivity/lib/connectivity.dart Co-Authored-By: Collin Jackson --- packages/connectivity/lib/connectivity.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index c4ac6ad20407..3c5525517181 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -109,7 +109,7 @@ class Connectivity { /// If the user declined the location service, it will never show the window to request the authorization again. /// The user has to go to the settings app in the phone to enable authorization. /// - /// This method is a helper to get the location authorization that is necessary for certain functionalities in this plugin. + /// This method is a helper to get the location authorization that is necessary for certain functionality of this plugin. /// It can be replaced with other permission handling code/plugin if preferred. /// To request location authorization, make sure to add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: /// * `NSLocationAlwaysAndWhenInUseUsageDescription` - describe why the app needs access to the user’s location information From 3a68b159083aa65c8ddb0b464665d553e6a2e91c Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:37:11 -0700 Subject: [PATCH 12/18] Update packages/connectivity/lib/connectivity.dart Co-Authored-By: Collin Jackson --- packages/connectivity/lib/connectivity.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index 3c5525517181..76637c6003dd 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -101,7 +101,7 @@ class Connectivity { /// This request. /// /// if the location information needs to be accessible all the time, set `requestAlwaysLocationUsage` to true. Note that the status - /// returned might not be [LocationAuthorizationStatus.authorizedAlways] even you requested it. User might have already chosen a location authorization + /// returned might not be [LocationAuthorizationStatus.authorizedAlways] even if you requested it. The user might have already chosen a location authorization /// to this app. /// /// It will show a platform standard window of requesting a location service. From ef2dcfb78bcff2d1b9beabcab3c8298449806811 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:37:22 -0700 Subject: [PATCH 13/18] Update packages/connectivity/lib/connectivity.dart Co-Authored-By: Collin Jackson --- packages/connectivity/lib/connectivity.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index 76637c6003dd..d0cbbb68aeb2 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -98,7 +98,7 @@ class Connectivity { /// Request to authorize the location service. Only on iOS. /// /// Returns a [LocationAuthorizationStatus] if the location service has already been authorized or user authorized the location on - /// This request. + /// this request. /// /// if the location information needs to be accessible all the time, set `requestAlwaysLocationUsage` to true. Note that the status /// returned might not be [LocationAuthorizationStatus.authorizedAlways] even if you requested it. The user might have already chosen a location authorization From c0d9783eaed92a900917617f81e802c1089cc0ed Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 13:37:30 -0700 Subject: [PATCH 14/18] Update packages/connectivity/README.md Co-Authored-By: Collin Jackson --- packages/connectivity/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index e6654490a791..27f84707d5e3 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -66,7 +66,7 @@ To use `.getWifiBSSID()` and `.getWifiName()` on iOS >= 12, the `Access WiFi inf ### iOS 13 -The methods `.getWifiBSSID()` and `.getWifiName()` utilize the [CNCopyCurrentNetworkInfo](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo) function on iOS. +The methods `.getWifiBSSID()` and `.getWifiName()` utilize the [`CNCopyCurrentNetworkInfo`](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo) function on iOS. As of iOS 13, Apple announced that these APIs will no longer return valid information. An app linked against iOS 12 or earlier receives pseudo-values such as: From 26b2874a314e33f112efa90f6508407c07f4fd18 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 17:04:27 -0700 Subject: [PATCH 15/18] review fixes --- packages/connectivity/CHANGELOG.md | 4 +- packages/connectivity/README.md | 2 +- .../example/ios/Runner/Info.plist | 4 +- packages/connectivity/example/lib/main.dart | 32 ++++-- .../ios/Classes/ConnectivityPlugin.m | 100 ++++++++---------- .../Classes/FLTConnectivityLocationHandler.h | 22 ++++ .../Classes/FLTConnectivityLocationHandler.m | 58 ++++++++++ packages/connectivity/lib/connectivity.dart | 89 +++++++++++++--- .../connectivity/test/connectivity_test.dart | 26 ++++- 9 files changed, 250 insertions(+), 87 deletions(-) create mode 100644 packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.h create mode 100644 packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.m diff --git a/packages/connectivity/CHANGELOG.md b/packages/connectivity/CHANGELOG.md index 93d19058a2f4..e88bc2b2d4df 100644 --- a/packages/connectivity/CHANGELOG.md +++ b/packages/connectivity/CHANGELOG.md @@ -1,6 +1,8 @@ ## 0.4.4 -* Add `requestLocationServiceAuthorizationIfUndetermined` to get location information on iOS. +* Add `requestLocationServiceAuthorization` to request location authorization on iOS. +* Add `getLocationServiceAuthorization` to get location authorization status on iOS. +* Update README: add more information on iOS 13 updates with CNCopyCurrentNetworkInfo. ## 0.4.3+7 diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index 27f84707d5e3..cba575c9dabb 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -88,7 +88,7 @@ The `CNCopyCurrentNetworkInfo` will work for Apps that: If your app falls into the last two categories, it will work as it is. If your app doesn't fall into the last two categories, and you still need to access the wifi information, you should request user's authorization to use location information. -There is a helper method provided in this plugin to request the location authorization: `requestLocationServiceAuthorizationIfUndetermined`. +There is a helper method provided in this plugin to request the location authorization: `requestLocationServiceAuthorization`. To request location authorization, make sure to add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: * `NSLocationAlwaysAndWhenInUseUsageDescription` - describe why the app needs access to the user’s location information all the time (foreground and background). _Privacy - Location Always and When In Use Usage Description_ in the visual editor. diff --git a/packages/connectivity/example/ios/Runner/Info.plist b/packages/connectivity/example/ios/Runner/Info.plist index 615657477506..c9da2979cc2a 100644 --- a/packages/connectivity/example/ios/Runner/Info.plist +++ b/packages/connectivity/example/ios/Runner/Info.plist @@ -23,9 +23,9 @@ LSRequiresIPhoneOS NSLocationAlwaysAndWhenInUseUsageDescription - Location service + This app requires accessing your location information all the time to get WIFI information. NSLocationWhenInUseUsageDescription - locaiton service in use + This app requires accessing your location information when the app is in foreground to get WIFI information. UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/packages/connectivity/example/lib/main.dart b/packages/connectivity/example/lib/main.dart index 4d296726b668..6b280523a025 100644 --- a/packages/connectivity/example/lib/main.dart +++ b/packages/connectivity/example/lib/main.dart @@ -92,15 +92,22 @@ class _MyHomePageState extends State { try { if (Platform.isIOS) { - LocationAuthorizationStatus status = await _connectivity.requestLocationServiceAuthorizationIfUndetermined(); - if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) { - wifiBSSID = await _connectivity.getWifiName(); + LocationAuthorizationStatus status = + await _connectivity.getLocationServiceAuthorization(); + if (status == LocationAuthorizationStatus.notDetermined) { + status = + await _connectivity.requestLocationServiceAuthorization(); + } + if (status == LocationAuthorizationStatus.authorizedAlways || + status == LocationAuthorizationStatus.authorizedWhenInUse) { + wifiName = await _connectivity.getWifiName(); } else { - print('location service is not authorized, the data might not be correct'); - wifiBSSID = await _connectivity.getWifiName(); + print( + 'location service is not authorized, wifiName might not be correct'); + wifiName = await _connectivity.getWifiName(); } } else { - wifiBSSID = await _connectivity.getWifiName(); + wifiName = await _connectivity.getWifiName(); } } on PlatformException catch (e) { print(e.toString()); @@ -109,11 +116,18 @@ class _MyHomePageState extends State { try { if (Platform.isIOS) { - LocationAuthorizationStatus status = await _connectivity.requestLocationServiceAuthorizationIfUndetermined(); - if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) { + LocationAuthorizationStatus status = + await _connectivity.getLocationServiceAuthorization(); + if (status == LocationAuthorizationStatus.notDetermined) { + status = + await _connectivity.requestLocationServiceAuthorization(); + } + if (status == LocationAuthorizationStatus.authorizedAlways || + status == LocationAuthorizationStatus.authorizedWhenInUse) { wifiBSSID = await _connectivity.getWifiBSSID(); } else { - print('location service is not authorized, the data might not be correct'); + print( + 'location service is not authorized, WIFIBSSID might not be correct'); wifiBSSID = await _connectivity.getWifiBSSID(); } } else { diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index eecb1fa013f9..27a3c9bac954 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -6,9 +6,9 @@ #import "Reachability/Reachability.h" -#import "SystemConfiguration/CaptiveNetwork.h" #import - +#import "FLTConnectivityLocationHandler.h" +#import "SystemConfiguration/CaptiveNetwork.h" #include @@ -16,8 +16,8 @@ @interface FLTConnectivityPlugin () -@property (strong, nonatomic) CLLocationManager* locationManager; -@property (copy, nonatomic) FlutterResult result; +@property(copy, nonatomic) FlutterResult result; +@property(strong, nonatomic) FLTConnectivityLocationHandler* locationHandler; @end @@ -103,41 +103,6 @@ - (NSString*)statusFromReachability:(Reachability*)reachability { } } -- (void)requestLocationServiceAuthorizationIfUndetermined:(FlutterResult)result always:(NSNumber *)always { - CLAuthorizationStatus status = CLLocationManager.authorizationStatus; - switch (status) { - case kCLAuthorizationStatusNotDetermined: { - self.result = result; - if (always.boolValue) { - [self.locationManager requestAlwaysAuthorization]; - } else { - [self.locationManager requestWhenInUseAuthorization]; - } - return; - } - case kCLAuthorizationStatusRestricted: { - result(@"restricted"); - break; - } - case kCLAuthorizationStatusDenied: { - result(@"denied"); - break; - } - case kCLAuthorizationStatusAuthorizedAlways: { - result(@"authorizedAlways"); - break; - } - case kCLAuthorizationStatusAuthorizedWhenInUse: { - result(@"authorizedWhenInUse"); - break; - } - default: { - result(@"unknown"); - break; - } - } -} - - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([call.method isEqualToString:@"check"]) { // This is supposed to be quick. Another way of doing this would be to @@ -152,9 +117,18 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { result([self getBSSID]); } else if ([call.method isEqualToString:@"wifiIPAddress"]) { result([self getWifiIP]); - } else if ([call.method isEqualToString:@"requestLocationServiceAuthorizationIfUndetermined"]) { - NSArray *arguments = call.arguments; - [self requestLocationServiceAuthorizationIfUndetermined:result always:arguments.firstObject]; + } else if ([call.method isEqualToString:@"getLocationServiceAuthorization"]) { + result([self convertCLAuthorizationStatusToString:[FLTConnectivityLocationHandler + locationAuthorizationStatus]]); + } else if ([call.method isEqualToString:@"requestLocationServiceAuthorization"]) { + NSArray* arguments = call.arguments; + BOOL always = [arguments.firstObject boolValue]; + __weak typeof(self) weakSelf = self; + [self.locationHandler + requestLocationAuthorization:always + completion:^(CLAuthorizationStatus status) { + result([weakSelf convertCLAuthorizationStatusToString:status]); + }]; } else { result(FlutterMethodNotImplemented); } @@ -165,6 +139,34 @@ - (void)onReachabilityDidChange:(NSNotification*)notification { _eventSink([self statusFromReachability:curReach]); } +- (NSString*)convertCLAuthorizationStatusToString:(CLAuthorizationStatus)status { + switch (status) { + case kCLAuthorizationStatusNotDetermined: { + return @"notDetermined"; + } + case kCLAuthorizationStatusRestricted: { + return @"restricted"; + } + case kCLAuthorizationStatusDenied: { + return @"denied"; + } + case kCLAuthorizationStatusAuthorizedAlways: { + return @"authorizedAlways"; + } + case kCLAuthorizationStatusAuthorizedWhenInUse: { + return @"authorizedWhenInUse"; + } + default: { return @"unknown"; } + } +} + +- (FLTConnectivityLocationHandler*)locationHandler { + if (!_locationHandler) { + _locationHandler = [FLTConnectivityLocationHandler new]; + } + return _locationHandler; +} + #pragma mark FlutterStreamHandler impl - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { @@ -184,18 +186,4 @@ - (FlutterError*)onCancelWithArguments:(id)arguments { return nil; } -#pragma mark - CLLocationManagerDelegate - -- (CLLocationManager *)locationManager { - if (!_locationManager) { - _locationManager = [CLLocationManager new]; - _locationManager.delegate = self; - } - return _locationManager; -} - -- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { - [self requestLocationServiceAuthorizationIfUndetermined:self.result always:nil]; -} - @end diff --git a/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.h b/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.h new file mode 100644 index 000000000000..1731d56fe782 --- /dev/null +++ b/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.h @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FLTConnectivityLocationDelegate; + +typedef void (^FLTConnectivityLocationCompletion)(CLAuthorizationStatus); + +@interface FLTConnectivityLocationHandler : NSObject + ++ (CLAuthorizationStatus)locationAuthorizationStatus; + +- (void)requestLocationAuthorization:(BOOL)always + completion:(_Nonnull FLTConnectivityLocationCompletion)completionHnadler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.m b/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.m new file mode 100644 index 000000000000..d4547aa78f5c --- /dev/null +++ b/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.m @@ -0,0 +1,58 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FLTConnectivityLocationHandler.h" + +@interface FLTConnectivityLocationHandler () + +@property(copy, nonatomic) FLTConnectivityLocationCompletion completion; +@property(strong, nonatomic) CLLocationManager *locationManager; + +@end + +@implementation FLTConnectivityLocationHandler + ++ (CLAuthorizationStatus)locationAuthorizationStatus { + return CLLocationManager.authorizationStatus; +} + +- (void)requestLocationAuthorization:(BOOL)always + completion:(FLTConnectivityLocationCompletion)completionHandler { + CLAuthorizationStatus status = CLLocationManager.authorizationStatus; + if (status != kCLAuthorizationStatusAuthorizedWhenInUse && always) { + completionHandler(kCLAuthorizationStatusDenied); + return; + } else if (status != kCLAuthorizationStatusNotDetermined) { + completionHandler(status); + return; + } + + if (self.completion) { + // If a request is still in process, immdediately return. + completionHandler(kCLAuthorizationStatusNotDetermined); + return; + } + + self.completion = completionHandler; + self.locationManager = [CLLocationManager new]; + self.locationManager.delegate = self; + if (always) { + [self.locationManager requestAlwaysAuthorization]; + } else { + [self.locationManager requestWhenInUseAuthorization]; + } +} + +- (void)locationManager:(CLLocationManager *)manager + didChangeAuthorizationStatus:(CLAuthorizationStatus)status { + if (status == kCLAuthorizationStatusNotDetermined) { + return; + } + if (self.completion) { + self.completion(status); + self.completion = nil; + } +} + +@end diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index d0cbbb68aeb2..1ef923b264cb 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -97,17 +97,15 @@ class Connectivity { /// Request to authorize the location service. Only on iOS. /// - /// Returns a [LocationAuthorizationStatus] if the location service has already been authorized or user authorized the location on - /// this request. + /// This method will throw a [PlatformException] on Android. /// - /// if the location information needs to be accessible all the time, set `requestAlwaysLocationUsage` to true. Note that the status - /// returned might not be [LocationAuthorizationStatus.authorizedAlways] even if you requested it. The user might have already chosen a location authorization - /// to this app. + /// Returns a [LocationAuthorizationStatus] after user authorized or denied the location on this request. /// - /// It will show a platform standard window of requesting a location service. + /// if the location information needs to be accessible all the time, set `requestAlwaysLocationUsage` to true. If user has + /// already granted a [LocationAuthorizationStatus.authorizedWhenInUse] prior to requesting an "always" access, it will return [LocationAuthorizationStatus.denied]. /// - /// If the user declined the location service, it will never show the window to request the authorization again. - /// The user has to go to the settings app in the phone to enable authorization. + /// If the location service authorization is not determined prior to making this call, a platform standard UI of requesting a location service will pop up. + /// This UI will only show once unless the user re-install the app to their phone which resets the location service authorization to not determined. /// /// This method is a helper to get the location authorization that is necessary for certain functionality of this plugin. /// It can be replaced with other permission handling code/plugin if preferred. @@ -127,21 +125,82 @@ class Connectivity { /// For example, /// ```dart /// if (Platform.isIOS) { - /// LocationAuthorizationStatus status = await _connectivity.requestLocationServiceAuthorizationIfUndetermined(); + /// LocationAuthorizationStatus status = await _connectivity.getLocationServiceAuthorization(); + /// if (status == LocationAuthorizationStatus.notDetermined) { + /// status = await _connectivity.requestLocationServiceAuthorization(); + /// } /// if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) { - /// wifiBSSID = await _connectivity.getWifiBSSID(); + /// wifiBSSID = await _connectivity.getWifiName(); /// } else { - /// print('location service is not authorized'); + /// print('location service is not authorized, the data might not be correct'); + /// wifiBSSID = await _connectivity.getWifiName(); /// } + /// } else { + /// wifiBSSID = await _connectivity.getWifiName(); /// } /// ``` - /// This method will throw on Android. - Future requestLocationServiceAuthorizationIfUndetermined({bool requestAlwaysLocationUsage = false}) async { + /// + /// Ideally, a location service authorization should only be requested if the current authorization status is not determined. + /// + /// See also [getLocationServiceAuthorization] to obtain current location service status. + Future requestLocationServiceAuthorization( + {bool requestAlwaysLocationUsage = false}) async { + //Just `assert(Platform.isIOS)` will disable us to do dart side unit testing. + assert(!Platform.isAndroid); + final String result = await methodChannel.invokeMethod( + 'requestLocationServiceAuthorization', + [requestAlwaysLocationUsage]); + return _convertLocationStatusString(result); + } + + /// Get the current location service authorization. Only on iOS. + /// + /// This method will throw a [PlatformException] on Android. + /// + /// Returns a [LocationAuthorizationStatus]. + /// If the returned value is [LocationAuthorizationStatus.notDetermined], a subsequent [requestLocationServiceAuthorization] call + /// can request the authorization. + /// If the returned value is not [LocationAuthorizationStatus.notDetermined], a subsequent [requestLocationServiceAuthorization] + /// will not initiate another request. It will instead return the "determined" status. + /// + /// This method is a helper to get the location authorization that is necessary for certain functionality of this plugin. + /// It can be replaced with other permission handling code/plugin if preferred. + /// + /// Starting from iOS 13, `getWifiBSSID` and `getWifiIP` will only work properly if: + /// + /// * The app uses Core Location, and has the user’s authorization to use location information. + /// * The app uses the NEHotspotConfiguration API to configure the current Wi-Fi network. + /// * The app has active VPN configurations installed. + /// + /// If the app falls into the first category, call this method before calling `getWifiBSSID` or `getWifiIP`. + /// For example, + /// ```dart + /// if (Platform.isIOS) { + /// LocationAuthorizationStatus status = await _connectivity.getLocationServiceAuthorization(); + /// if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) { + /// wifiBSSID = await _connectivity.getWifiName(); + /// } else { + /// print('location service is not authorized, the data might not be correct'); + /// wifiBSSID = await _connectivity.getWifiName(); + /// } + /// } else { + /// wifiBSSID = await _connectivity.getWifiName(); + /// } + /// ``` + /// + /// See also [requestLocationServiceAuthorization] for requesting a location service authorization. + Future getLocationServiceAuthorization() async { //Just `assert(Platform.isIOS)` will disable us to do dart side unit testing. assert(!Platform.isAndroid); final String result = await methodChannel - .invokeMethod('requestLocationServiceAuthorizationIfUndetermined', [requestAlwaysLocationUsage]); + .invokeMethod('getLocationServiceAuthorization'); + return _convertLocationStatusString(result); + } + + LocationAuthorizationStatus _convertLocationStatusString(String result) { switch (result) { + case 'notDetermined': + return LocationAuthorizationStatus.notDetermined; case 'restricted': return LocationAuthorizationStatus.restricted; case 'denied': @@ -170,6 +229,8 @@ ConnectivityResult _parseConnectivityResult(String state) { /// The status of the location service authorization. enum LocationAuthorizationStatus { + /// The authorization of the location service is not determined. + notDetermined, /// This app is not authorized to use location. restricted, diff --git a/packages/connectivity/test/connectivity_test.dart b/packages/connectivity/test/connectivity_test.dart index 29e59fdf2206..5980a0829de7 100644 --- a/packages/connectivity/test/connectivity_test.dart +++ b/packages/connectivity/test/connectivity_test.dart @@ -19,7 +19,9 @@ void main() { return 'c0:ff:33:c0:d3:55'; case 'wifiIPAddress': return '127.0.0.1'; - case 'requestLocationServiceAuthorizationIfUndetermined': + case 'requestLocationServiceAuthorization': + return 'authorizedAlways'; + case 'getLocationServiceAuthorization': return 'authorizedAlways'; default: return null; @@ -94,14 +96,30 @@ void main() { ); }); - test('requestLocationServiceAuthorizationIfUndetermined', () async { - final LocationAuthorizationStatus result = await Connectivity().requestLocationServiceAuthorizationIfUndetermined(); + test('requestLocationServiceAuthorization', () async { + final LocationAuthorizationStatus result = + await Connectivity().requestLocationServiceAuthorization(); + expect(result, LocationAuthorizationStatus.authorizedAlways); + expect( + log, + [ + isMethodCall( + 'requestLocationServiceAuthorization', + arguments: null, + ), + ], + ); + }); + + test('getLocationServiceAuthorization', () async { + final LocationAuthorizationStatus result = + await Connectivity().getLocationServiceAuthorization(); expect(result, LocationAuthorizationStatus.authorizedAlways); expect( log, [ isMethodCall( - 'requestLocationServiceAuthorizationIfUndetermined', + 'getLocationServiceAuthorization', arguments: null, ), ], From 666821bf488d805d2d0a81424c660a238ed80fff Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Mon, 26 Aug 2019 17:06:11 -0700 Subject: [PATCH 16/18] remove unnecesary result property --- packages/connectivity/ios/Classes/ConnectivityPlugin.m | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index 27a3c9bac954..c69871175b01 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -16,7 +16,6 @@ @interface FLTConnectivityPlugin () -@property(copy, nonatomic) FlutterResult result; @property(strong, nonatomic) FLTConnectivityLocationHandler* locationHandler; @end From e577181c2da4bf17a2014e7f349080cfadfd5b7c Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 28 Aug 2019 14:49:08 -0700 Subject: [PATCH 17/18] review fixes and add driver test for --- packages/connectivity/README.md | 4 ++-- packages/connectivity/example/ios/Runner/Info.plist | 4 ++-- packages/connectivity/example/lib/main.dart | 4 ---- .../connectivity/example/test_driver/connectivity.dart | 9 +++++++++ .../ios/Classes/FLTConnectivityLocationHandler.m | 2 +- packages/connectivity/lib/connectivity.dart | 10 +++++----- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index cba575c9dabb..c26def4e8ea4 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -50,7 +50,7 @@ dispose() { } ``` -You can get WIFI related information using: +You can get wi-fi related information using: ```dart import 'package:connectivity/connectivity.dart'; @@ -91,7 +91,7 @@ and you still need to access the wifi information, you should request user's aut There is a helper method provided in this plugin to request the location authorization: `requestLocationServiceAuthorization`. To request location authorization, make sure to add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: -* `NSLocationAlwaysAndWhenInUseUsageDescription` - describe why the app needs access to the user’s location information all the time (foreground and background). _Privacy - Location Always and When In Use Usage Description_ in the visual editor. +* `NSLocationAlwaysAndWhenInUseUsageDescription` - describe why the app needs access to the user’s location information all the time (foreground and background). This is called _Privacy - Location Always and When In Use Usage Description_ in the visual editor. * `NSLocationWhenInUseUsageDescription` - describe why the app needs access to the user’s location information when the app is running in the foreground. This is called _Privacy - Location When In Use Usage Description_ in the visual editor. ## Getting Started diff --git a/packages/connectivity/example/ios/Runner/Info.plist b/packages/connectivity/example/ios/Runner/Info.plist index c9da2979cc2a..babbd80f1619 100644 --- a/packages/connectivity/example/ios/Runner/Info.plist +++ b/packages/connectivity/example/ios/Runner/Info.plist @@ -23,9 +23,9 @@ LSRequiresIPhoneOS NSLocationAlwaysAndWhenInUseUsageDescription - This app requires accessing your location information all the time to get WIFI information. + This app requires accessing your location information all the time to get wi-fi information. NSLocationWhenInUseUsageDescription - This app requires accessing your location information when the app is in foreground to get WIFI information. + This app requires accessing your location information when the app is in foreground to get wi-fi information. UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/packages/connectivity/example/lib/main.dart b/packages/connectivity/example/lib/main.dart index 6b280523a025..c01a110efb60 100644 --- a/packages/connectivity/example/lib/main.dart +++ b/packages/connectivity/example/lib/main.dart @@ -102,8 +102,6 @@ class _MyHomePageState extends State { status == LocationAuthorizationStatus.authorizedWhenInUse) { wifiName = await _connectivity.getWifiName(); } else { - print( - 'location service is not authorized, wifiName might not be correct'); wifiName = await _connectivity.getWifiName(); } } else { @@ -126,8 +124,6 @@ class _MyHomePageState extends State { status == LocationAuthorizationStatus.authorizedWhenInUse) { wifiBSSID = await _connectivity.getWifiBSSID(); } else { - print( - 'location service is not authorized, WIFIBSSID might not be correct'); wifiBSSID = await _connectivity.getWifiBSSID(); } } else { diff --git a/packages/connectivity/example/test_driver/connectivity.dart b/packages/connectivity/example/test_driver/connectivity.dart index 7efba5e4ab50..685f69efb1c8 100644 --- a/packages/connectivity/example/test_driver/connectivity.dart +++ b/packages/connectivity/example/test_driver/connectivity.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:connectivity/connectivity.dart'; @@ -28,5 +29,13 @@ void main() { break; } }); + + test('test location methods, iOS only', () async { + print(Platform.isIOS); + if (Platform.isIOS) { + expect((await _connectivity.getLocationServiceAuthorization()), + LocationAuthorizationStatus.notDetermined); + } + }); }); } diff --git a/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.m b/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.m index d4547aa78f5c..bbb93aea6a5b 100644 --- a/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.m +++ b/packages/connectivity/ios/Classes/FLTConnectivityLocationHandler.m @@ -29,7 +29,7 @@ - (void)requestLocationAuthorization:(BOOL)always } if (self.completion) { - // If a request is still in process, immdediately return. + // If a request is still in process, immediately return. completionHandler(kCLAuthorizationStatusNotDetermined); return; } diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index 1ef923b264cb..03659f5455a9 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -95,13 +95,13 @@ class Connectivity { return await methodChannel.invokeMethod('wifiIPAddress'); } - /// Request to authorize the location service. Only on iOS. + /// Request to authorize the location service (Only on iOS). /// /// This method will throw a [PlatformException] on Android. /// /// Returns a [LocationAuthorizationStatus] after user authorized or denied the location on this request. /// - /// if the location information needs to be accessible all the time, set `requestAlwaysLocationUsage` to true. If user has + /// If the location information needs to be accessible all the time, set `requestAlwaysLocationUsage` to true. If user has /// already granted a [LocationAuthorizationStatus.authorizedWhenInUse] prior to requesting an "always" access, it will return [LocationAuthorizationStatus.denied]. /// /// If the location service authorization is not determined prior to making this call, a platform standard UI of requesting a location service will pop up. @@ -145,7 +145,7 @@ class Connectivity { /// See also [getLocationServiceAuthorization] to obtain current location service status. Future requestLocationServiceAuthorization( {bool requestAlwaysLocationUsage = false}) async { - //Just `assert(Platform.isIOS)` will disable us to do dart side unit testing. + //Just `assert(Platform.isIOS)` will prevent us from doing dart side unit testing. assert(!Platform.isAndroid); final String result = await methodChannel.invokeMethod( 'requestLocationServiceAuthorization', @@ -153,7 +153,7 @@ class Connectivity { return _convertLocationStatusString(result); } - /// Get the current location service authorization. Only on iOS. + /// Get the current location service authorization (Only on iOS). /// /// This method will throw a [PlatformException] on Android. /// @@ -190,7 +190,7 @@ class Connectivity { /// /// See also [requestLocationServiceAuthorization] for requesting a location service authorization. Future getLocationServiceAuthorization() async { - //Just `assert(Platform.isIOS)` will disable us to do dart side unit testing. + //Just `assert(Platform.isIOS)` will prevent us from doing dart side unit testing. assert(!Platform.isAndroid); final String result = await methodChannel .invokeMethod('getLocationServiceAuthorization'); From 12efe99bc0bccd60fc6c57184a94a82a1c5da981 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 29 Aug 2019 13:49:51 -0700 Subject: [PATCH 18/18] fix tests --- packages/connectivity/test/connectivity_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connectivity/test/connectivity_test.dart b/packages/connectivity/test/connectivity_test.dart index 5980a0829de7..d30dee55dfc8 100644 --- a/packages/connectivity/test/connectivity_test.dart +++ b/packages/connectivity/test/connectivity_test.dart @@ -105,7 +105,7 @@ void main() { [ isMethodCall( 'requestLocationServiceAuthorization', - arguments: null, + arguments: [false], ), ], );