feat(android): add Pinduoduo search probe
This commit is contained in:
@@ -42,15 +42,23 @@ import com.roubao.autopilot.vlm.GUIOwlClient
|
||||
import com.roubao.autopilot.vlm.MAIUIClient
|
||||
import com.roubao.autopilot.vlm.VLMClient
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import rikka.shizuku.Shizuku
|
||||
import android.util.Log
|
||||
import com.roubao.autopilot.pinduoduo.AndroidPinduoduoUiDriver
|
||||
import com.roubao.autopilot.pinduoduo.PinduoduoSearchAutomation
|
||||
import com.roubao.autopilot.pinduoduo.PinduoduoSearchWorkflow
|
||||
import com.roubao.autopilot.workflow.WorkflowReport
|
||||
import com.roubao.autopilot.workflow.WorkflowRunner
|
||||
import com.roubao.autopilot.workflow.WorkflowState
|
||||
|
||||
private const val TAG = "MainActivity"
|
||||
|
||||
sealed class Screen(val route: String, val title: String, val icon: ImageVector, val selectedIcon: ImageVector) {
|
||||
object Device : Screen("device", "设备", Icons.Outlined.Build, Icons.Filled.Build)
|
||||
object Home : Screen("home", "肉包", Icons.Outlined.Home, Icons.Filled.Home)
|
||||
object Home : Screen("home", "探针", Icons.Outlined.Search, Icons.Filled.Search)
|
||||
object History : Screen("history", "记录", Icons.Outlined.List, Icons.Filled.List)
|
||||
object Settings : Screen("settings", "设置", Icons.Outlined.Settings, Icons.Filled.Settings)
|
||||
}
|
||||
@@ -65,6 +73,11 @@ class MainActivity : ComponentActivity() {
|
||||
private val mobileAgent = mutableStateOf<MobileAgent?>(null)
|
||||
private var shizukuAvailable = mutableStateOf(false)
|
||||
private val readinessSnapshot = mutableStateOf(DeviceReadinessSnapshot.empty())
|
||||
private val searchProbeState = mutableStateOf(WorkflowState.IDLE)
|
||||
private val searchProbeStepId = mutableStateOf<String?>(null)
|
||||
private val searchProbeReport = mutableStateOf<WorkflowReport?>(null)
|
||||
private var searchProbeRunner: WorkflowRunner? = null
|
||||
private var searchProbeJob: Job? = null
|
||||
|
||||
// 当前执行的协程 Job(用于停止任务)
|
||||
private var currentExecutionJob: kotlinx.coroutines.Job? = null
|
||||
@@ -172,21 +185,19 @@ class MainActivity : ComponentActivity() {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainApp() {
|
||||
var currentScreen by remember { mutableStateOf<Screen>(Screen.Device) }
|
||||
var currentScreen by remember { mutableStateOf<Screen>(Screen.Home) }
|
||||
var selectedRecord by remember { mutableStateOf<ExecutionRecord?>(null) }
|
||||
var showShizukuHelpDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val settings by settingsManager.settings.collectAsState()
|
||||
val colors = BaoziTheme.colors
|
||||
val agent = mobileAgent.value
|
||||
val agentState by agent?.state?.collectAsState() ?: remember { mutableStateOf(null) }
|
||||
val logs by agent?.logs?.collectAsState() ?: remember { mutableStateOf(emptyList<String>()) }
|
||||
val records by remember { executionRecords }
|
||||
val isShizukuAvailable = shizukuAvailable.value && checkShizukuPermission()
|
||||
val executing by remember { isExecuting }
|
||||
val navigateToRecord by remember { shouldNavigateToRecord }
|
||||
val recordId by remember { currentRecordId }
|
||||
val readiness by remember { readinessSnapshot }
|
||||
val probeState by remember { searchProbeState }
|
||||
val probeStepId by remember { searchProbeStepId }
|
||||
val probeReport by remember { searchProbeReport }
|
||||
|
||||
// 监听跳转事件
|
||||
LaunchedEffect(navigateToRecord, recordId) {
|
||||
@@ -211,7 +222,7 @@ class MainActivity : ComponentActivity() {
|
||||
contentColor = colors.textPrimary,
|
||||
tonalElevation = 0.dp
|
||||
) {
|
||||
listOf(Screen.Device, Screen.Home, Screen.History, Screen.Settings).forEach { screen ->
|
||||
listOf(Screen.Home, Screen.Device, Screen.History, Screen.Settings).forEach { screen ->
|
||||
val selected = currentScreen == screen
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
@@ -270,35 +281,14 @@ class MainActivity : ComponentActivity() {
|
||||
},
|
||||
onOpenPinduoduo = { openPinduoduo() }
|
||||
)
|
||||
Screen.Home -> {
|
||||
// 每次进入首页都检测 Shizuku 状态
|
||||
LaunchedEffect(Unit) {
|
||||
checkAndUpdateShizukuStatus()
|
||||
}
|
||||
HomeScreen(
|
||||
agentState = agentState,
|
||||
logs = logs,
|
||||
onExecute = { instruction ->
|
||||
runAgent(
|
||||
instruction = instruction,
|
||||
apiKey = settings.apiKey,
|
||||
baseUrl = settings.baseUrl,
|
||||
model = settings.model,
|
||||
maxSteps = settings.maxSteps,
|
||||
isGUIAgent = settings.currentProvider.isGUIAgent,
|
||||
providerId = settings.currentProviderId
|
||||
)
|
||||
},
|
||||
onStop = {
|
||||
mobileAgent.value?.stop()
|
||||
},
|
||||
shizukuAvailable = isShizukuAvailable,
|
||||
currentModel = settings.model,
|
||||
onRefreshShizuku = { refreshShizukuStatus() },
|
||||
onShizukuRequired = { showShizukuHelpDialog = true },
|
||||
isExecuting = executing
|
||||
)
|
||||
}
|
||||
Screen.Home -> SearchProbeScreen(
|
||||
readiness = readiness,
|
||||
state = probeState,
|
||||
currentStepId = probeStepId,
|
||||
report = probeReport,
|
||||
onStart = { startSearchProbe() },
|
||||
onStop = { stopSearchProbe() }
|
||||
)
|
||||
Screen.History -> HistoryScreen(
|
||||
records = records,
|
||||
onRecordClick = { record -> selectedRecord = record },
|
||||
@@ -340,10 +330,6 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// Shizuku 帮助对话框
|
||||
if (showShizukuHelpDialog) {
|
||||
ShizukuHelpDialog(onDismiss = { showShizukuHelpDialog = false })
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteRecord(id: String) {
|
||||
@@ -357,10 +343,15 @@ class MainActivity : ComponentActivity() {
|
||||
super.onResume()
|
||||
if (::readinessChecker.isInitialized) {
|
||||
refreshReadiness()
|
||||
lifecycleScope.launch {
|
||||
delay(READINESS_RECHECK_DELAY_MS)
|
||||
refreshReadiness()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
searchProbeRunner?.requestStop()
|
||||
super.onDestroy()
|
||||
Shizuku.removeBinderReceivedListener(binderReceivedListener)
|
||||
Shizuku.removeBinderDeadListener(binderDeadListener)
|
||||
@@ -382,6 +373,48 @@ class MainActivity : ComponentActivity() {
|
||||
startActivity(launchIntent)
|
||||
}
|
||||
|
||||
private fun startSearchProbe() {
|
||||
val readiness = readinessChecker.snapshot()
|
||||
readinessSnapshot.value = readiness
|
||||
if (!readiness.canStartProbe) {
|
||||
Toast.makeText(this, "设备检查存在阻塞项", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
if (searchProbeJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
|
||||
val runner = WorkflowRunner(
|
||||
PinduoduoSearchAutomation(AndroidPinduoduoUiDriver(this))
|
||||
)
|
||||
searchProbeRunner = runner
|
||||
searchProbeReport.value = null
|
||||
searchProbeState.value = WorkflowState.IDLE
|
||||
searchProbeStepId.value = null
|
||||
searchProbeJob = lifecycleScope.launch {
|
||||
val stateCollector = launch {
|
||||
runner.state.collect { state -> searchProbeState.value = state }
|
||||
}
|
||||
val stepCollector = launch {
|
||||
runner.currentStepId.collect { stepId -> searchProbeStepId.value = stepId }
|
||||
}
|
||||
try {
|
||||
val report = runner.run(PinduoduoSearchWorkflow.steps())
|
||||
searchProbeReport.value = report
|
||||
searchProbeState.value = report.state
|
||||
} finally {
|
||||
stateCollector.cancel()
|
||||
stepCollector.cancel()
|
||||
searchProbeRunner = null
|
||||
refreshReadiness()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopSearchProbe() {
|
||||
searchProbeRunner?.requestStop()
|
||||
}
|
||||
|
||||
private fun checkShizukuPermission(): Boolean {
|
||||
return try {
|
||||
val granted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
|
||||
@@ -460,6 +493,10 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val READINESS_RECHECK_DELAY_MS = 750L
|
||||
}
|
||||
|
||||
private fun runAgent(
|
||||
instruction: String,
|
||||
apiKey: String,
|
||||
|
||||
Reference in New Issue
Block a user