/** * PermissionManager.uts * * Utility class for managing Android permissions throughout the app * Handles requesting permissions, checking status, and directing users to settings */ /** * Common permission types that can be requested */ export enum PermissionType { BLUETOOTH = 'bluetooth', LOCATION = 'location', STORAGE = 'storage', CAMERA = 'camera', MICROPHONE = 'microphone', NOTIFICATIONS = 'notifications', CALENDAR = 'calendar', CONTACTS = 'contacts', SENSORS = 'sensors' } /** * Result of a permission request */ type PermissionResult = { granted: boolean; grantedPermissions: string[]; deniedPermissions: string[]; } /** * Manages permission requests and checks throughout the app */ export class PermissionManager { /** * Maps permission types to the actual Android permission strings */ private static getPermissionsForType(type: PermissionType): string[] { switch (type) { case PermissionType.BLUETOOTH: return [ 'android.permission.BLUETOOTH_SCAN', 'android.permission.BLUETOOTH_CONNECT', 'android.permission.BLUETOOTH_ADVERTISE' ]; case PermissionType.LOCATION: return [ 'android.permission.ACCESS_FINE_LOCATION', 'android.permission.ACCESS_COARSE_LOCATION' ]; case PermissionType.STORAGE: return [ 'android.permission.READ_EXTERNAL_STORAGE', 'android.permission.WRITE_EXTERNAL_STORAGE' ]; case PermissionType.CAMERA: return ['android.permission.CAMERA']; case PermissionType.MICROPHONE: return ['android.permission.RECORD_AUDIO']; case PermissionType.NOTIFICATIONS: return ['android.permission.POST_NOTIFICATIONS']; case PermissionType.CALENDAR: return [ 'android.permission.READ_CALENDAR', 'android.permission.WRITE_CALENDAR' ]; case PermissionType.CONTACTS: return [ 'android.permission.READ_CONTACTS', 'android.permission.WRITE_CONTACTS' ]; case PermissionType.SENSORS: return ['android.permission.BODY_SENSORS']; default: return []; } } /** * Get appropriate display name for a permission type */ private static getPermissionDisplayName(type: PermissionType): string { switch (type) { case PermissionType.BLUETOOTH: return '蓝牙'; case PermissionType.LOCATION: return '位置'; case PermissionType.STORAGE: return '存储'; case PermissionType.CAMERA: return '相机'; case PermissionType.MICROPHONE: return '麦克风'; case PermissionType.NOTIFICATIONS: return '通知'; case PermissionType.CALENDAR: return '日历'; case PermissionType.CONTACTS: return '联系人'; case PermissionType.SENSORS: return '身体传感器'; default: return '未知权限'; } } /** * Check if a permission is granted * @param type The permission type to check * @returns True if the permission is granted, false otherwise */ static isPermissionGranted(type: PermissionType): boolean { // #ifdef APP-ANDROID try { const permissions = this.getPermissionsForType(type); const activity = UTSAndroid.getUniActivity(); if (activity == null || permissions.length === 0) { return false; } // Check each permission in the group for (const permission of permissions) { if (!UTSAndroid.checkSystemPermissionGranted(activity, [permission])) { return false; } } return true; } catch (e) { console.error(`Error checking ${type} permission:`, e); return false; } // #endif // #ifndef APP-ANDROID return true; // Non-Android platforms don't need explicit permission checks // #endif } /** * Request a permission from the user * @param type The permission type to request * @param callback Function to call with the result of the permission request * @param showRationale Whether to show a rationale dialog if permission was previously denied */ static requestPermission( type: PermissionType, callback: (result: PermissionResult) => void, showRationale: boolean = true ): void { // #ifdef APP-ANDROID try { const permissions = this.getPermissionsForType(type); const activity = UTSAndroid.getUniActivity(); if (activity == null || permissions.length === 0) { callback({ granted: false, grantedPermissions: [], deniedPermissions: permissions }); return; } // Check if already granted let allGranted = true; for (const permission of permissions) { if (!UTSAndroid.checkSystemPermissionGranted(activity, [permission])) { allGranted = false; break; } } if (allGranted) { callback({ granted: true, grantedPermissions: permissions, deniedPermissions: [] }); return; } // Request the permissions UTSAndroid.requestSystemPermission( activity, permissions, (granted: boolean, grantedPermissions: string[]) => { if (granted) { callback({ granted: true, grantedPermissions: grantedPermissions, deniedPermissions: [] }); } else if (showRationale) { // Show rationale dialog this.showPermissionRationale(type, callback); } else { // Just report the denial callback({ granted: false, grantedPermissions: grantedPermissions, deniedPermissions: this.getDeniedPermissions(permissions, grantedPermissions) }); } }, (denied: boolean, deniedPermissions: string[]) => { callback({ granted: false, grantedPermissions: this.getGrantedPermissions(permissions, deniedPermissions), deniedPermissions: deniedPermissions }); } ); } catch (e) { console.error(`Error requesting ${type} permission:`, e); callback({ granted: false, grantedPermissions: [], deniedPermissions: this.getPermissionsForType(type) }); } // #endif // #ifndef APP-ANDROID // Non-Android platforms don't need explicit permissions callback({ granted: true, grantedPermissions: this.getPermissionsForType(type), deniedPermissions: [] }); // #endif } /** * Show a rationale dialog explaining why the permission is needed */ private static showPermissionRationale( type: PermissionType, callback: (result: PermissionResult) => void ): void { const permissionName = this.getPermissionDisplayName(type); uni.showModal({ title: '权限申请', content: `需要${permissionName}权限才能使用相关功能`, confirmText: '去设置', cancelText: '取消', success: (result) => { if (result.confirm) { this.openAppSettings(); callback({ granted: false, grantedPermissions: [], deniedPermissions: this.getPermissionsForType(type) }); } else { callback({ granted: false, grantedPermissions: [], deniedPermissions: this.getPermissionsForType(type) }); } } }); } /** * Open the app settings page */ static openAppSettings(): void { // #ifdef APP-ANDROID try { const context = UTSAndroid.getAppContext(); if (context != null) { const intent = new android.content.Intent(); intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); const uri = android.net.Uri.fromParts("package", context.getPackageName(), null); intent.setData(uri); intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } } catch (e) { console.error('Failed to open app settings', e); uni.showToast({ title: '请手动前往系统设置修改应用权限', icon: 'none', duration: 3000 }); } // #endif // #ifndef APP-ANDROID uni.showToast({ title: '请在系统设置中管理应用权限', icon: 'none', duration: 2000 }); // #endif } /** * Helper to get the list of granted permissions */ private static getGrantedPermissions(allPermissions: string[], deniedPermissions: string[]): string[] { return allPermissions.filter(p => !deniedPermissions.includes(p)); } /** * Helper to get the list of denied permissions */ private static getDeniedPermissions(allPermissions: string[], grantedPermissions: string[]): string[] { return allPermissions.filter(p => !grantedPermissions.includes(p)); } /** * Request multiple permission types at once * @param types Array of permission types to request * @param callback Function to call when all permissions have been processed */ static requestMultiplePermissions( types: PermissionType[], callback: (results: Map) => void ): void { if (types.length === 0) { callback(new Map()); return; } const results = new Map(); let remaining = types.length; for (const type of types) { this.requestPermission( type, (result) => { results.set(type, result); remaining--; if (remaining === 0) { callback(results); } }, true ); } } /** * Convenience method to request Bluetooth permissions * @param callback Function to call after the permission request */ static requestBluetoothPermissions(callback: (granted: boolean) => void): void { this.requestPermission(PermissionType.BLUETOOTH, (result) => { // For Bluetooth, we also need location permissions on Android if (result.granted) { this.requestPermission(PermissionType.LOCATION, (locationResult) => { callback(locationResult.granted); }); } else { callback(false); } }); } }