feat(android): add device readiness checks

This commit is contained in:
QiuSW
2026-07-25 18:41:16 +08:00
parent 04fa4a0994
commit d013ac8c84
19 changed files with 884 additions and 28 deletions
@@ -1,5 +1,6 @@
package com.roubao.autopilot
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
@@ -30,6 +31,9 @@ import com.roubao.autopilot.agent.MobileAgent
import com.roubao.autopilot.controller.AppScanner
import com.roubao.autopilot.controller.DeviceController
import com.roubao.autopilot.data.*
import com.roubao.autopilot.readiness.DeviceReadinessChecker
import com.roubao.autopilot.readiness.DeviceReadinessSnapshot
import com.roubao.autopilot.readiness.PINDUODUO_PACKAGE
import com.roubao.autopilot.ui.screens.*
import com.roubao.autopilot.ui.theme.*
import androidx.compose.ui.graphics.toArgb
@@ -45,8 +49,8 @@ import android.util.Log
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 Capabilities : Screen("capabilities", "能力", Icons.Outlined.Star, Icons.Filled.Star)
object History : Screen("history", "记录", Icons.Outlined.List, Icons.Filled.List)
object Settings : Screen("settings", "设置", Icons.Outlined.Settings, Icons.Filled.Settings)
}
@@ -56,9 +60,11 @@ class MainActivity : ComponentActivity() {
private lateinit var deviceController: DeviceController
private lateinit var settingsManager: SettingsManager
private lateinit var executionRepository: ExecutionRepository
private lateinit var readinessChecker: DeviceReadinessChecker
private val mobileAgent = mutableStateOf<MobileAgent?>(null)
private var shizukuAvailable = mutableStateOf(false)
private val readinessSnapshot = mutableStateOf(DeviceReadinessSnapshot.empty())
// 当前执行的协程 Job(用于停止任务)
private var currentExecutionJob: kotlinx.coroutines.Job? = null
@@ -113,6 +119,8 @@ class MainActivity : ComponentActivity() {
deviceController.setCacheDir(cacheDir)
settingsManager = SettingsManager(this)
executionRepository = ExecutionRepository(this)
readinessChecker = DeviceReadinessChecker(this)
refreshReadiness()
// 加载执行记录
lifecycleScope.launch {
@@ -164,10 +172,9 @@ class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainApp() {
var currentScreen by remember { mutableStateOf<Screen>(Screen.Home) }
var currentScreen by remember { mutableStateOf<Screen>(Screen.Device) }
var selectedRecord by remember { mutableStateOf<ExecutionRecord?>(null) }
var showShizukuHelpDialog by remember { mutableStateOf(false) }
var hasShownShizukuHelp by remember { mutableStateOf(false) }
val settings by settingsManager.settings.collectAsState()
val colors = BaoziTheme.colors
@@ -179,6 +186,7 @@ class MainActivity : ComponentActivity() {
val executing by remember { isExecuting }
val navigateToRecord by remember { shouldNavigateToRecord }
val recordId by remember { currentRecordId }
val readiness by remember { readinessSnapshot }
// 监听跳转事件
LaunchedEffect(navigateToRecord, recordId) {
@@ -193,14 +201,6 @@ class MainActivity : ComponentActivity() {
}
}
// 首次进入且 Shizuku 未连接时,显示帮助引导(只显示一次)
LaunchedEffect(Unit) {
if (!isShizukuAvailable && settings.hasSeenOnboarding && !hasShownShizukuHelp) {
hasShownShizukuHelp = true
showShizukuHelpDialog = true
}
}
Scaffold(
modifier = Modifier.background(colors.background),
containerColor = colors.background,
@@ -211,7 +211,7 @@ class MainActivity : ComponentActivity() {
contentColor = colors.textPrimary,
tonalElevation = 0.dp
) {
listOf(Screen.Home, Screen.Capabilities, Screen.History, Screen.Settings).forEach { screen ->
listOf(Screen.Device, Screen.Home, Screen.History, Screen.Settings).forEach { screen ->
val selected = currentScreen == screen
NavigationBarItem(
icon = {
@@ -262,6 +262,14 @@ class MainActivity : ComponentActivity() {
label = "screen"
) { screen ->
when (screen) {
Screen.Device -> DeviceReadinessScreen(
snapshot = readiness,
onRefresh = { refreshReadiness() },
onOpenAccessibilitySettings = {
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
},
onOpenPinduoduo = { openPinduoduo() }
)
Screen.Home -> {
// 每次进入首页都检测 Shizuku 状态
LaunchedEffect(Unit) {
@@ -291,7 +299,6 @@ class MainActivity : ComponentActivity() {
isExecuting = executing
)
}
Screen.Capabilities -> CapabilitiesScreen()
Screen.History -> HistoryScreen(
records = records,
onRecordClick = { record -> selectedRecord = record },
@@ -346,6 +353,13 @@ class MainActivity : ComponentActivity() {
}
}
override fun onResume() {
super.onResume()
if (::readinessChecker.isInitialized) {
refreshReadiness()
}
}
override fun onDestroy() {
super.onDestroy()
Shizuku.removeBinderReceivedListener(binderReceivedListener)
@@ -354,6 +368,20 @@ class MainActivity : ComponentActivity() {
deviceController.unbindService()
}
private fun refreshReadiness() {
readinessSnapshot.value = readinessChecker.snapshot()
}
private fun openPinduoduo() {
val launchIntent = packageManager.getLaunchIntentForPackage(PINDUODUO_PACKAGE)
if (launchIntent == null) {
Toast.makeText(this, "未安装拼多多", Toast.LENGTH_SHORT).show()
refreshReadiness()
return
}
startActivity(launchIntent)
}
private fun checkShizukuPermission(): Boolean {
return try {
val granted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
@@ -365,7 +393,7 @@ class MainActivity : ComponentActivity() {
}
}
private fun checkAndUpdateShizukuStatus() {
private fun checkAndUpdateShizukuStatus(requestPermission: Boolean = false) {
Log.d(TAG, "checkAndUpdateShizukuStatus called")
try {
val binderAlive = Shizuku.pingBinder()
@@ -379,7 +407,7 @@ class MainActivity : ComponentActivity() {
if (hasPermission) {
Log.d(TAG, "Binding Shizuku service")
deviceController.bindService()
} else {
} else if (requestPermission) {
Log.d(TAG, "Requesting Shizuku permission")
requestShizukuPermission()
}
@@ -396,7 +424,7 @@ class MainActivity : ComponentActivity() {
private fun refreshShizukuStatus() {
Log.d(TAG, "refreshShizukuStatus called by user")
Toast.makeText(this, "正在检查 Shizuku 状态...", Toast.LENGTH_SHORT).show()
checkAndUpdateShizukuStatus()
checkAndUpdateShizukuStatus(requestPermission = true)
if (shizukuAvailable.value && checkShizukuPermission()) {
Toast.makeText(this, "Shizuku 已连接", Toast.LENGTH_SHORT).show()