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
* 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> {
children: Permission.values
.where((permission) {
return permission != Permission.unknown &&
permission != Permission.phone &&
permission != Permission.sms &&
permission != Permission.storage &&
permission != Permission.ignoreBatteryOptimizations &&
permission != Permission.accessMediaLocation &&
permission != Permission.activityRecognition &&
......@@ -47,7 +47,12 @@ class _PermissionHandlerWidgetState extends State<PermissionHandlerWidget> {
permission != Permission.accessNotificationPolicy &&
permission != Permission.bluetoothScan &&
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))
.toList()),
......
......@@ -26,9 +26,17 @@
}
+ (void)checkServiceStatus:(enum PermissionGroup)permission result:(FlutterResult)result {
id <PermissionStrategy> permissionStrategy = [PermissionManager createPermissionStrategy:permission];
ServiceStatus status = [permissionStrategy checkServiceStatus:permission];
result([Codec encodeServiceStatus:status]);
__block id <PermissionStrategy> permissionStrategy = [PermissionManager createPermissionStrategy:permission];
[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 {
......
......@@ -20,8 +20,8 @@
return PermissionStatusGranted;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -22,8 +22,8 @@
return PermissionStatusDenied;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -11,8 +11,9 @@
@implementation BluetoothPermissionStrategy {
CBCentralManager *_centralManager;
PermissionStatusHandler _permissionStatusHandler;
PermissionGroup _requestedPermission;
PermissionStatusHandler _permissionStatusHandler;
ServiceStatusHandler _serviceStatusHandler;
}
- (void)initManagerIfNeeded {
......@@ -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 {
[self initManagerIfNeeded];
if (@available(iOS 13.1, *)) {
CBManagerAuthorization blePermission = [_centralManager authorization];
return [BluetoothPermissionStrategy parsePermission:blePermission];
} else if (@available(iOS 13.0, *)){
CBManagerAuthorization blePermission = [_centralManager authorization];
CBManagerAuthorization blePermission = [CBCentralManager authorization];
return [BluetoothPermissionStrategy parsePermission:blePermission];
} else if (@available(iOS 13.0, *)) {
return PermissionStatusDenied;
}
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];
_serviceStatusHandler = completionHandler;
_requestedPermission = permission;
}
- (void)handleCheckServiceStatusCallback:(CBCentralManager *)centralManager {
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 {
......@@ -55,7 +83,17 @@
_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){
case CBManagerAuthorizationNotDetermined:
return PermissionStatusDenied;
......@@ -69,8 +107,13 @@
}
- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)centralManager {
PermissionStatus permissionStatus = [self checkPermissionStatus:_requestedPermission];
_permissionStatusHandler(permissionStatus);
if (_permissionStatusHandler != nil) {
[self handleRequestPermissionCallback:centralManager];
}
if (_serviceStatusHandler != nil) {
[self handleCheckServiceStatusCallback:centralManager];
}
}
@end
......
......@@ -13,8 +13,8 @@
return [ContactPermissionStrategy permissionStatus];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -15,8 +15,8 @@
return [CriticalAlertsPermissionStrategy permissionStatus];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -23,8 +23,8 @@
return PermissionStatusDenied;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -35,8 +35,8 @@ NSString *const UserDefaultPermissionRequestedKey = @"org.baseflow.permission_ha
return [LocationPermissionStrategy permissionStatus:permission];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return [CLLocationManager locationServicesEnabled] ? ServiceStatusEnabled : ServiceStatusDisabled;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler([CLLocationManager locationServicesEnabled] ? ServiceStatusEnabled : ServiceStatusDisabled);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -13,8 +13,8 @@
return [MediaLibraryPermissionStrategy permissionStatus];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -15,8 +15,8 @@
return [NotificationPermissionStrategy permissionStatus];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -6,12 +6,13 @@
#import <Foundation/Foundation.h>
#import "PermissionHandlerEnums.h"
typedef void (^ServiceStatusHandler)(ServiceStatus serviceStatus);
typedef void (^PermissionStatusHandler)(PermissionStatus permissionStatus);
@protocol PermissionStrategy <NSObject>
- (PermissionStatus)checkPermissionStatus:(PermissionGroup)permission;
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler;
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler;
@end
......@@ -16,13 +16,12 @@
return PermissionStatusDenied;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
// https://stackoverflow.com/a/5095058
if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel://"]]) {
return ServiceStatusNotApplicable;
completionHandler(ServiceStatusNotApplicable);
}
return [self canDevicePlaceAPhoneCall] ? ServiceStatusEnabled : ServiceStatusDisabled;
completionHandler([self canDevicePlaceAPhoneCall] ? ServiceStatusEnabled : ServiceStatusDisabled);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -24,8 +24,8 @@
return [PhotoPermissionStrategy permissionStatus:addOnlyAccessLevel];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -12,14 +12,15 @@
return [SensorPermissionStrategy permissionStatus];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
if (@available(iOS 11.0, *)) {
return [CMMotionActivityManager isActivityAvailable]
completionHandler([CMMotionActivityManager isActivityAvailable]
? ServiceStatusEnabled
: ServiceStatusDisabled;
: ServiceStatusDisabled
);
}
return ServiceStatusDisabled;
completionHandler(ServiceStatusDisabled);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -12,8 +12,8 @@
return [SpeechPermissionStrategy permissionStatus];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -13,8 +13,8 @@
return [StoragePermissionStrategy permissionStatus];
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusNotApplicable;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusNotApplicable);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -12,8 +12,8 @@
return PermissionStatusDenied;
}
- (ServiceStatus)checkServiceStatus:(PermissionGroup)permission {
return ServiceStatusDisabled;
- (void)checkServiceStatus:(PermissionGroup)permission completionHandler:(ServiceStatusHandler)completionHandler {
completionHandler(ServiceStatusDisabled);
}
- (void)requestPermission:(PermissionGroup)permission completionHandler:(PermissionStatusHandler)completionHandler {
......
......@@ -2,7 +2,7 @@ name: permission_handler_apple
description: Permission plugin for Flutter. This plugin provides the iOS API to request and check permissions.
repository: https://github.com/baseflow/flutter-permission-handler
issue_tracker: https://github.com/Baseflow/flutter-permission-handler/issues
version: 9.1.3
version: 9.1.4
environment:
sdk: ">=2.15.0 <4.0.0"
......@@ -18,7 +18,7 @@ flutter:
dependencies:
flutter:
sdk: flutter
permission_handler_platform_interface: ^3.11.0
permission_handler_platform_interface: ^3.11.2
dev_dependencies:
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