import 'package:flutter/services.dart'; import 'package:pj_supermarket_cloud/common/models/manage/root_access_models.dart'; /// 当前 Android 设备的运行环境。 class AppRuntimeEnvironment { /// 是否命中 Android 虚拟机特征;通道不可用时按真机降级,避免阻塞应用启动。 final bool isEmulator; /// Android DisplayManager 当前识别的显示屏数量,最少按单屏处理。 final int displayCount; /// 当前 Android 系统发行版本,例如 `11`;原生字段不可用时为空字符串。 final String androidVersion; /// 当前 Android API Level,例如 Android 11 对应 30;原生字段不可用时为 0。 final int androidSdkInt; /// 当前进程实际安装 APK 的展示版本,不使用后台待更新版本代替。 final String apkVersionName; /// 当前进程实际安装 APK 的内部版本码;原生字段不可用时为 0。 final int apkVersionCode; /// 创建一份由 Android 原生运行时采集的不可变环境快照。 const AppRuntimeEnvironment({ required this.isEmulator, required this.displayCount, required this.androidVersion, required this.androidSdkInt, required this.apkVersionName, required this.apkVersionCode, }); /// 从 MethodChannel 返回值解析环境快照,缺失版本字段使用可安全展示的空值。 factory AppRuntimeEnvironment.fromMap(Map? data) { return AppRuntimeEnvironment( isEmulator: data?['isEmulator'] == true, displayCount: (data?['displayCount'] as num?)?.toInt() ?? 1, androidVersion: data?['androidVersion']?.toString() ?? '', androidSdkInt: (data?['androidSdkInt'] as num?)?.toInt() ?? 0, apkVersionName: data?['apkVersionName']?.toString() ?? '', apkVersionCode: (data?['apkVersionCode'] as num?)?.toInt() ?? 0, ); } } /// Android 专用设备模式的实时状态。 class AppKioskStatus { final bool deviceAdminActive; final bool deviceOwner; final bool available; final bool active; final bool lockTaskPermitted; final bool autoEnterEnabled; final bool bootAutoStartSupported; const AppKioskStatus({ required this.deviceAdminActive, required this.deviceOwner, required this.available, required this.active, required this.lockTaskPermitted, required this.autoEnterEnabled, required this.bootAutoStartSupported, }); const AppKioskStatus.unavailable() : deviceAdminActive = false, deviceOwner = false, available = false, active = false, lockTaskPermitted = false, autoEnterEnabled = false, bootAutoStartSupported = false; factory AppKioskStatus.fromMap(Map? data) { return AppKioskStatus( deviceAdminActive: data?['deviceAdminActive'] == true, deviceOwner: data?['deviceOwner'] == true, available: data?['available'] == true, active: data?['active'] == true, lockTaskPermitted: data?['lockTaskPermitted'] == true, autoEnterEnabled: data?['autoEnterEnabled'] == true, bootAutoStartSupported: data?['bootAutoStartSupported'] == true, ); } bool get canExitToAndroidHome => available && active; String get statusLabel { if (!available) return '暂不可实现'; if (active && !autoEnterEnabled) return 'Kiosk 运行中 · 自动开启已关闭'; if (active) return '可以实现 · Kiosk 运行中'; if (!autoEnterEnabled) return '可以实现 · 自动开启已关闭'; if (lockTaskPermitted) return '可以实现 · 正在进入'; return '可以实现 · 等待策略生效'; } String get description { if (available && active && !autoEnterEnabled) { return '当前仍在 Kiosk;管理员退出后,再次打开或回到 App 将不再自动锁定。'; } if (available && !autoEnterEnabled) { return '设备具备 Device Owner 权限;开启右侧配置后,每次打开或回到 App 都会自动进入 Kiosk。'; } if (available) { return '已配置 Device Owner;每次打开或回到 App 都会自动进入 Kiosk,并隐藏主页、最近任务和系统栏。'; } if (deviceAdminActive) { return '当前仅激活了设备管理员,仍不能可靠禁用主页和最近任务;需要配置为 Device Owner。'; } return '未配置 Device Owner,普通全屏不能保证无法退出;需在恢复出厂或未初始化的设备上部署。'; } } /// Root 执行 DPM 后返回的结构化 Kiosk 启用结果。 class AppKioskProvisionResult { /// 创建一份包含稳定错误编码、可操作说明和最新 Kiosk 状态的结果。 const AppKioskProvisionResult({ required this.success, required this.code, required this.message, required this.status, }); /// DPM 是否成功配置当前应用为 Device Owner 并进入 Kiosk。 final bool success; /// 原生层稳定错误编码,用于区分账户、Owner、用户和厂商限制。 final String code; /// 面向终端管理员展示的中文处理建议。 final String message; /// 命令执行完成后重新读取的 Kiosk 实时状态。 final AppKioskStatus status; /// 从原生结构化返回值恢复结果,通道字段缺失时使用安全失败值。 factory AppKioskProvisionResult.fromMap(Map? data) { return AppKioskProvisionResult( success: data?['success'] == true, code: data?['code']?.toString() ?? 'UNKNOWN', message: data?['message']?.toString() ?? 'Kiosk 启用失败,请检查设备策略', status: AppKioskStatus.fromMap(data?['status'] as Map?), ); } } /// Android APK 校验或 Root 安装命令的结构化结果。 class AppApkOperationResult { /// 创建一份供自动更新流程判断的原生操作结果。 const AppApkOperationResult({ required this.success, required this.code, required this.message, this.versionCode, }); /// APK 校验通过或安装命令成功执行时为 true。 final bool success; /// 原生稳定结果编码,例如 SIGNATURE_MISMATCH 或 INSTALL_STARTED。 final String code; /// 面向任务日志或人工操作展示的中文摘要。 final String message; /// 原生 APK 实际版本码;返回数据不含版本时为空。 final int? versionCode; /// 从 MethodChannel 返回映射创建操作结果。 factory AppApkOperationResult.fromMap(Map? data) { return AppApkOperationResult( success: data?['valid'] == true, code: data?['code']?.toString() ?? 'UNKNOWN', message: data?['message']?.toString() ?? '原生 APK 操作失败', versionCode: (data?['versionCode'] as num?)?.toInt(), ); } } /// 应用启动时消费的待更新恢复结果。 class AppUpdateRecoveryResult { /// 创建一份待更新确认结果。 const AppUpdateRecoveryResult({ required this.pending, required this.success, required this.message, }); /// 原生层是否存在上次安装前持久化的待更新记录。 final bool pending; /// 当前安装版本是否达到待更新目标版本码。 final bool success; /// 启动确认结果摘要,用于完成任务记录。 final String message; /// 从 MethodChannel 返回映射创建恢复结果。 factory AppUpdateRecoveryResult.fromMap(Map? data) { return AppUpdateRecoveryResult( pending: data?['pending'] == true, success: data?['success'] == true, message: data?['message']?.toString() ?? '没有待确认的自动更新', ); } } class AppNetworkSnapshot { final bool connected; final String type; final int receivedBytes; final int transmittedBytes; const AppNetworkSnapshot({ required this.connected, required this.type, required this.receivedBytes, required this.transmittedBytes, }); factory AppNetworkSnapshot.fromMap(Map? data) { return AppNetworkSnapshot( connected: data?['connected'] == true, type: data?['type']?.toString() ?? 'offline', receivedBytes: (data?['rxBytes'] as num?)?.toInt() ?? 0, transmittedBytes: (data?['txBytes'] as num?)?.toInt() ?? 0, ); } } /// Android 进程与设备资源快照。 /// /// CPU 使用率需要比较两次 [processCpuTimeMillis],原生层只返回累计值,避免在平台通道内 /// 持有采样定时器;内存和存储同时保留总量,便于顶栏给出百分比并在 Tooltip 展示明细。 class AppPerformanceSnapshot { /// Android 系统自本次开机后的运行时长,单位为秒。 final int systemUptimeSeconds; /// 当前应用进程自启动后的运行时长,单位为秒。 final int appUptimeSeconds; /// Android 进程从启动至采样时累计消耗的 CPU 时间,单位为毫秒。 final int processCpuTimeMillis; /// Android Runtime 报告的逻辑处理器数量,最小按 1 处理。 final int logicalProcessorCount; /// 系统物理内存总量,单位为字节;平台数据不可用时为 0。 final int systemMemoryTotalBytes; /// 系统当前可用物理内存,单位为字节;平台数据不可用时为 0。 final int systemMemoryAvailableBytes; /// 当前应用进程的 PSS 内存占用,单位为字节。 final int appMemoryBytes; /// 应用文件目录所在存储卷的总容量,单位为字节;平台数据不可用时为 0。 final int storageTotalBytes; /// 应用文件目录所在存储卷的可用容量,单位为字节;平台数据不可用时为 0。 final int storageAvailableBytes; /// 创建一份原生平台已经完成单位换算的资源快照。 const AppPerformanceSnapshot({ required this.systemUptimeSeconds, required this.appUptimeSeconds, required this.processCpuTimeMillis, required this.logicalProcessorCount, required this.systemMemoryTotalBytes, required this.systemMemoryAvailableBytes, required this.appMemoryBytes, required this.storageTotalBytes, required this.storageAvailableBytes, }); /// 创建平台通道不可用时的空快照,百分比 Getter 将稳定返回 0。 const AppPerformanceSnapshot.empty() : systemUptimeSeconds = 0, appUptimeSeconds = 0, processCpuTimeMillis = 0, logicalProcessorCount = 1, systemMemoryTotalBytes = 0, systemMemoryAvailableBytes = 0, appMemoryBytes = 0, storageTotalBytes = 0, storageAvailableBytes = 0; /// 从 Android MethodChannel 返回值解析资源快照,缺失或类型异常字段按 0 处理。 factory AppPerformanceSnapshot.fromMap(Map? data) { int value(String key) => (data?[key] as num?)?.toInt() ?? 0; return AppPerformanceSnapshot( systemUptimeSeconds: value('systemUptimeSeconds'), appUptimeSeconds: value('appUptimeSeconds'), processCpuTimeMillis: value('processCpuTimeMs'), logicalProcessorCount: value('logicalProcessorCount').clamp(1, 256).toInt(), systemMemoryTotalBytes: value('memoryTotalBytes'), systemMemoryAvailableBytes: value('memoryAvailableBytes'), appMemoryBytes: value('appMemoryBytes'), storageTotalBytes: value('storageTotalBytes'), storageAvailableBytes: value('storageAvailableBytes'), ); } /// 系统内存已使用比例,范围为 0 到 1;总量未知时返回 0。 double get memoryUsageRatio => _usedRatio( total: systemMemoryTotalBytes, available: systemMemoryAvailableBytes, ); /// 应用所在存储卷已使用比例,范围为 0 到 1;总量未知时返回 0。 double get storageUsageRatio => _usedRatio( total: storageTotalBytes, available: storageAvailableBytes, ); /// 根据总量和可用量计算受限于 0 到 1 的占用比例,容忍原生瞬时采样误差。 static double _usedRatio({required int total, required int available}) { if (total <= 0) return 0; return ((total - available) / total).clamp(0, 1).toDouble(); } } /// 统一管理虚拟机识别和硬件手动输入模式,避免各业务页面自行猜测设备能力。 abstract class AppRuntimeTool { static const MethodChannel _channel = MethodChannel( 'biz_xfj/runtime_environment', ); static AppRuntimeEnvironment _environment = const AppRuntimeEnvironment( isEmulator: false, displayCount: 1, androidVersion: '', androidSdkInt: 0, apkVersionName: '', apkVersionCode: 0, ); static bool _initialized = false; static bool _manualHardwareInput = false; static AppKioskStatus _kioskStatus = const AppKioskStatus.unavailable(); /// 请求 Android 终端执行短震动;通道不可用或平台不支持时回退到 Flutter 触觉反馈。 static Future vibrate({int durationMs = 60}) async { final safeDuration = durationMs.clamp(20, 200).toInt(); try { final handled = await _channel.invokeMethod( 'vibrate', {'durationMs': safeDuration}, ); if (handled == true) return; } on MissingPluginException { // 非 Android 平台没有运行时通道,继续使用 Flutter 提供的系统触觉能力。 } on PlatformException { // 厂商设备禁用震动或原生调用异常时,回退触觉不能影响弹框关闭控制。 } await HapticFeedback.mediumImpact(); } static bool get isEmulator => _environment.isEmulator; static int get displayCount => _environment.displayCount; /// 返回设置页使用的 Android 发行版本与 API Level,通道不可用时明确显示未知。 static String get androidVersionLabel { final version = _environment.androidVersion.trim(); final sdk = _environment.androidSdkInt; if (version.isNotEmpty && sdk > 0) return 'Android $version(API $sdk)'; if (version.isNotEmpty) return 'Android $version'; if (sdk > 0) return 'Android API $sdk'; return '未知'; } /// 返回当前实际安装 APK 的展示版本与 Build,字段缺失时仍保留已知部分。 static String get apkVersionLabel { final versionName = _environment.apkVersionName.trim(); final versionCode = _environment.apkVersionCode; if (versionName.isNotEmpty && versionCode > 0) { return '$versionName(Build $versionCode)'; } if (versionName.isNotEmpty) return versionName; if (versionCode > 0) return 'Build $versionCode'; return '未知'; } /// 虚拟机始终使用手动输入;真机可在设置中主动开启手动输入模式。 static bool get useManualHardwareInput => isEmulator || _manualHardwareInput; static bool get canUsePhysicalHardware => !useManualHardwareInput; static bool get hasMultipleDisplays => displayCount >= 2; static AppKioskStatus get kioskStatus => _kioskStatus; static Future initializePlatform() async { if (_initialized) return; try { final data = await _channel.invokeMapMethod( 'getRuntimeEnvironment', ); _environment = AppRuntimeEnvironment.fromMap(data); _kioskStatus = await refreshKioskStatus(); } on PlatformException { // 原生通道异常时按单屏处理,避免错误地启动副屏。 _environment = const AppRuntimeEnvironment( isEmulator: false, displayCount: 1, androidVersion: '', androidSdkInt: 0, apkVersionName: '', apkVersionCode: 0, ); _kioskStatus = const AppKioskStatus.unavailable(); } _initialized = true; } static Future refreshKioskStatus() async { try { final data = await _channel.invokeMapMethod( 'getKioskStatus', ); _kioskStatus = AppKioskStatus.fromMap(data); } on PlatformException { _kioskStatus = const AppKioskStatus.unavailable(); } return _kioskStatus; } /// 配置每次进入 App 时是否自动恢复 Kiosk,配置由 Android 原生层持久化。 static Future setKioskAutoEnterEnabled(bool enabled) async { try { final data = await _channel.invokeMapMethod( 'setKioskAutoEnterEnabled', {'enabled': enabled}, ); _kioskStatus = AppKioskStatus.fromMap(data); } on PlatformException { // 保留原状态,设置页会据此判断本次配置没有生效。 } return _kioskStatus; } /// 用户确认后通过 Root 请求 Android 配置当前应用为 Device Owner,并立即进入 Kiosk。 static Future enableKioskWithRoot() async { try { final data = await _channel.invokeMapMethod( 'enableKioskWithRoot', ); final provision = AppKioskProvisionResult.fromMap(data); _kioskStatus = provision.status; return provision; } on PlatformException catch (error) { return AppKioskProvisionResult( success: false, code: error.code, message: error.message ?? 'Kiosk 启用失败,请稍后重试', status: _kioskStatus, ); } } /// 执行带原生 5 秒超时的 `su -c id`,只有退出码为零且输出确认 uid=0 才返回可用。 static Future checkRootAccess() async { try { final data = await _channel.invokeMapMethod( 'checkRootAccess', ); return RootAccessCheckResult.fromMap(data); } on PlatformException catch (error) { return RootAccessCheckResult( status: RootAccessStatus.unavailable, checkedAt: DateTime.now(), diagnostic: error.message ?? 'Root 检查通道不可用', ); } } /// 由 Android PackageManager 校验 APK 包名、签名和目标版本码。 static Future validateApk({ required String path, required int expectedVersionCode, }) async { try { final data = await _channel.invokeMapMethod( 'validateApk', {'path': path, 'expectedVersionCode': expectedVersionCode}, ); return AppApkOperationResult.fromMap(data); } on PlatformException catch (error) { return AppApkOperationResult( success: false, code: error.code, message: error.message ?? 'APK 校验失败', ); } } /// 安装前由原生层再次校验 Root 与 APK,并执行不降级、不卸载数据的 `pm install -r`。 static Future installApkWithRoot({ required String path, required int expectedVersionCode, }) async { try { final data = await _channel.invokeMapMethod( 'installApkWithRoot', {'path': path, 'expectedVersionCode': expectedVersionCode}, ); return AppApkOperationResult.fromMap(data); } on PlatformException catch (error) { return AppApkOperationResult( success: false, code: error.code, message: error.message ?? 'APK 安装失败', ); } } /// 启动时消费原生待更新状态,确认版本后原生层同步清理私有 APK。 static Future consumePendingUpdate() async { try { final data = await _channel.invokeMapMethod( 'consumePendingUpdate', ); return AppUpdateRecoveryResult.fromMap(data); } on PlatformException catch (error) { return AppUpdateRecoveryResult( pending: false, success: false, message: error.message ?? '自动更新恢复检查失败', ); } } /// 只有原生层确认 Device Owner/Kiosk 可用时才允许退出到系统桌面。 static Future exitKioskToAndroidHome() async { if (!_kioskStatus.canExitToAndroidHome) return false; try { return await _channel.invokeMethod('exitKioskToHome') ?? false; } on PlatformException { return false; } } static Future getNetworkSnapshot() async { try { final data = await _channel.invokeMapMethod( 'getNetworkSnapshot', ); return AppNetworkSnapshot.fromMap(data); } on PlatformException { return const AppNetworkSnapshot( connected: false, type: 'offline', receivedBytes: 0, transmittedBytes: 0, ); } } /// 读取一次 Android 进程、内存和存储快照;通道失败时返回可安全展示的空快照。 static Future getPerformanceSnapshot() async { try { final data = await _channel.invokeMapMethod( 'getPerformanceSnapshot', ); return AppPerformanceSnapshot.fromMap(data); } on PlatformException { return const AppPerformanceSnapshot.empty(); } } /// 查询插件当前 Presentation 是否仍在展示,用于避免 showSubDisplay() 先关后开造成闪屏。 static Future isSubScreenShowing() async { try { return await _channel.invokeMethod('isSubScreenShowing') ?? false; } on PlatformException { return false; } } /// 单向转发业务状态到副屏 Engine,原生层转发后立即回包。 /// /// 锁定的双屏插件不会结束普通业务消息的主屏 MethodChannel 请求,因此人脸等 /// 硬件主链路必须使用此入口,避免等待副屏回包导致 Camera 初始化被永久阻塞。 static Future sendViceMessage( String method, { Map? params, }) async { return await _channel.invokeMethod('sendViceMessage', { 'method': method, 'params': params ?? {}, }) ?? false; } static void applyManualHardwareInput(bool enabled) { _manualHardwareInput = enabled; } }