Commit 74ffdfbe by Jeroen Weener Committed by GitHub

Allow querying Bluetooth service status on iOS (Apple) (#1100)

* Fix querying of Bluetooth service status

Makes `checkServiceStatus` async.
Keeps track of `PermissionStrategy` in `PermissionManager`.
Updates example app's displayed permissions.

* Fix memory leak

* Update `BluetoothPermissionStrategy.m`
parent 4c7bb448
## 9.1.4
* Adds checking whether Bluetooth service is enabled through `Permission.bluetooth.serviceStatus`.
## 9.1.3 ## 9.1.3
* Fixes an issue where the `Permission.location.request()`, `Permission.locationWhenInUse.request()` and `Permission.locationAlways.request()` calls returned `PermissionStatus.denied` regardless of the actual permission status. * Fixes an issue where the `Permission.location.request()`, `Permission.locationWhenInUse.request()` and `Permission.locationAlways.request()` calls returned `PermissionStatus.denied` regardless of the actual permission status.
......
...@@ -36,8 +36,8 @@ class _PermissionHandlerWidgetState extends State<PermissionHandlerWidget> { ...@@ -36,8 +36,8 @@ class _PermissionHandlerWidgetState extends State<PermissionHandlerWidget> {
children: Permission.values children: Permission.values
.where((permission) { .where((permission) {
return permission != Permission.unknown && return permission != Permission.unknown &&
permission != Permission.phone &&
permission != Permission.sms && permission != Permission.sms &&
permission != Permission.storage &&
permission != Permission.ignoreBatteryOptimizations && permission != Permission.ignoreBatteryOptimizations &&
permission != Permission.accessMediaLocation && permission != Permission.accessMediaLocation &&
permission != Permission.activityRecognition && permission != Permission.activityRecognition &&
...@@ -47,7 +47,12 @@ class _PermissionHandlerWidgetState extends State<PermissionHandlerWidget> { ...@@ -47,7 +47,12 @@ class _PermissionHandlerWidgetState extends State<PermissionHandlerWidget> {
permission != Permission.accessNotificationPolicy && permission != Permission.accessNotificationPolicy &&
permission != Permission.bluetoothScan && permission != Permission.bluetoothScan &&
permission != Permission.bluetoothAdvertise && permission != Permission.bluetoothAdvertise &&
permission != Permission.bluetoothConnect; permission != Permission.bluetoothConnect &&
permission != Permission.nearbyWifiDevices &&
permission != Permission.videos &&
permission != Permission.audio &&
permission != Permission.scheduleExactAlarm &&
permission != Permission.sensorsAlways;
}) })
.map((permission) => PermissionWidget(permission)) .map((permission) => PermissionWidget(permission))
.toList()), .toList()),
......
...@@ -26,9 +26,17 @@ ...@@ -26,9 +26,17 @@
} }
+ (void)checkServiceStatus:(enum PermissionGroup)permission result:(FlutterResult)result { + (void)checkServiceStatus:(enum PermissionGroup)permission result:(FlutterResult)result {
id <PermissionStrategy> permissionStrategy = [PermissionManager createPermissionStrategy:permission]; __block id <PermissionStrategy> permissionStrategy = [PermissionManager createPermissionStrategy:permission];
ServiceStatus status = [permissionStrategy checkServiceStatus:permission];
result([Codec encodeServiceStatus:status]); [permissionStrategy checkServiceStatus:permission completionHandler:^(ServiceStatus serviceStatus) {
result([Codec encodeServiceStatus:serviceStatus]);
// Make sure `result` is called before cleaning up the reference
// otherwise the `result` block is also dereferenced on iOS 12 and
// below (this is most likely a bug in Objective-C which is solved in the
// later versions of the runtime).
permissionStrategy = nil;
}];
} }
- (void)requestPermissions:(NSArray *)permissions completion:(PermissionRequestCompletion)completion { - (void)requestPermissions:(NSArray *)permissions completion:(PermissionRequestCompletion)completion {
......
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
return PermissionStatusGranted; return PermissionStatusGranted;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
return PermissionStatusDenied; return PermissionStatusDenied;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -11,8 +11,9 @@ ...@@ -11,8 +11,9 @@
@implementation BluetoothPermissionStrategy { @implementation BluetoothPermissionStrategy {
CBCentralManager *_centralManager; CBCentralManager *_centralManager;
PermissionStatusHandler _permissionStatusHandler;
PermissionGroup _requestedPermission; PermissionGroup _requestedPermission;
PermissionStatusHandler _permissionStatusHandler;
ServiceStatusHandler _serviceStatusHandler;
} }
- (void)initManagerIfNeeded { - (void)initManagerIfNeeded {
...@@ -22,24 +23,51 @@ ...@@ -22,24 +23,51 @@
} }
} }
/// Reads the permission status of the Bluetooth service.
///
/// The behavior differs across iOS versions:
/// - Starting with iOS 13.1, this function returns the expected result.
/// - On iOS 13.0, applications are unable to check for the permission status without triggering a
/// permission request. Therefore, `PermissionStatusDenied` is always returned. To obtain the
/// actual permission status, the application should request permission using `requestPermission()`.
/// If the permission was already granted, no dialog will pop up.
/// - Below iOS 13.0, applications do not have to ask for permission to use Bluetooth. Therefore,
/// `PermissionStatusGranted` is always returned.
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission { - (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission {
[self initManagerIfNeeded];
if (@available(iOS 13.1, *)) { if (@available(iOS 13.1, *)) {
CBManagerAuthorization blePermission = [_centralManager authorization]; CBManagerAuthorization blePermission = [CBCentralManager authorization];
return [BluetoothPermissionStrategy parsePermission:blePermission];
} else if (@available(iOS 13.0, *)){
CBManagerAuthorization blePermission = [_centralManager authorization];
return [BluetoothPermissionStrategy parsePermission:blePermission]; return [BluetoothPermissionStrategy parsePermission:blePermission];
} else if (@available(iOS 13.0, *)) {
return PermissionStatusDenied;
} }
return PermissionStatusGranted; return PermissionStatusGranted;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { /// Asynchronously requests the status of the Bluetooth service.
///
/// The result will be fetched in `handleCheckServiceStatusCallback`, which will in turn call
/// `completionHandler` with either `ServiceStatusEnabled` or `ServiceStatusDisabled`.
///
/// If the Bluetooth permission has been requested and was denied, this will return `ServiceStatusDisabled`,
/// regardless of the actual state of the Bluetooth service.
/// If the Bluetooth permission has not been granted or denied yet, this call will trigger a permission dialog, asking
/// the user to give the application access to Bluetooth.
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
[self initManagerIfNeeded]; [self initManagerIfNeeded];
_serviceStatusHandler = completionHandler;
_requestedPermission = permission;
}
- (void)handleCheckServiceStatusCallback:(CBCentralManager *)centralManager {
if (@available(iOS 10, *)) { if (@available(iOS 10, *)) {
return [_centralManager state] == CBManagerStatePoweredOn ? ServiceStatusEnabled : ServiceStatusDisabled; ServiceStatus serviceStatus = [centralManager state] == CBManagerStatePoweredOn ? ServiceStatusEnabled : ServiceStatusDisabled;
_serviceStatusHandler(serviceStatus);
} }
return [_centralManager state] == CBCentralManagerStatePoweredOn ? ServiceStatusEnabled : ServiceStatusDisabled; #pragma clang diagnostic ignored "-Wdeprecated-declarations"
ServiceStatus serviceStatus = [centralManager state] == CBCentralManagerStatePoweredOn ? ServiceStatusEnabled : ServiceStatusDisabled;
_serviceStatusHandler(serviceStatus);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
...@@ -55,7 +83,17 @@ ...@@ -55,7 +83,17 @@
_requestedPermission = permission; _requestedPermission = permission;
} }
+ (PermissionStatus)parsePermission:(CBManagerAuthorization)bluetoothPermission API_AVAILABLE(ios(13)){ - (void)handleRequestPermissionCallback:(CBCentralManager *)centralManager {
if (@available(iOS 13.0, *)) {
CBManagerAuthorization blePermission = [centralManager authorization];
PermissionStatus permissionStatus = [BluetoothPermissionStrategy parsePermission:blePermission];
_permissionStatusHandler(permissionStatus);
} else {
_permissionStatusHandler(PermissionStatusGranted);
}
}
+ (PermissionStatus)parsePermission:(CBManagerAuthorization)bluetoothPermission API_AVAILABLE(ios(13)) {
switch(bluetoothPermission){ switch(bluetoothPermission){
case CBManagerAuthorizationNotDetermined: case CBManagerAuthorizationNotDetermined:
return PermissionStatusDenied; return PermissionStatusDenied;
...@@ -69,8 +107,13 @@ ...@@ -69,8 +107,13 @@
} }
- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)centralManager { - (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)centralManager {
PermissionStatus permissionStatus = [self checkPermissionStatus:_requestedPermission]; if (_permissionStatusHandler != nil) {
_permissionStatusHandler(permissionStatus); [self handleRequestPermissionCallback:centralManager];
}
if (_serviceStatusHandler != nil) {
[self handleCheckServiceStatusCallback:centralManager];
}
} }
@end @end
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
return [ContactPermissionStrategy permissionStatus]; return [ContactPermissionStrategy permissionStatus];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
return [CriticalAlertsPermissionStrategy permissionStatus]; return [CriticalAlertsPermissionStrategy permissionStatus];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -23,8 +23,8 @@ ...@@ -23,8 +23,8 @@
return PermissionStatusDenied; return PermissionStatusDenied;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -35,8 +35,8 @@ NSString *const UserDefaultPermissionRequestedKey = @"org.baseflow.permission_ha ...@@ -35,8 +35,8 @@ NSString *const UserDefaultPermissionRequestedKey = @"org.baseflow.permission_ha
return [LocationPermissionStrategy permissionStatus:permission]; return [LocationPermissionStrategy permissionStatus:permission];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return [CLLocationManager locationServicesEnabled] ? ServiceStatusEnabled : ServiceStatusDisabled; completionHandler([CLLocationManager locationServicesEnabled] ? ServiceStatusEnabled : ServiceStatusDisabled);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
return [MediaLibraryPermissionStrategy permissionStatus]; return [MediaLibraryPermissionStrategy permissionStatus];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
return [NotificationPermissionStrategy permissionStatus]; return [NotificationPermissionStrategy permissionStatus];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -6,12 +6,13 @@ ...@@ -6,12 +6,13 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "PermissionHandlerEnums.h" #import "PermissionHandlerEnums.h"
typedef void (^ServiceStatusHandler)(ServiceStatus serviceStatus);
typedef void (^PermissionStatusHandler)(PermissionStatus permissionStatus); typedef void (^PermissionStatusHandler)(PermissionStatus permissionStatus);
@protocol PermissionStrategy <NSObject> @protocol PermissionStrategy <NSObject>
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission; - (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission;
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission; - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler;
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler; - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler;
@end @end
...@@ -16,13 +16,12 @@ ...@@ -16,13 +16,12 @@
return PermissionStatusDenied; return PermissionStatusDenied;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
// https://stackoverflow.com/a/5095058 // https://stackoverflow.com/a/5095058
if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel://"]]) { if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel://"]]) {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
completionHandler([self canDevicePlaceAPhoneCall] ? ServiceStatusEnabled : ServiceStatusDisabled);
return [self canDevicePlaceAPhoneCall] ? ServiceStatusEnabled : ServiceStatusDisabled;
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
return [PhotoPermissionStrategy permissionStatus:addOnlyAccessLevel]; return [PhotoPermissionStrategy permissionStatus:addOnlyAccessLevel];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -12,14 +12,15 @@ ...@@ -12,14 +12,15 @@
return [SensorPermissionStrategy permissionStatus]; return [SensorPermissionStrategy permissionStatus];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
if (@available(iOS 11.0, *)) { if (@available(iOS 11.0, *)) {
return [CMMotionActivityManager isActivityAvailable] completionHandler([CMMotionActivityManager isActivityAvailable]
? ServiceStatusEnabled ? ServiceStatusEnabled
: ServiceStatusDisabled; : ServiceStatusDisabled
);
} }
return ServiceStatusDisabled; completionHandler(ServiceStatusDisabled);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
return [SpeechPermissionStrategy permissionStatus]; return [SpeechPermissionStrategy permissionStatus];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
return [StoragePermissionStrategy permissionStatus]; return [StoragePermissionStrategy permissionStatus];
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusNotApplicable; completionHandler(ServiceStatusNotApplicable);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
return PermissionStatusDenied; return PermissionStatusDenied;
} }
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission { - (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
return ServiceStatusDisabled; completionHandler(ServiceStatusDisabled);
} }
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler { - (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
...@@ -2,7 +2,7 @@ name: permission_handler_apple ...@@ -2,7 +2,7 @@ name: permission_handler_apple
description: Permission plugin for Flutter. This plugin provides the iOS API to request and check permissions. description: Permission plugin for Flutter. This plugin provides the iOS API to request and check permissions.
repository: https://github.com/baseflow/flutter-permission-handler repository: https://github.com/baseflow/flutter-permission-handler
issue_tracker: https://github.com/Baseflow/flutter-permission-handler/issues issue_tracker: https://github.com/Baseflow/flutter-permission-handler/issues
version: 9.1.3 version: 9.1.4
environment: environment:
sdk: ">=2.15.0 <4.0.0" sdk: ">=2.15.0 <4.0.0"
...@@ -18,7 +18,7 @@ flutter: ...@@ -18,7 +18,7 @@ flutter:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
permission_handler_platform_interface: ^3.11.0 permission_handler_platform_interface: ^3.11.2
dev_dependencies: dev_dependencies:
flutter_lints: ^1.0.4 flutter_lints: ^1.0.4
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment