import 'dart:async'; import 'dart:io' show Platform; import 'package:device_info_plus/device_info_plus.dart'; import 'package:logger/logger.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../constants.dart'; import '../../core/services/stoppable_service.dart'; import '../../core/util/logger.dart'; class PermissionService extends StoppableService { final Logger _logger = getLogger(); Timer? _serviceCheckTimer; bool _devicePermissionDialogActive = false; bool _deviceInformationInitialized = false; bool _useStoragePermission = true; PermissionService(); Future checkEnabledAndPermission() async { if (_devicePermissionDialogActive) { _logger.d('Device permission dialog active, skipping'); return; } bool allGranted = false; bool anyPermanentlyDenied = false; // Since Android compileSdk >= 33, "storage" is deprecated // Instead, request access to all of // - Permission.photos // - Permission.videos // - Permission.audio // // For iOS and Android < 33, keep using "storage" if (_useStoragePermission) { PermissionStatus storagePermission = await Permission.storage.status; allGranted = PermissionStatus.granted == storagePermission; anyPermanentlyDenied = PermissionStatus.permanentlyDenied == storagePermission; } else { PermissionStatus photosPermission = await Permission.photos.status; PermissionStatus videosPermission = await Permission.videos.status; PermissionStatus audioPermission = await Permission.audio.status; allGranted = PermissionStatus.granted == photosPermission && PermissionStatus.granted == videosPermission && PermissionStatus.granted == audioPermission; anyPermanentlyDenied = PermissionStatus.permanentlyDenied == photosPermission || PermissionStatus.permanentlyDenied == videosPermission || PermissionStatus.permanentlyDenied == audioPermission; } // show warning to user to manually handle, don't enforce it over and over again if (anyPermanentlyDenied) { _logger.w( "At least one required permission has been denied permanently, stopping service"); stop(); return; } // all good, stop the permission service if (allGranted) { _logger.d("All permissions have been granted, stopping service"); stop(); return; } // not all have been granted, show OS dialog _logger.d( "Not all permissions have been granted yet, initializing permission dialog"); _devicePermissionDialogActive = true; if (_useStoragePermission) { await [Permission.storage].request().whenComplete(() { _logger.d('Device request permission finished'); _devicePermissionDialogActive = false; }); } else { await [Permission.photos, Permission.videos, Permission.audio] .request() .whenComplete(() { _logger.d('Device request permission finished'); _devicePermissionDialogActive = false; }); } } @override Future start() async { super.start(); await _determineDeviceInfo(); await checkEnabledAndPermission(); _serviceCheckTimer = Timer.periodic( const Duration(milliseconds: Constants.mediaPermissionCheckInterval), (serviceTimer) async { if (!super.serviceStopped) { await checkEnabledAndPermission(); } else { serviceTimer.cancel(); } }); _logger.d('PermissionService started'); } @override void stop() { _removeServiceCheckTimer(); super.stop(); _logger.d('PermissionService stopped'); } Future _determineDeviceInfo() async { if (_deviceInformationInitialized) { _logger.d('Device information already initialized, skipping'); return; } DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); if (Platform.isAndroid) { final androidInfo = await deviceInfoPlugin.androidInfo; if (androidInfo.version.sdkInt >= 33) { _useStoragePermission = false; } } if (_useStoragePermission) { _logger.d('Device requires [storage] permission'); } else { _logger.d('Device requires [photos,videos,audio] permission'); } _deviceInformationInitialized = true; } void _removeServiceCheckTimer() { if (_serviceCheckTimer != null) { _serviceCheckTimer!.cancel(); _serviceCheckTimer = null; _logger.d('Removed service check timer'); } } }