fix: harden OriginOS recognition tracking
This commit is contained in:
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user