// BLE Permissions Helper // Handles Bluetooth permission requests with graceful fallback import { PermissionsAndroid, Platform, Linking, Alert } from 'react-native'; import { BleManager, State } from 'react-native-ble-plx'; export interface PermissionStatus { granted: boolean; canRequest: boolean; // false if user previously denied with "Don't ask again" error?: string; } export interface BluetoothStatus { enabled: boolean; canEnable: boolean; error?: string; } /** * Check and request BLE permissions based on platform * Returns permission status with graceful fallback info */ export async function requestBLEPermissions(): Promise { if (Platform.OS === 'ios') { // iOS handles permissions automatically via Info.plist // BLE permission dialog shows on first BLE operation return { granted: true, canRequest: true }; } if (Platform.OS === 'android') { try { if (Platform.Version >= 31) { // Android 12+ requires BLUETOOTH_SCAN and BLUETOOTH_CONNECT const results = await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN!, PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT!, PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION!, ]); const allGranted = Object.values(results).every( status => status === PermissionsAndroid.RESULTS.GRANTED ); const anyNeverAskAgain = Object.values(results).some( status => status === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN ); if (allGranted) { return { granted: true, canRequest: true }; } if (anyNeverAskAgain) { return { granted: false, canRequest: false, error: 'Bluetooth permissions were previously denied. Please enable them in Settings.', }; } return { granted: false, canRequest: true, error: 'Bluetooth permissions are required to scan for sensors.', }; } else { // Android < 12 requires ACCESS_FINE_LOCATION const result = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION! ); if (result === PermissionsAndroid.RESULTS.GRANTED) { return { granted: true, canRequest: true }; } if (result === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) { return { granted: false, canRequest: false, error: 'Location permission was previously denied. Please enable it in Settings.', }; } return { granted: false, canRequest: true, error: 'Location permission is required to scan for Bluetooth devices.', }; } } catch (error: any) { return { granted: false, canRequest: false, error: `Failed to request permissions: ${error.message}`, }; } } // Unknown platform return { granted: false, canRequest: false, error: 'Bluetooth permissions not supported on this platform', }; } /** * Check Bluetooth state (enabled/disabled) * Returns status with helpful error messages */ export async function checkBluetoothEnabled(manager: BleManager): Promise { try { const state = await manager.state(); switch (state) { case State.PoweredOn: return { enabled: true, canEnable: true }; case State.PoweredOff: return { enabled: false, canEnable: true, error: 'Bluetooth is turned off. Please enable it in your device settings.', }; case State.Unauthorized: return { enabled: false, canEnable: false, error: 'Bluetooth access is not authorized. Please enable permissions in Settings.', }; case State.Unsupported: return { enabled: false, canEnable: false, error: 'Bluetooth is not supported on this device.', }; case State.Resetting: return { enabled: false, canEnable: true, error: 'Bluetooth is resetting. Please wait a moment and try again.', }; case State.Unknown: default: return { enabled: false, canEnable: true, error: 'Bluetooth state is unknown. Please check your device settings.', }; } } catch (error: any) { return { enabled: false, canEnable: false, error: `Failed to check Bluetooth state: ${error.message}`, }; } } /** * Show a user-friendly alert for permission/Bluetooth issues * Offers to open Settings if needed */ export function showPermissionAlert( permissionStatus: PermissionStatus, bluetoothStatus: BluetoothStatus ): void { // Bluetooth disabled (can be fixed easily) if (!bluetoothStatus.enabled && bluetoothStatus.canEnable) { Alert.alert( 'Bluetooth Required', bluetoothStatus.error || 'Please enable Bluetooth to scan for sensors.', [ { text: 'Cancel', style: 'cancel' }, { text: 'Open Settings', onPress: () => { if (Platform.OS === 'ios') { Linking.openURL('App-Prefs:Bluetooth'); } else { Linking.sendIntent('android.settings.BLUETOOTH_SETTINGS'); } }, }, ] ); return; } // Permission denied with "Never ask again" if (!permissionStatus.granted && !permissionStatus.canRequest) { Alert.alert( 'Permissions Required', permissionStatus.error || 'Bluetooth permissions are required to scan for sensors. Please enable them in Settings.', [ { text: 'Cancel', style: 'cancel' }, { text: 'Open Settings', onPress: () => { Linking.openSettings(); }, }, ] ); return; } // Permission denied (can retry) if (!permissionStatus.granted) { Alert.alert( 'Permissions Required', permissionStatus.error || 'Bluetooth permissions are required to scan for sensors.', [{ text: 'OK' }] ); return; } // Bluetooth not supported or other unrecoverable error if (!bluetoothStatus.canEnable) { Alert.alert( 'Bluetooth Unavailable', bluetoothStatus.error || 'Bluetooth is not available on this device.', [{ text: 'OK' }] ); } } /** * Comprehensive pre-scan check * Returns true if ready to scan, false otherwise (with user alert shown) */ export async function checkBLEReadiness(manager: BleManager): Promise { // Step 1: Check permissions const permissionStatus = await requestBLEPermissions(); // Step 2: Check Bluetooth state const bluetoothStatus = await checkBluetoothEnabled(manager); // Step 3: If not ready, show appropriate alert if (!permissionStatus.granted || !bluetoothStatus.enabled) { showPermissionAlert(permissionStatus, bluetoothStatus); return false; } return true; }