fix: harden OriginOS recognition tracking

This commit is contained in:
2026-08-31 11:29:04 +08:00
parent 9ccbdfead4
commit 477f83c5b1
17 changed files with 980 additions and 108 deletions
@@ -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') ??