feat(kiosk): add android kiosk terminal mode support
完整实现收银终端的Android专用Kiosk模式支持,包括: - 添加原生设备管理与Kiosk模式核心逻辑 - 新增Flutter设置页的Kiosk相关配置与控制UI - 更新README添加部署命令 - 补充Android权限与组件声明
This commit is contained in:
1 parent
94f8c5e67b
commit
a0ad3a06ff
14 files changed
+557
-37
No files matched your search
@@ -18,6 +18,75 @@ class AppRuntimeEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<dynamic, dynamic>? 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,普通全屏不能保证无法退出;需在恢复出厂或未初始化的设备上部署。';
|
||||
}
|
||||
}
|
||||
|
||||
class AppNetworkSnapshot {
|
||||
final bool connected;
|
||||
final String type;
|
||||
@@ -53,6 +122,7 @@ abstract class AppRuntimeTool {
|
||||
);
|
||||
static bool _initialized = false;
|
||||
static bool _manualHardwareInput = false;
|
||||
static AppKioskStatus _kioskStatus = const AppKioskStatus.unavailable();
|
||||
|
||||
static bool get isEmulator => _environment.isEmulator;
|
||||
|
||||
@@ -65,6 +135,8 @@ abstract class AppRuntimeTool {
|
||||
|
||||
static bool get hasMultipleDisplays => displayCount >= 2;
|
||||
|
||||
static AppKioskStatus get kioskStatus => _kioskStatus;
|
||||
|
||||
static Future<void> initializePlatform() async {
|
||||
if (_initialized) return;
|
||||
try {
|
||||
@@ -72,16 +144,54 @@ abstract class AppRuntimeTool {
|
||||
'getRuntimeEnvironment',
|
||||
);
|
||||
_environment = AppRuntimeEnvironment.fromMap(data);
|
||||
_kioskStatus = await refreshKioskStatus();
|
||||
} on PlatformException {
|
||||
// 原生通道异常时按单屏处理,避免错误地启动副屏。
|
||||
_environment = const AppRuntimeEnvironment(
|
||||
isEmulator: false,
|
||||
displayCount: 1,
|
||||
);
|
||||
_kioskStatus = const AppKioskStatus.unavailable();
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
static Future<AppKioskStatus> refreshKioskStatus() async {
|
||||
try {
|
||||
final data = await _channel.invokeMapMethod<dynamic, dynamic>(
|
||||
'getKioskStatus',
|
||||
);
|
||||
_kioskStatus = AppKioskStatus.fromMap(data);
|
||||
} on PlatformException {
|
||||
_kioskStatus = const AppKioskStatus.unavailable();
|
||||
}
|
||||
return _kioskStatus;
|
||||
}
|
||||
|
||||
/// 配置每次进入 App 时是否自动恢复 Kiosk,配置由 Android 原生层持久化。
|
||||
static Future<AppKioskStatus> setKioskAutoEnterEnabled(bool enabled) async {
|
||||
try {
|
||||
final data = await _channel.invokeMapMethod<dynamic, dynamic>(
|
||||
'setKioskAutoEnterEnabled',
|
||||
{'enabled': enabled},
|
||||
);
|
||||
_kioskStatus = AppKioskStatus.fromMap(data);
|
||||
} on PlatformException {
|
||||
// 保留原状态,设置页会据此判断本次配置没有生效。
|
||||
}
|
||||
return _kioskStatus;
|
||||
}
|
||||
|
||||
/// 只有原生层确认 Device Owner/Kiosk 可用时才允许退出到系统桌面。
|
||||
static Future<bool> exitKioskToAndroidHome() async {
|
||||
if (!_kioskStatus.canExitToAndroidHome) return false;
|
||||
try {
|
||||
return await _channel.invokeMethod<bool>('exitKioskToHome') ?? false;
|
||||
} on PlatformException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<AppNetworkSnapshot> getNetworkSnapshot() async {
|
||||
try {
|
||||
final data = await _channel.invokeMapMethod<dynamic, dynamic>(
|
||||
|
||||
Reference in new issue
Block a user