fix: harden OriginOS recognition tracking
This commit is contained in:
@@ -197,8 +197,16 @@ class _MiaoJiAppState extends State<MiaoJiApp> with WidgetsBindingObserver {
|
||||
|
||||
Future<void> _restoreRecognitionServices() async {
|
||||
await RecognitionImportService.configureNativeContext();
|
||||
await ScreenshotChannel.waitForAccessibilityConnection();
|
||||
await ScreenshotChannel.ensureRecognitionKeepAlive();
|
||||
await RecognitionImportService.importAutomatic();
|
||||
unawaited(_runSafely(_importAfterAccessibilityReconnect));
|
||||
}
|
||||
|
||||
Future<void> _importAfterAccessibilityReconnect() async {
|
||||
final status = await ScreenshotChannel.waitForAccessibilityConnection();
|
||||
if (status.accessibilityConnected) {
|
||||
await RecognitionImportService.importAutomatic();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refreshRemoteState() async {
|
||||
@@ -231,6 +239,10 @@ class _MiaoJiAppState extends State<MiaoJiApp> with WidgetsBindingObserver {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 180));
|
||||
final context = _rootNavigatorKey.currentContext;
|
||||
if (!mounted || context == null || !context.mounted) return;
|
||||
if (action['action'] == 'open_recognition_settings') {
|
||||
router.push('/screenshot-settings');
|
||||
return;
|
||||
}
|
||||
await RecognitionImportService.handleAction(context, action);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,15 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
|
||||
Timer? _diagnosticRefreshTimer;
|
||||
Timer? _previewExpiryTimer;
|
||||
bool _diagnosticRefreshInFlight = false;
|
||||
bool _connectionCheckInFlight = false;
|
||||
bool _keepAliveStarting = false;
|
||||
bool _recentsProtectionNoticeChecked = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_check(waitForConnection: true);
|
||||
_check();
|
||||
_diagnosticRefreshTimer = Timer.periodic(
|
||||
const Duration(seconds: 2),
|
||||
(_) => _refreshRunningDiagnostic(),
|
||||
@@ -60,18 +63,16 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
|
||||
void _refreshRunningDiagnostic() {
|
||||
if (!mounted ||
|
||||
_diagnosticRefreshInFlight ||
|
||||
_status?.latestDiagnostic?.result != 'started') {
|
||||
!{'started', 'waiting'}.contains(_status?.latestDiagnostic?.result)) {
|
||||
return;
|
||||
}
|
||||
_diagnosticRefreshInFlight = true;
|
||||
_check().whenComplete(() => _diagnosticRefreshInFlight = false);
|
||||
}
|
||||
|
||||
Future<void> _check({bool waitForConnection = false}) async {
|
||||
Future<void> _check({bool monitorConnection = true}) async {
|
||||
try {
|
||||
var status = waitForConnection
|
||||
? await ScreenshotChannel.waitForAccessibilityConnection()
|
||||
: await ScreenshotChannel.recognitionStatus();
|
||||
var status = await ScreenshotChannel.recognitionStatus();
|
||||
final invalid = <String>[
|
||||
if (status.accessibilityEvents &&
|
||||
(!status.accessibilityAuthorized ||
|
||||
@@ -99,6 +100,14 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
|
||||
_error = null;
|
||||
});
|
||||
_schedulePreviewExpiry(status);
|
||||
unawaited(_showRecentsProtectionNotice(status));
|
||||
if (monitorConnection &&
|
||||
status.accessibilityAuthorized &&
|
||||
!status.accessibilityConnected &&
|
||||
!status.taskCleanerRecoveryNeeded &&
|
||||
(status.accessibilityEvents || status.aiScreenshot)) {
|
||||
unawaited(_monitorAccessibilityConnection());
|
||||
}
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
@@ -108,6 +117,49 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showRecentsProtectionNotice(RecognitionStatus status) async {
|
||||
if (_recentsProtectionNoticeChecked || !status.recentsProtectionActive) {
|
||||
return;
|
||||
}
|
||||
_recentsProtectionNoticeChecked = true;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
const key = 'originos_recents_protection_notice_v1';
|
||||
if (prefs.getBool(key) == true || !mounted) return;
|
||||
await prefs.setBool(key, true);
|
||||
if (!mounted) return;
|
||||
_showMessage('记之已从最近任务隐藏,可从桌面图标重新打开;关闭全部识别后恢复。');
|
||||
}
|
||||
|
||||
Future<void> _monitorAccessibilityConnection({
|
||||
bool ensureKeepAlive = false,
|
||||
}) async {
|
||||
if (_connectionCheckInFlight) return;
|
||||
_connectionCheckInFlight = true;
|
||||
if (mounted) setState(() {});
|
||||
try {
|
||||
if (ensureKeepAlive) {
|
||||
_keepAliveStarting = true;
|
||||
if (mounted) setState(() {});
|
||||
await ScreenshotChannel.ensureRecognitionKeepAlive();
|
||||
}
|
||||
await ScreenshotChannel.waitForAccessibilityConnection();
|
||||
final status = await ScreenshotChannel.recognitionStatus();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_status = status;
|
||||
_error = null;
|
||||
});
|
||||
_schedulePreviewExpiry(status);
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() => _error = '连接状态复核失败,请重试');
|
||||
} finally {
|
||||
_connectionCheckInFlight = false;
|
||||
_keepAliveStarting = false;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _schedulePreviewExpiry(RecognitionStatus status) {
|
||||
_previewExpiryTimer?.cancel();
|
||||
final expiresAt = status.ocrDiagnosticPreviewExpiresAt;
|
||||
@@ -182,7 +234,7 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
|
||||
|
||||
Future<void> _resumeAuthorization() async {
|
||||
if (_authorizing) return;
|
||||
await _check(waitForConnection: true);
|
||||
await _check();
|
||||
final key = _pendingAuthorizationKey;
|
||||
final status = _status;
|
||||
if (key == null || status == null) return;
|
||||
@@ -433,12 +485,31 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
|
||||
'仅在微信和支付宝疑似支付流程中读取可见文字,并按需在内存中进行本地截图 OCR;不保存图片、完整控件树,也不监听按键。',
|
||||
authorized: status!.accessibilityAuthorized,
|
||||
connected: status.accessibilityConnected,
|
||||
statusLabel: status.accessibilityConnectionLabel,
|
||||
statusLabel:
|
||||
_connectionCheckInFlight && !status.accessibilityConnected
|
||||
? '正在连接'
|
||||
: status.accessibilityConnectionLabel,
|
||||
recoveryMessage: status.accessibilityNeedsRecovery
|
||||
? status.taskCleanerRecoveryNeeded
|
||||
? 'OriginOS 清理了识别进程,系统仍保留授权,但无障碍服务已被标记故障。'
|
||||
: '系统仍显示已授权,但 OriginOS 未重新绑定服务。请前往系统设置,将记之无障碍服务关闭后重新开启。'
|
||||
: (status.accessibilityEvents || status.aiScreenshot) &&
|
||||
status.keepAliveNeedsRecovery
|
||||
? _keepAliveStarting
|
||||
? '正在启动后台保护…'
|
||||
: status.keepAliveError ?? '后台保护未运行,划掉应用后识别可能中断。'
|
||||
: null,
|
||||
onOpenSettings: ScreenshotChannel.openAccessibilitySettings,
|
||||
onRetry:
|
||||
status.accessibilityAuthorized &&
|
||||
!status.accessibilityConnected
|
||||
? () => _check(waitForConnection: true)
|
||||
settingsLabel: '前往无障碍设置',
|
||||
onRetry: status.accessibilityNeedsRecovery
|
||||
? () => _monitorAccessibilityConnection()
|
||||
: null,
|
||||
onStartKeepAlive:
|
||||
(status.accessibilityEvents || status.aiScreenshot) &&
|
||||
status.keepAliveNeedsRecovery &&
|
||||
!_keepAliveStarting
|
||||
? () =>
|
||||
_monitorAccessibilityConnection(ensureKeepAlive: true)
|
||||
: null,
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -546,7 +617,12 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
|
||||
status.notificationEvents ||
|
||||
status.aiScreenshot) ...[
|
||||
const SizedBox(height: 10),
|
||||
_BackgroundKeepAliveCard(status: status),
|
||||
_BackgroundKeepAliveCard(
|
||||
status: status,
|
||||
starting: _keepAliveStarting,
|
||||
onStart: () =>
|
||||
_monitorAccessibilityConnection(ensureKeepAlive: true),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
_RecognitionCard(
|
||||
@@ -844,8 +920,14 @@ class _EvidencePill extends StatelessWidget {
|
||||
|
||||
class _BackgroundKeepAliveCard extends StatelessWidget {
|
||||
final RecognitionStatus status;
|
||||
final bool starting;
|
||||
final VoidCallback onStart;
|
||||
|
||||
const _BackgroundKeepAliveCard({required this.status});
|
||||
const _BackgroundKeepAliveCard({
|
||||
required this.status,
|
||||
required this.starting,
|
||||
required this.onStart,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -883,9 +965,18 @@ class _BackgroundKeepAliveCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
status.batteryOptimizationIgnored ? '后台限制较少' : '需要设置',
|
||||
status.recentsProtectionActive
|
||||
? '任务清理防护中'
|
||||
: status.keepAliveRunning
|
||||
? '保护运行中'
|
||||
: status.keepAliveExpected
|
||||
? '保护未运行'
|
||||
: status.batteryOptimizationIgnored
|
||||
? '后台限制较少'
|
||||
: '需要设置',
|
||||
style: TextStyle(
|
||||
color: status.batteryOptimizationIgnored
|
||||
color:
|
||||
status.keepAliveRunning || status.recentsProtectionActive
|
||||
? AppTheme.primaryDeep
|
||||
: AppTheme.orange,
|
||||
fontSize: 10,
|
||||
@@ -896,15 +987,29 @@ class _BackgroundKeepAliveCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'无障碍和通知监听由 Android 系统持续绑定。请允许记之后台活动,'
|
||||
'${isVivo ? '并在 OriginOS 中开启自启动,' : ''}'
|
||||
'否则系统清理进程后可能暂时收不到支付事件。',
|
||||
status.recentsProtectionActive
|
||||
? 'OriginOS 最近任务保护已开启。记之不会显示在最近任务中,请从桌面图标重新打开;关闭全部识别后会自动恢复任务卡。'
|
||||
: '无障碍和通知监听由 Android 系统持续绑定。请允许记之后台活动,'
|
||||
'${isVivo ? '并在 OriginOS 中开启自启动,' : ''}'
|
||||
'否则系统清理进程后可能暂时收不到支付事件。',
|
||||
style: TextStyle(
|
||||
color: context.jz.text2,
|
||||
fontSize: 11.5,
|
||||
height: 1.55,
|
||||
),
|
||||
),
|
||||
if (status.keepAliveNeedsRecovery) ...[
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: JzActionButton(
|
||||
label: starting ? '正在启动…' : '启动后台保护',
|
||||
secondary: true,
|
||||
icon: const Icon(Icons.shield_outlined, size: 18),
|
||||
onPressed: starting ? null : onStart,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
@@ -941,6 +1046,9 @@ class _RecognitionCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
final VoidCallback? onOpenSettings;
|
||||
final VoidCallback? onRetry;
|
||||
final VoidCallback? onStartKeepAlive;
|
||||
final String? recoveryMessage;
|
||||
final String settingsLabel;
|
||||
|
||||
const _RecognitionCard({
|
||||
required this.icon,
|
||||
@@ -952,12 +1060,15 @@ class _RecognitionCard extends StatelessWidget {
|
||||
this.statusLabel,
|
||||
this.onOpenSettings,
|
||||
this.onRetry,
|
||||
this.onStartKeepAlive,
|
||||
this.recoveryMessage,
|
||||
this.settingsLabel = '前往系统设置',
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final palette = context.jz;
|
||||
final color = connected ? AppTheme.primary : AppTheme.ai;
|
||||
final color = connected ? AppTheme.primary : AppTheme.orange;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -976,7 +1087,7 @@ class _RecognitionCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: connected
|
||||
? palette.primaryBackground
|
||||
: palette.aiBackground,
|
||||
: palette.warningBackground,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 21),
|
||||
@@ -996,7 +1107,7 @@ class _RecognitionCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: connected
|
||||
? palette.primaryBackground
|
||||
: palette.aiBackground,
|
||||
: palette.warningBackground,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
@@ -1020,9 +1131,35 @@ class _RecognitionCard extends StatelessWidget {
|
||||
description,
|
||||
style: TextStyle(color: palette.text2, fontSize: 12, height: 1.55),
|
||||
),
|
||||
if (recoveryMessage != null) ...[
|
||||
const SizedBox(height: 9),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.info_outline_rounded,
|
||||
color: AppTheme.orange,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: Text(
|
||||
recoveryMessage!,
|
||||
style: TextStyle(
|
||||
color: palette.text2,
|
||||
fontSize: 11.5,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 6),
|
||||
child,
|
||||
if (onOpenSettings != null || onRetry != null) ...[
|
||||
if (onOpenSettings != null ||
|
||||
onRetry != null ||
|
||||
onStartKeepAlive != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
@@ -1034,11 +1171,17 @@ class _RecognitionCard extends StatelessWidget {
|
||||
icon: const Icon(Icons.refresh_rounded, size: 18),
|
||||
label: const Text('重新检测'),
|
||||
),
|
||||
if (onStartKeepAlive != null)
|
||||
TextButton.icon(
|
||||
onPressed: onStartKeepAlive,
|
||||
icon: const Icon(Icons.shield_outlined, size: 18),
|
||||
label: const Text('启动后台保护'),
|
||||
),
|
||||
if (onOpenSettings != null)
|
||||
TextButton.icon(
|
||||
onPressed: onOpenSettings,
|
||||
icon: const Icon(Icons.settings_outlined, size: 18),
|
||||
label: const Text('前往系统设置'),
|
||||
label: Text(settingsLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -49,6 +49,7 @@ class RecognitionDiagnosticDisplay {
|
||||
'matched' || 'auto_ready' => '已识别',
|
||||
'confirm' => '待确认',
|
||||
'started' => '处理中',
|
||||
'waiting' => '等待结果页',
|
||||
'failed' => '失败',
|
||||
_ => '未触发入账',
|
||||
};
|
||||
@@ -56,6 +57,7 @@ class RecognitionDiagnosticDisplay {
|
||||
|
||||
static String _summaryLabel(String result, String reason) {
|
||||
if (reason == 'duplicate_result_surface') return '已合并';
|
||||
if (result == 'waiting') return '等待结果页';
|
||||
if (_captureFailureReasons.contains(reason)) return '截图失败';
|
||||
if (_ocrNoResultReasons.contains(reason)) return 'OCR 无结果';
|
||||
if (_ruleRejectedReasons.contains(reason) || result == 'rejected') {
|
||||
@@ -65,6 +67,7 @@ class RecognitionDiagnosticDisplay {
|
||||
'matched' || 'auto_ready' => '已识别',
|
||||
'confirm' => '待确认',
|
||||
'started' => '处理中',
|
||||
'waiting' => '等待结果页',
|
||||
'failed' => '失败',
|
||||
_ => '没事件',
|
||||
};
|
||||
@@ -75,8 +78,9 @@ class RecognitionDiagnosticDisplay {
|
||||
'history_page' => '当前是账单或交易历史页',
|
||||
'blocked_status' => '当前状态为失败、处理中或已取消',
|
||||
'no_text' => '截图中没有识别到文字',
|
||||
'no_success_status' => '没有找到明确或弱完成状态,可开启诊断预览查看脱敏结果',
|
||||
'payment_input_page' => '当前仍是付款输入或确认页面,已拒绝入账',
|
||||
'no_success_status' => '暂未找到完成状态,正在按计划复核结果页',
|
||||
'payment_input_page' => '当前仍是付款输入或确认页面,等待结果页,不会提前入账',
|
||||
'result_page_timeout' => '90 秒内未等到明确结果页,本次流程已停止追踪',
|
||||
'direction_unknown' => '识别到完成状态,但无法确认收支方向',
|
||||
'missing_amount' => '成功状态已识别,但没有找到金额',
|
||||
'expected_amount_missing' => '结果页没有金额,且付款前金额不唯一或未捕获',
|
||||
@@ -167,8 +171,7 @@ class RecognitionDiagnosticDisplay {
|
||||
static const _ruleRejectedReasons = {
|
||||
'history_page',
|
||||
'blocked_status',
|
||||
'no_success_status',
|
||||
'payment_input_page',
|
||||
'result_page_timeout',
|
||||
'direction_unknown',
|
||||
'missing_amount',
|
||||
'expected_amount_missing',
|
||||
|
||||
@@ -95,6 +95,14 @@ class RecognitionStatus {
|
||||
final DateTime? accessibilityLastConnectedAt;
|
||||
final DateTime? accessibilityLastDisconnectedAt;
|
||||
final DateTime? recognitionProcessStartedAt;
|
||||
final bool keepAliveExpected;
|
||||
final bool keepAliveRunning;
|
||||
final String? keepAliveError;
|
||||
final bool recentsProtectionExpected;
|
||||
final bool recentsProtectionActive;
|
||||
final DateTime? lastRecognitionExitAt;
|
||||
final String? lastRecognitionExitReason;
|
||||
final bool taskCleanerRecoveryNeeded;
|
||||
final bool notificationAuthorized;
|
||||
final bool notificationConnected;
|
||||
final bool postNotificationsGranted;
|
||||
@@ -116,6 +124,14 @@ class RecognitionStatus {
|
||||
this.accessibilityLastConnectedAt,
|
||||
this.accessibilityLastDisconnectedAt,
|
||||
this.recognitionProcessStartedAt,
|
||||
this.keepAliveExpected = false,
|
||||
this.keepAliveRunning = false,
|
||||
this.keepAliveError,
|
||||
this.recentsProtectionExpected = false,
|
||||
this.recentsProtectionActive = false,
|
||||
this.lastRecognitionExitAt,
|
||||
this.lastRecognitionExitReason,
|
||||
this.taskCleanerRecoveryNeeded = false,
|
||||
required this.notificationAuthorized,
|
||||
required this.notificationConnected,
|
||||
required this.postNotificationsGranted,
|
||||
@@ -158,6 +174,17 @@ class RecognitionStatus {
|
||||
'accessibilityLastDisconnectedAt',
|
||||
),
|
||||
recognitionProcessStartedAt: epochDate('recognitionProcessStartedAt'),
|
||||
keepAliveExpected: value['keepAliveExpected'] as bool? ?? false,
|
||||
keepAliveRunning: value['keepAliveRunning'] as bool? ?? false,
|
||||
keepAliveError: value['keepAliveError']?.toString(),
|
||||
recentsProtectionExpected:
|
||||
value['recentsProtectionExpected'] as bool? ?? false,
|
||||
recentsProtectionActive:
|
||||
value['recentsProtectionActive'] as bool? ?? false,
|
||||
lastRecognitionExitAt: epochDate('lastRecognitionExitAt'),
|
||||
lastRecognitionExitReason: value['lastRecognitionExitReason']?.toString(),
|
||||
taskCleanerRecoveryNeeded:
|
||||
value['taskCleanerRecoveryNeeded'] as bool? ?? false,
|
||||
notificationAuthorized: value['notificationAuthorized'] as bool? ?? false,
|
||||
notificationConnected: value['notificationConnected'] as bool? ?? false,
|
||||
postNotificationsGranted:
|
||||
@@ -189,6 +216,8 @@ class RecognitionStatus {
|
||||
!accessibilityConnected &&
|
||||
accessibilityConnectionState == AccessibilityConnectionState.disconnected;
|
||||
|
||||
bool get keepAliveNeedsRecovery => keepAliveExpected && !keepAliveRunning;
|
||||
|
||||
String get accessibilityConnectionLabel =>
|
||||
switch (accessibilityConnectionState) {
|
||||
AccessibilityConnectionState.unauthorized => '未授权',
|
||||
@@ -320,6 +349,7 @@ class SpeechEvent {
|
||||
/// Android native capabilities for screenshots, AI progress and speech.
|
||||
class ScreenshotChannel {
|
||||
static const _channel = MethodChannel('com.miaoji/screenshot');
|
||||
static Future<RecognitionStatus>? _activeAccessibilityConnectionWait;
|
||||
static void Function(String path)? _screenshotReady;
|
||||
static void Function(String error)? _screenshotError;
|
||||
static void Function(SpeechEvent event)? _speechEvent;
|
||||
@@ -472,13 +502,32 @@ class ScreenshotChannel {
|
||||
static Future<RecognitionStatus> waitForAccessibilityConnection({
|
||||
Duration timeout = const Duration(seconds: 5),
|
||||
Duration interval = const Duration(milliseconds: 500),
|
||||
}) {
|
||||
final active = _activeAccessibilityConnectionWait;
|
||||
if (active != null) return active;
|
||||
final operation = _waitForAccessibilityConnection(
|
||||
timeout: timeout,
|
||||
interval: interval,
|
||||
);
|
||||
_activeAccessibilityConnectionWait = operation;
|
||||
return operation.whenComplete(() {
|
||||
if (identical(_activeAccessibilityConnectionWait, operation)) {
|
||||
_activeAccessibilityConnectionWait = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<RecognitionStatus> _waitForAccessibilityConnection({
|
||||
required Duration timeout,
|
||||
required Duration interval,
|
||||
}) async {
|
||||
var status = await recognitionStatus();
|
||||
final recognitionEnabled =
|
||||
status.accessibilityEvents || status.aiScreenshot;
|
||||
if (!recognitionEnabled ||
|
||||
!status.accessibilityAuthorized ||
|
||||
status.accessibilityConnected) {
|
||||
status.accessibilityConnected ||
|
||||
status.taskCleanerRecoveryNeeded) {
|
||||
return status;
|
||||
}
|
||||
final deadline = DateTime.now().add(timeout);
|
||||
@@ -487,6 +536,7 @@ class ScreenshotChannel {
|
||||
status = await recognitionStatus();
|
||||
if (!status.accessibilityAuthorized ||
|
||||
status.accessibilityConnected ||
|
||||
status.taskCleanerRecoveryNeeded ||
|
||||
!(status.accessibilityEvents || status.aiScreenshot)) {
|
||||
return status;
|
||||
}
|
||||
@@ -494,6 +544,15 @@ class ScreenshotChannel {
|
||||
return status;
|
||||
}
|
||||
|
||||
static Future<bool> ensureRecognitionKeepAlive() async {
|
||||
try {
|
||||
return await _channel.invokeMethod<bool>('ensureRecognitionKeepAlive') ??
|
||||
false;
|
||||
} on MissingPluginException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> clearRecognitionDiagnostic() async {
|
||||
try {
|
||||
return await _channel.invokeMethod<bool>('clearRecognitionDiagnostic') ??
|
||||
|
||||
Reference in New Issue
Block a user