Files
OpenMotion/lib/services/ble_permissions.dart

76 lines
2.4 KiB
Dart

import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
/// Outcome of a Bluetooth permission check or request.
enum BleAccess {
/// Everything granted. Bluetooth adapter state is checked separately.
granted,
/// The user declined but can be asked again.
denied,
/// The user declined with "don't ask again" (Android) or in Settings (iOS).
/// Only the system settings screen can fix this.
permanentlyDenied,
/// The platform reported Bluetooth as unavailable.
unsupported,
}
/// Requests the runtime permissions OpenMotion needs to scan and connect.
///
/// Android 12+ (API 31): BLUETOOTH_SCAN and BLUETOOTH_CONNECT, shown to the
/// user as "Nearby devices". Android 11 and below: legacy BLE scanning needs
/// location while in use. iOS: the single Bluetooth permission, which the OS
/// prompts for the first time CoreBluetooth is touched.
class BlePermissions {
BlePermissions._();
static int? _androidSdk;
static Future<int> androidSdk() async {
if (!Platform.isAndroid) return 0;
return _androidSdk ??= (await DeviceInfoPlugin().androidInfo).version.sdkInt;
}
static Future<List<Permission>> _needed() async {
if (Platform.isAndroid) {
final sdk = await androidSdk();
return sdk >= 31
? const [Permission.bluetoothScan, Permission.bluetoothConnect]
: const [Permission.locationWhenInUse];
}
if (Platform.isIOS || Platform.isMacOS) return const [Permission.bluetooth];
return const [];
}
/// Checks without prompting.
static Future<BleAccess> status() async {
final statuses = <PermissionStatus>[];
for (final p in await _needed()) {
statuses.add(await p.status);
}
return _combine(statuses);
}
/// Prompts for anything not yet granted.
static Future<BleAccess> request() async {
final needed = await _needed();
if (needed.isEmpty) return BleAccess.granted;
final results = await needed.request();
return _combine(results.values.toList());
}
static BleAccess _combine(List<PermissionStatus> statuses) {
if (statuses.any((s) => s.isPermanentlyDenied || s.isRestricted)) {
return BleAccess.permanentlyDenied;
}
if (statuses.any((s) => s.isDenied)) return BleAccess.denied;
return BleAccess.granted;
}
static Future<bool> openSettings() => openAppSettings();
}