feat: add status pull task check (#98)
This commit is contained in:
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "cn.ilapage.goauto.agent"
|
||||
minSdk = 23
|
||||
targetSdk = 34
|
||||
versionCode = 10
|
||||
versionName = "0.6.0"
|
||||
versionCode = 11
|
||||
versionName = "0.7.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -49,6 +49,7 @@ android {
|
||||
dependencies {
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.8.5")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
|
||||
+45
-8
@@ -63,6 +63,7 @@ class AgentForegroundService : Service() {
|
||||
private val taskMutex = TaskExecutionMutex()
|
||||
private val runningTaskId = AtomicReference<Long?>(null)
|
||||
private val working = AtomicBoolean(false)
|
||||
private val manualCheckRequested = AtomicBoolean(false)
|
||||
private val registeredThisProcess = AtomicBoolean(false)
|
||||
private val taskWakeActive = AtomicBoolean(false)
|
||||
private val idleReturn = IdleReturnCoordinator()
|
||||
@@ -101,6 +102,7 @@ class AgentForegroundService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent?.action == ACTION_RECONNECT) registeredThisProcess.set(false)
|
||||
if (intent?.action == ACTION_CHECK_NOW) manualCheckRequested.set(true)
|
||||
triggerSync()
|
||||
return START_STICKY
|
||||
}
|
||||
@@ -120,16 +122,19 @@ class AgentForegroundService : Service() {
|
||||
private fun triggerSync() {
|
||||
if (!working.compareAndSet(false, true)) return
|
||||
executor.execute {
|
||||
val manualCheck = manualCheckRequested.getAndSet(false)
|
||||
try {
|
||||
synchronizeAgent()
|
||||
val outcome = synchronizeAgent()
|
||||
if (manualCheck) publishManualCheckResult(outcome)
|
||||
} finally {
|
||||
working.set(false)
|
||||
if (manualCheckRequested.get()) triggerSync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun synchronizeAgent() {
|
||||
try {
|
||||
private fun synchronizeAgent(): String {
|
||||
return try {
|
||||
val serverUrl = ServerUrlPolicy.normalize(settingsStore.serverUrl(), BuildConfig.DEBUG)
|
||||
val api = AgentApiClient(serverUrl)
|
||||
var credentials = identityStore.credentials()
|
||||
@@ -183,6 +188,7 @@ class AgentForegroundService : Service() {
|
||||
tokenStored = storedCredentials != null,
|
||||
)
|
||||
updateNotification(if (authenticationError) "设备认证失败" else "连接失败,等待网络恢复")
|
||||
if (authenticationError) MANUAL_AUTH_ERROR else MANUAL_NETWORK_ERROR
|
||||
} catch (error: Exception) {
|
||||
cancelIdleReturn("Agent 运行异常")
|
||||
val configured = settingsStore.serverUrl().isNotBlank()
|
||||
@@ -194,26 +200,27 @@ class AgentForegroundService : Service() {
|
||||
tokenStored = storedCredentials != null,
|
||||
)
|
||||
updateNotification(if (configured) "运行异常" else "等待配置服务端")
|
||||
if (configured) MANUAL_ERROR else MANUAL_CONFIG_REQUIRED
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleTask(api: AgentApiClient, token: String) {
|
||||
if (taskMutex.currentTaskId() != null) return
|
||||
private fun scheduleTask(api: AgentApiClient, token: String): String {
|
||||
if (taskMutex.currentTaskId() != null) return MANUAL_BUSY
|
||||
recoverInterruptedPurchases(api, token)
|
||||
flushPurchaseOutbox(api, token)
|
||||
val purchaseTask = api.nextPurchaseTask(token)
|
||||
if (purchaseTask != null) {
|
||||
cancelIdleReturn("收到新的采购任务")
|
||||
schedulePurchaseTask(api, purchaseTask, token)
|
||||
return
|
||||
return MANUAL_PURCHASE_TASK
|
||||
}
|
||||
val task = api.nextTask(token)
|
||||
if (task == null) {
|
||||
evaluateIdleReturn()
|
||||
return
|
||||
return MANUAL_EMPTY
|
||||
}
|
||||
cancelIdleReturn("收到新的采集任务")
|
||||
if (!taskMutex.tryAcquire(task.taskId)) return
|
||||
if (!taskMutex.tryAcquire(task.taskId)) return MANUAL_BUSY
|
||||
if (task.status == "running") runningTaskId.set(task.taskId)
|
||||
stateStore.setActiveTask(task.taskId, "collection")
|
||||
taskExecutor.execute {
|
||||
@@ -226,6 +233,14 @@ class AgentForegroundService : Service() {
|
||||
triggerSync()
|
||||
}
|
||||
}
|
||||
return MANUAL_COLLECTION_TASK
|
||||
}
|
||||
|
||||
private fun publishManualCheckResult(result: String) {
|
||||
sendBroadcast(Intent(ACTION_CHECK_RESULT).apply {
|
||||
setPackage(packageName)
|
||||
putExtra(EXTRA_CHECK_RESULT, result)
|
||||
})
|
||||
}
|
||||
|
||||
private fun schedulePurchaseTask(api: AgentApiClient, task: PurchaseAgentTask, token: String) {
|
||||
@@ -669,6 +684,17 @@ class AgentForegroundService : Service() {
|
||||
|
||||
companion object {
|
||||
const val ACTION_RECONNECT = "cn.ilapage.goauto.agent.RECONNECT"
|
||||
const val ACTION_CHECK_NOW = "cn.ilapage.goauto.agent.CHECK_NOW"
|
||||
const val ACTION_CHECK_RESULT = "cn.ilapage.goauto.agent.CHECK_RESULT"
|
||||
const val EXTRA_CHECK_RESULT = "check_result"
|
||||
const val MANUAL_EMPTY = "empty"
|
||||
const val MANUAL_COLLECTION_TASK = "collection_task"
|
||||
const val MANUAL_PURCHASE_TASK = "purchase_task"
|
||||
const val MANUAL_BUSY = "busy"
|
||||
const val MANUAL_CONFIG_REQUIRED = "config_required"
|
||||
const val MANUAL_AUTH_ERROR = "auth_error"
|
||||
const val MANUAL_NETWORK_ERROR = "network_error"
|
||||
const val MANUAL_ERROR = "error"
|
||||
private const val CHANNEL_ID = "agent_connection"
|
||||
private const val NOTIFICATION_ID = 1001
|
||||
private const val HEARTBEAT_SECONDS = 15L
|
||||
@@ -686,6 +712,17 @@ class AgentForegroundService : Service() {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkNow(context: Context) {
|
||||
val intent = Intent(context, AgentForegroundService::class.java).apply {
|
||||
action = ACTION_CHECK_NOW
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,88 +1,152 @@
|
||||
package cn.ilapage.goauto.agent.ui
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import cn.ilapage.goauto.agent.R
|
||||
import cn.ilapage.goauto.agent.identity.SecureDeviceStore
|
||||
import cn.ilapage.goauto.agent.service.AgentForegroundService
|
||||
import cn.ilapage.goauto.agent.service.AgentSettingsStore
|
||||
import cn.ilapage.goauto.agent.service.AgentStateStore
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
|
||||
internal object ManualTaskCheckPolicy {
|
||||
fun blockedMessage(stateCode: String, currentTaskId: Long?, readiness: AccessibilityReadiness): String? = when {
|
||||
currentTaskId != null || stateCode == "BUSY" -> "当前任务执行中,无需重复检查"
|
||||
readiness != AccessibilityReadiness.READY -> "请先到“设置”开启采集采购助手"
|
||||
stateCode == "CONFIG_REQUIRED" -> "请先到“设置”配置服务地址"
|
||||
stateCode == "AUTH_ERROR" -> "设备身份校验失败,请先检查设置"
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun resultMessage(result: String): String = when (result) {
|
||||
AgentForegroundService.MANUAL_EMPTY -> "暂无新任务,Agent 会继续自动检查"
|
||||
AgentForegroundService.MANUAL_COLLECTION_TASK -> "已领取采集任务"
|
||||
AgentForegroundService.MANUAL_PURCHASE_TASK -> "已领取采购任务"
|
||||
AgentForegroundService.MANUAL_BUSY -> "当前任务执行中,无需重复检查"
|
||||
AgentForegroundService.MANUAL_CONFIG_REQUIRED -> "请先到“设置”配置服务地址"
|
||||
AgentForegroundService.MANUAL_AUTH_ERROR -> "设备身份校验失败,请先检查设置"
|
||||
AgentForegroundService.MANUAL_NETWORK_ERROR -> "检查失败,Agent 将继续自动重试"
|
||||
else -> "检查失败,Agent 将继续自动重试"
|
||||
}
|
||||
}
|
||||
|
||||
class AgentStatusFragment : Fragment() {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private lateinit var stateStore: AgentStateStore
|
||||
private lateinit var settingsStore: AgentSettingsStore
|
||||
private lateinit var identityStore: SecureDeviceStore
|
||||
private lateinit var refreshLayout: SwipeRefreshLayout
|
||||
private lateinit var manualFeedback: TextView
|
||||
private lateinit var connectionTitle: TextView
|
||||
private lateinit var connectionDetail: TextView
|
||||
private lateinit var taskText: TextView
|
||||
private lateinit var accessibilityTitle: TextView
|
||||
private lateinit var accessibilityDetail: TextView
|
||||
private lateinit var deviceText: TextView
|
||||
private var receiverRegistered = false
|
||||
|
||||
private val refresh = object : Runnable {
|
||||
override fun run() {
|
||||
refreshStatus()
|
||||
handler.postDelayed(this, REFRESH_MILLIS)
|
||||
}
|
||||
}
|
||||
private val manualTimeout = Runnable {
|
||||
if (::refreshLayout.isInitialized && refreshLayout.isRefreshing) {
|
||||
finishManualCheck("检查超时,Agent 将继续自动重试", error = true)
|
||||
}
|
||||
}
|
||||
private val hideFeedback = Runnable {
|
||||
if (::manualFeedback.isInitialized && !refreshLayout.isRefreshing) manualFeedback.visibility = View.GONE
|
||||
}
|
||||
private val checkReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action != AgentForegroundService.ACTION_CHECK_RESULT) return
|
||||
val result = intent.getStringExtra(AgentForegroundService.EXTRA_CHECK_RESULT).orEmpty()
|
||||
finishManualCheck(
|
||||
ManualTaskCheckPolicy.resultMessage(result),
|
||||
error = result in setOf(
|
||||
AgentForegroundService.MANUAL_AUTH_ERROR,
|
||||
AgentForegroundService.MANUAL_CONFIG_REQUIRED,
|
||||
AgentForegroundService.MANUAL_NETWORK_ERROR,
|
||||
AgentForegroundService.MANUAL_ERROR,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
stateStore = AgentStateStore(requireContext())
|
||||
settingsStore = AgentSettingsStore(requireContext())
|
||||
identityStore = SecureDeviceStore(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: android.view.LayoutInflater, container: ViewGroup?, state: Bundle?): View {
|
||||
val context = requireContext()
|
||||
val content = context.column().apply {
|
||||
addView(context.screenTitle("设备状态", "查看连接、任务与无障碍是否就绪"))
|
||||
addView(context.screenTitle("设备状态", "在页面顶部下拉可立即检查新任务"))
|
||||
deviceText = context.label("正在读取设备信息", 13f, context.getColor(R.color.agent_text_muted), true)
|
||||
addView(deviceText, deviceText.fullWidth())
|
||||
manualFeedback = context.label("", 14f, context.getColor(R.color.agent_primary_light), true).apply {
|
||||
visibility = View.GONE
|
||||
setPadding(0, context.dp(12), 0, context.dp(12))
|
||||
}
|
||||
addView(manualFeedback)
|
||||
addView(context.card(context.cardColumn().apply {
|
||||
addView(context.label("服务端连接", 14f, context.getColor(R.color.agent_text_muted)))
|
||||
connectionTitle = context.label("正在读取", 21f, context.getColor(R.color.agent_warning), true)
|
||||
connectionDetail = context.label("—", 14f, context.getColor(R.color.agent_text_muted))
|
||||
connectionDetail.setPadding(0, context.dp(6), 0, 0)
|
||||
connectionDetail = context.label("—", 14f, context.getColor(R.color.agent_text_muted)).apply {
|
||||
setPadding(0, context.dp(6), 0, 0)
|
||||
}
|
||||
addView(connectionTitle)
|
||||
addView(connectionDetail)
|
||||
}))
|
||||
addView(context.card(context.cardColumn().apply {
|
||||
addView(context.label("当前任务", 14f, context.getColor(R.color.agent_text_muted)))
|
||||
taskText = context.label("设备空闲", 18f, context.getColor(R.color.agent_text), true)
|
||||
taskText.setPadding(0, context.dp(6), 0, 0)
|
||||
taskText = context.label("等待任务", 18f, context.getColor(R.color.agent_text), true).apply {
|
||||
setPadding(0, context.dp(6), 0, 0)
|
||||
}
|
||||
addView(taskText)
|
||||
}))
|
||||
addView(context.card(context.cardColumn().apply {
|
||||
addView(context.label("无障碍服务", 14f, context.getColor(R.color.agent_text_muted)))
|
||||
addView(context.label("无障碍", 14f, context.getColor(R.color.agent_text_muted)))
|
||||
accessibilityTitle = context.label("正在检查", 18f, context.getColor(R.color.agent_warning), true)
|
||||
accessibilityDetail = context.label("—", 14f, context.getColor(R.color.agent_text_muted))
|
||||
accessibilityDetail.setPadding(0, context.dp(6), 0, 0)
|
||||
accessibilityDetail = context.label("—", 14f, context.getColor(R.color.agent_text_muted)).apply {
|
||||
setPadding(0, context.dp(6), 0, 0)
|
||||
}
|
||||
addView(accessibilityTitle)
|
||||
addView(accessibilityDetail)
|
||||
addView(MaterialButton(context).apply {
|
||||
text = "打开无障碍设置"
|
||||
contentDescription = "打开系统无障碍设置"
|
||||
minHeight = context.dp(48)
|
||||
setOnClickListener { startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)) }
|
||||
}, fullWidth(12))
|
||||
}))
|
||||
addView(context.card(context.cardColumn().apply {
|
||||
addView(context.label("设备信息", 14f, context.getColor(R.color.agent_text_muted)))
|
||||
deviceText = context.label("—", 14f, context.getColor(R.color.agent_text))
|
||||
deviceText.setPadding(0, context.dp(6), 0, 0)
|
||||
addView(deviceText)
|
||||
}))
|
||||
}
|
||||
return context.page(content)
|
||||
refreshLayout = SwipeRefreshLayout(context).apply {
|
||||
setColorSchemeResources(R.color.agent_primary_light)
|
||||
setProgressBackgroundColorSchemeResource(R.color.agent_surface)
|
||||
setOnRefreshListener(::checkNow)
|
||||
addView(context.page(content), ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
))
|
||||
}
|
||||
return refreshLayout
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
registerCheckReceiver()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
unregisterCheckReceiver()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -98,56 +162,108 @@ class AgentStatusFragment : Fragment() {
|
||||
|
||||
override fun onDestroyView() {
|
||||
handler.removeCallbacks(refresh)
|
||||
handler.removeCallbacks(manualTimeout)
|
||||
handler.removeCallbacks(hideFeedback)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun checkNow() {
|
||||
val state = stateStore.read()
|
||||
val blocked = ManualTaskCheckPolicy.blockedMessage(
|
||||
state.code,
|
||||
state.currentTaskId,
|
||||
AccessibilityReadinessDetector.current(requireContext()),
|
||||
)
|
||||
if (blocked != null) {
|
||||
finishManualCheck(blocked, error = true)
|
||||
return
|
||||
}
|
||||
showManualFeedback("正在检查新任务…", error = false)
|
||||
handler.removeCallbacks(manualTimeout)
|
||||
handler.postDelayed(manualTimeout, MANUAL_TIMEOUT_MILLIS)
|
||||
AgentForegroundService.checkNow(requireContext())
|
||||
}
|
||||
|
||||
private fun finishManualCheck(message: String, error: Boolean) {
|
||||
handler.removeCallbacks(manualTimeout)
|
||||
if (::refreshLayout.isInitialized) refreshLayout.isRefreshing = false
|
||||
showManualFeedback(message, error)
|
||||
refreshStatus()
|
||||
handler.removeCallbacks(hideFeedback)
|
||||
handler.postDelayed(hideFeedback, FEEDBACK_MILLIS)
|
||||
}
|
||||
|
||||
private fun showManualFeedback(message: String, error: Boolean) {
|
||||
manualFeedback.text = message
|
||||
manualFeedback.setTextColor(requireContext().getColor(if (error) R.color.agent_warning else R.color.agent_primary_light))
|
||||
manualFeedback.visibility = View.VISIBLE
|
||||
manualFeedback.announceForAccessibility(message)
|
||||
}
|
||||
|
||||
private fun refreshStatus() {
|
||||
if (!isAdded || view == null) return
|
||||
val context = requireContext()
|
||||
val state = stateStore.read()
|
||||
val connected = state.code in setOf("ONLINE", "BUSY")
|
||||
connectionTitle.text = when (state.code) {
|
||||
"ONLINE" -> "已连接 · 设备空闲"
|
||||
"BUSY" -> "已连接 · 正在执行"
|
||||
"ONLINE" -> "在线 · 空闲"
|
||||
"BUSY" -> "在线 · 执行中"
|
||||
"CONNECTING" -> "正在连接"
|
||||
"CONFIG_REQUIRED" -> "等待配置服务端"
|
||||
"AUTH_ERROR" -> "设备身份校验失败"
|
||||
"CONFIG_REQUIRED" -> "等待配置"
|
||||
"AUTH_ERROR" -> "身份校验失败"
|
||||
else -> "连接异常"
|
||||
}
|
||||
connectionTitle.setTextColor(context.getColor(if (connected) R.color.agent_primary_light else R.color.agent_warning))
|
||||
val heartbeat = state.lastHeartbeatAt.takeIf { it > 0L }?.let {
|
||||
DateFormat.getDateTimeInstance().format(Date(it))
|
||||
} ?: "尚无成功心跳"
|
||||
} ?: "尚无心跳"
|
||||
connectionDetail.text = "${state.message}\n最近心跳:$heartbeat"
|
||||
taskText.text = state.currentTaskId?.let { id ->
|
||||
val type = if (state.currentTaskType == "purchase") "采购" else "采集"
|
||||
"正在执行${type}任务 ${if (type == "采购") "CG-" else "#"}$id"
|
||||
} ?: "设备空闲,等待新任务"
|
||||
|
||||
val purchase = state.currentTaskType == "purchase"
|
||||
"${if (purchase) "采购" else "采集"}任务 ${if (purchase) "CG-" else "#"}$id"
|
||||
} ?: "等待任务"
|
||||
when (AccessibilityReadinessDetector.current(context)) {
|
||||
AccessibilityReadiness.READY -> {
|
||||
accessibilityTitle.text = "已开启并就绪"
|
||||
accessibilityTitle.text = "已开启"
|
||||
accessibilityTitle.setTextColor(context.getColor(R.color.agent_primary_light))
|
||||
accessibilityDetail.text = "服务已绑定,可以执行采集和采购任务。"
|
||||
accessibilityDetail.text = "可以执行任务"
|
||||
}
|
||||
AccessibilityReadiness.ENABLED_WAITING -> {
|
||||
accessibilityTitle.text = "已开启,等待服务连接"
|
||||
accessibilityTitle.text = "等待连接"
|
||||
accessibilityTitle.setTextColor(context.getColor(R.color.agent_warning))
|
||||
accessibilityDetail.text = "系统开关已打开,但 Agent 尚未连接服务。请返回本页稍候。"
|
||||
accessibilityDetail.text = "请到“设置”检查无障碍服务"
|
||||
}
|
||||
AccessibilityReadiness.DISABLED -> {
|
||||
accessibilityTitle.text = "未开启"
|
||||
accessibilityTitle.setTextColor(context.getColor(R.color.agent_error))
|
||||
accessibilityDetail.text = "请打开 GoAuto 采集服务,否则任务会失败。"
|
||||
accessibilityDetail.text = "请到“设置”开启"
|
||||
}
|
||||
}
|
||||
val displayName = settingsStore.deviceName().ifBlank { "本机" }
|
||||
deviceText.text = "$displayName · ${state.deviceId.takeIf { it > 0 }?.let { "#$it" } ?: "未注册"}"
|
||||
}
|
||||
|
||||
val installId = runCatching { identityStore.installId() }.getOrElse { "读取失败" }
|
||||
val displayName = settingsStore.deviceName().ifBlank { "使用手机型号" }
|
||||
deviceText.text = "设备名称:$displayName\ninstallId:$installId\n设备编号:${state.deviceId.takeIf { it > 0 } ?: "未注册"}"
|
||||
private fun registerCheckReceiver() {
|
||||
if (receiverRegistered) return
|
||||
val filter = IntentFilter(AgentForegroundService.ACTION_CHECK_RESULT)
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
requireContext().registerReceiver(checkReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
requireContext().registerReceiver(checkReceiver, filter)
|
||||
}
|
||||
receiverRegistered = true
|
||||
}
|
||||
|
||||
private fun unregisterCheckReceiver() {
|
||||
if (!receiverRegistered) return
|
||||
runCatching { requireContext().unregisterReceiver(checkReceiver) }
|
||||
receiverRegistered = false
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val REFRESH_MILLIS = 1_000L
|
||||
const val MANUAL_TIMEOUT_MILLIS = 20_000L
|
||||
const val FEEDBACK_MILLIS = 3_000L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.ilapage.goauto.agent
|
||||
|
||||
import cn.ilapage.goauto.agent.ui.AccessibilityReadiness
|
||||
import cn.ilapage.goauto.agent.ui.AccessibilityReadinessResolver
|
||||
import cn.ilapage.goauto.agent.ui.ManualTaskCheckPolicy
|
||||
import cn.ilapage.goauto.agent.ui.SettingsAvailabilityResolver
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
@@ -37,4 +38,25 @@ class AccessibilityReadinessTest {
|
||||
assertEquals(false, SettingsAvailabilityResolver.editable("ONLINE", null, testing = true))
|
||||
assertEquals(true, SettingsAvailabilityResolver.editable("ONLINE", null, testing = false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun manualCheckExplainsBusyAndAccessibilityBlocks() {
|
||||
assertEquals(
|
||||
"当前任务执行中,无需重复检查",
|
||||
ManualTaskCheckPolicy.blockedMessage("BUSY", 35L, AccessibilityReadiness.READY),
|
||||
)
|
||||
assertEquals(
|
||||
"请先到“设置”开启采集采购助手",
|
||||
ManualTaskCheckPolicy.blockedMessage("ONLINE", null, AccessibilityReadiness.DISABLED),
|
||||
)
|
||||
assertEquals(null, ManualTaskCheckPolicy.blockedMessage("ONLINE", null, AccessibilityReadiness.READY))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun manualCheckUsesPlainResultMessages() {
|
||||
assertEquals("暂无新任务,Agent 会继续自动检查", ManualTaskCheckPolicy.resultMessage("empty"))
|
||||
assertEquals("已领取采集任务", ManualTaskCheckPolicy.resultMessage("collection_task"))
|
||||
assertEquals("已领取采购任务", ManualTaskCheckPolicy.resultMessage("purchase_task"))
|
||||
assertEquals("检查失败,Agent 将继续自动重试", ManualTaskCheckPolicy.resultMessage("network_error"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Architecture-and-Code-Map
|
||||
wiki_url: https://git.ilapage.cn/OPC/goauto/wiki/Architecture-and-Code-Map.-
|
||||
wiki_revision: ca5286cc8b94d2338109552599da8a91b339127c
|
||||
synchronized_at: 2026-08-26T06:52:02Z
|
||||
wiki_revision: 18b8c41f545d2c2e883913a57647680a87a570b6
|
||||
synchronized_at: 2026-08-26T07:52:55Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 架构与代码地图
|
||||
@@ -191,3 +191,11 @@ Android Portal/Agent
|
||||
- Android 8.0 及以上的 `GoAutoAccessibilityService` 请求系统 `flagRequestAccessibilityButton`,服务连接后向 `AccessibilityButtonController` 注册单一回调,销毁时注销;服务重连不会叠加回调。
|
||||
- 单击由系统分配给 GoAuto 的辅助功能按钮时,只调用既有 `openAgentStatus()`,以前台 `MainActivity` 打开“状态”Tab;不领取、创建、重置或重试任务,不打开 PDD,也不改变 15 秒空闲返回规则。
|
||||
- 不支持或未分配系统辅助功能按钮的 ROM 保持安全降级;项目不创建自定义悬浮窗。
|
||||
|
||||
## Android Agent 状态页手动检查(#98)
|
||||
|
||||
- `AgentStatusFragment` 使用 Android 原生下拉刷新容器;只有状态页顶部下拉会请求一次立即检查,采集/采购历史页的下拉只刷新记录,两者语义不同。
|
||||
- `AgentForegroundService.checkNow` 只向既有单线程同步与调度入口提交一个可合并请求;不建立第二套轮询器,也不改变 15 秒自动轮询、采购优先、设备互斥或服务端租约。
|
||||
- 手动请求在已有同步运行时保留一个待处理标记,当前同步结束后再执行一次;重复手势不会并发请求。
|
||||
- 结果通过仅限当前应用包的广播返回状态页,区分无任务、采集任务、采购任务、设备忙、配置/认证和网络错误。状态页无障碍信息只读,唯一系统设置入口保留在设置 Tab。
|
||||
- 手动检查不创建、重置或重试任务,不绕过无障碍与身份校验,不打开 PDD,不修改地址、不创建订单、不支付。
|
||||
|
||||
Reference in New Issue
Block a user