feat: refine AI navigation and accessibility recovery

This commit is contained in:
2026-08-26 22:58:26 +08:00
parent c6f98b5445
commit 9ccbdfead4
15 changed files with 521 additions and 203 deletions
@@ -57,34 +57,34 @@
android:process=":recognition"
android:stopWithTask="false"/>
<service
android:name=".ScreenshotTileService"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/screenshot_tile_label"
android:process=":recognition"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<service
android:name=".ScreenshotTileService"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/screenshot_tile_label"
android:stopWithTask="false"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>
<service
android:name=".PaymentNotificationListenerService"
android:exported="true"
android:label="@string/notification_listener_label"
android:process=":recognition"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
android:name=".PaymentNotificationListenerService"
android:exported="true"
android:label="@string/notification_listener_label"
android:stopWithTask="false"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService"/>
</intent-filter>
</service>
<service
android:name=".ScreenshotAccessibilityService"
android:exported="true"
android:process=":recognition"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<service
android:name=".ScreenshotAccessibilityService"
android:exported="true"
android:stopWithTask="false"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
@@ -95,10 +95,9 @@
<provider
android:name=".RecognitionBridgeProvider"
android:authorities="${applicationId}.recognition.bridge"
android:exported="false"
android:grantUriPermissions="false"
android:process=":recognition"/>
android:authorities="${applicationId}.recognition.bridge"
android:exported="false"
android:grantUriPermissions="false"/>
<meta-data
android:name="flutterEmbedding"
@@ -778,8 +778,8 @@ class MainActivity : FlutterActivity() {
},
)
}
private fun recognitionStatus(): Map<String, Any?> {
val response = RecognitionBridge.call(
private fun recognitionStatus(): Map<String, Any?> {
val response = RecognitionBridge.call(
this,
RecognitionBridgeProvider.METHOD_STATUS,
)
@@ -787,13 +787,27 @@ class MainActivity : FlutterActivity() {
contentResolver,
"enabled_notification_listeners",
).orEmpty()
val notificationAuthorized = enabledListeners
val notificationAuthorized = enabledListeners
.split(':')
.mapNotNull(ComponentName::unflattenFromString)
.any { it == ComponentName(this, PaymentNotificationListenerService::class.java) }
return mapOf(
"accessibilityAuthorized" to isAccessibilityEnabledInSystem(),
"accessibilityConnected" to (response?.getBoolean("accessibilityConnected") == true),
.any { it == ComponentName(this, PaymentNotificationListenerService::class.java) }
val accessibilityAuthorized = isAccessibilityEnabledInSystem()
val accessibilityConnected = response?.getBoolean("accessibilityConnected") == true
val recognitionProcessStartedAt = response?.getLong("recognitionProcessStartedAt") ?: 0L
val accessibilityConnectionState = when {
!accessibilityAuthorized -> "unauthorized"
accessibilityConnected -> "connected"
recognitionProcessStartedAt > 0L &&
System.currentTimeMillis() - recognitionProcessStartedAt < 5_000L -> "reconnecting"
else -> "disconnected"
}
return mapOf(
"accessibilityAuthorized" to accessibilityAuthorized,
"accessibilityConnected" to accessibilityConnected,
"accessibilityConnectionState" to accessibilityConnectionState,
"accessibilityLastConnectedAt" to (response?.getLong("accessibilityLastConnectedAt") ?: 0L),
"accessibilityLastDisconnectedAt" to (response?.getLong("accessibilityLastDisconnectedAt") ?: 0L),
"recognitionProcessStartedAt" to recognitionProcessStartedAt,
"notificationAuthorized" to notificationAuthorized,
"notificationConnected" to (response?.getBoolean("notificationConnected") == true),
"postNotificationsGranted" to (
@@ -22,6 +22,9 @@ class RecognitionBridgeProvider : ContentProvider() {
return when (method) {
METHOD_STATUS -> Bundle().apply {
putBoolean("accessibilityConnected", ScreenshotAccessibilityService.isConnected)
putLong("accessibilityLastConnectedAt", ScreenshotAccessibilityService.lastConnectedAt)
putLong("accessibilityLastDisconnectedAt", ScreenshotAccessibilityService.lastDisconnectedAt)
putLong("recognitionProcessStartedAt", PROCESS_STARTED_AT)
putBoolean("notificationConnected", PaymentNotificationListenerService.isConnected)
putString("settings", RecognitionSettings.statusJson(appContext))
putString("latestStatus", RecognitionCoordinator.get(appContext).latestStatus())
@@ -143,6 +146,7 @@ class RecognitionBridgeProvider : ContentProvider() {
private data class CaptureResult(val path: String?, val error: String?)
companion object {
private val PROCESS_STARTED_AT = System.currentTimeMillis()
const val METHOD_STATUS = "status"
const val METHOD_SET_TOGGLE = "setToggle"
const val METHOD_SET_RUNTIME = "setRuntime"
@@ -78,6 +78,7 @@ class ScreenshotAccessibilityService : AccessibilityService() {
}
activeInstance = this
isConnected = true
lastConnectedAt = System.currentTimeMillis()
Log.i(TAG, "Accessibility recognition service connected")
}
@@ -363,6 +364,7 @@ class ScreenshotAccessibilityService : AccessibilityService() {
private fun disconnect() {
if (activeInstance === this) activeInstance = null
isConnected = false
lastDisconnectedAt = System.currentTimeMillis()
pendingRetry = null
retryTimeout = null
pendingVisualCapture = null
@@ -1270,6 +1272,14 @@ class ScreenshotAccessibilityService : AccessibilityService() {
var isConnected = false
private set
@Volatile
var lastConnectedAt = 0L
private set
@Volatile
var lastDisconnectedAt = 0L
private set
fun requestScreenshot(
showResult: Boolean,
delayMs: Long = 0L,