fix: harden OriginOS recognition tracking
This commit is contained in:
@@ -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