Compare commits

...
Author SHA1 Message Date
QiuSWandClaude Sonnet 5.5 ff8c5361cb feat(android): 状态页新增设备连接卡片 (#380)
显示当前 Wi-Fi IPv4 与无线调试开关状态(adb_wifi_enabled),读取失败显示无法读取;
端口仅给出固定查看提示。读数到文案/颜色为纯函数策略,不持久化、不上报、不写日志。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-10-12 15:27:26 +08:00
3 changed files with 168 additions and 0 deletions
@@ -74,6 +74,8 @@ class AgentStatusFragment : Fragment() {
private lateinit var mediaPermissionDetail: TextView
private lateinit var mediaPermissionAction: android.widget.Button
private lateinit var deviceText: TextView
private lateinit var deviceWifiText: TextView
private lateinit var deviceDebugText: TextView
private val mediaPermissionRequestLauncher by lazy {
registerForActivityResult(androidx.activity.result.contract.ActivityResultContracts.RequestPermission()) {
refreshStatus()
@@ -168,6 +170,20 @@ class AgentStatusFragment : Fragment() {
}
addView(mediaPermissionAction)
}))
addView(context.card(context.cardColumn().apply {
addView(context.label("设备连接", 14f, context.getColor(R.color.agent_text_muted)))
deviceWifiText = context.label("Wi-Fi IP:—", 18f, context.getColor(R.color.agent_text), true).apply {
setPadding(0, context.dp(6), 0, 0)
}
deviceDebugText = context.label("无线调试:—", 18f, context.getColor(R.color.agent_text_muted), true).apply {
setPadding(0, context.dp(6), 0, 0)
}
addView(deviceWifiText)
addView(deviceDebugText)
addView(context.label(DeviceConnectionPolicy.PORT_HINT, 14f, context.getColor(R.color.agent_text_muted)).apply {
setPadding(0, context.dp(6), 0, 0)
})
}))
}
refreshLayout = SwipeRefreshLayout(context).apply {
setColorSchemeResources(R.color.agent_primary_light)
@@ -245,6 +261,10 @@ class AgentStatusFragment : Fragment() {
private fun refreshStatus() {
if (!isAdded || view == null) return
val context = requireContext()
val deviceConnection = DeviceConnectionPolicy.display(DeviceConnectionReader.read(context))
deviceWifiText.text = deviceConnection.wifiText
deviceDebugText.text = deviceConnection.debugText
deviceDebugText.setTextColor(context.getColor(deviceConnection.debugColor))
val state = stateStore.read()
val connected = state.code in setOf("ONLINE", "BUSY", "COLLECTION_COOLDOWN")
connectionTitle.text = when (state.code) {
@@ -0,0 +1,85 @@
package cn.ilapage.goauto.agent.ui
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.provider.Settings
import androidx.annotation.ColorRes
import cn.ilapage.goauto.agent.R
import java.net.Inet4Address
import java.net.InetAddress
/** 无线调试开关的读取结果;读取失败为 [UNKNOWN]。 */
internal enum class WirelessDebugState { ENABLED, DISABLED, UNKNOWN }
internal data class DeviceConnectionReading(
val wifiIpv4: String?,
val wirelessDebug: WirelessDebugState,
)
internal data class DeviceConnectionDisplay(
val wifiText: String,
val debugText: String,
@ColorRes val debugColor: Int,
)
/** 原始读数到显示文案/颜色的纯函数策略,不依赖 Android 系统服务。 */
internal object DeviceConnectionPolicy {
const val PORT_HINT = "端口请在 开发者选项 → 无线调试 中查看"
const val NO_WIFI_TEXT = "未连接 Wi-Fi"
/** 仅当前网络为 Wi-Fi 时,从地址列表中取第一个非回环 IPv4;否则返回 null。 */
fun selectWifiIpv4(isWifi: Boolean, addresses: List<InetAddress>): String? {
if (!isWifi) return null
return addresses.firstOrNull { it is Inet4Address && !it.isLoopbackAddress }?.hostAddress
}
/** adb_wifi_enabled 原始值:1 开启,0 未开启,其他值或读取失败(null)为未知。 */
fun debugState(raw: Int?): WirelessDebugState = when (raw) {
1 -> WirelessDebugState.ENABLED
0 -> WirelessDebugState.DISABLED
else -> WirelessDebugState.UNKNOWN
}
fun display(reading: DeviceConnectionReading): DeviceConnectionDisplay {
val (text, color) = when (reading.wirelessDebug) {
WirelessDebugState.ENABLED -> "已开启" to R.color.agent_primary_light
WirelessDebugState.DISABLED -> "未开启" to R.color.agent_warning
WirelessDebugState.UNKNOWN -> "无法读取" to R.color.agent_text_muted
}
return DeviceConnectionDisplay(
wifiText = "Wi-Fi IP:${reading.wifiIpv4 ?: NO_WIFI_TEXT}",
debugText = "无线调试:$text",
debugColor = color,
)
}
}
/** 系统读取的薄封装;任何异常都降级为「未知」,不记录日志、不持久化。 */
internal object DeviceConnectionReader {
fun read(context: Context): DeviceConnectionReading = DeviceConnectionReading(
wifiIpv4 = readWifiIpv4(context),
wirelessDebug = DeviceConnectionPolicy.debugState(readDebugRaw(context)),
)
private fun readWifiIpv4(context: Context): String? = try {
val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
val network = manager?.activeNetwork
val isWifi = network?.let { manager.getNetworkCapabilities(it) }
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
val addresses = if (isWifi && manager != null && network != null) {
manager.getLinkProperties(network)?.linkAddresses?.map { it.address }.orEmpty()
} else {
emptyList()
}
DeviceConnectionPolicy.selectWifiIpv4(isWifi, addresses)
} catch (_: Exception) {
null
}
private fun readDebugRaw(context: Context): Int? = try {
Settings.Global.getInt(context.contentResolver, "adb_wifi_enabled", 0)
} catch (_: Exception) {
null
}
}
@@ -0,0 +1,63 @@
package cn.ilapage.goauto.agent
import cn.ilapage.goauto.agent.ui.DeviceConnectionPolicy
import cn.ilapage.goauto.agent.ui.DeviceConnectionReading
import cn.ilapage.goauto.agent.ui.WirelessDebugState
import java.net.InetAddress
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class DeviceConnectionPolicyTest {
private fun ip(text: String) = InetAddress.getByName(text)
@Test fun wifiWithIpv4ReturnsAddress() {
assertEquals("192.168.0.93", DeviceConnectionPolicy.selectWifiIpv4(true, listOf(ip("192.168.0.93"))))
}
@Test fun nonWifiReturnsNull() {
assertNull(DeviceConnectionPolicy.selectWifiIpv4(false, listOf(ip("10.0.0.2"))))
}
@Test fun wifiWithOnlyIpv6ReturnsNull() {
assertNull(DeviceConnectionPolicy.selectWifiIpv4(true, listOf(ip("2001:db8::1"))))
}
@Test fun wifiWithMultipleAddressesPicksIpv4() {
assertEquals(
"172.16.1.5",
DeviceConnectionPolicy.selectWifiIpv4(true, listOf(ip("2001:db8::1"), ip("172.16.1.5"))),
)
}
@Test fun loopbackIpv4IsSkipped() {
assertNull(DeviceConnectionPolicy.selectWifiIpv4(true, listOf(ip("127.0.0.1"))))
}
@Test fun debugRawValuesMapToState() {
assertEquals(WirelessDebugState.ENABLED, DeviceConnectionPolicy.debugState(1))
assertEquals(WirelessDebugState.DISABLED, DeviceConnectionPolicy.debugState(0))
assertEquals(WirelessDebugState.UNKNOWN, DeviceConnectionPolicy.debugState(2))
assertEquals(WirelessDebugState.UNKNOWN, DeviceConnectionPolicy.debugState(null))
}
@Test fun displayEnabledUsesPrimaryColor() {
val d = DeviceConnectionPolicy.display(DeviceConnectionReading("192.168.0.93", WirelessDebugState.ENABLED))
assertEquals("Wi-Fi IP:192.168.0.93", d.wifiText)
assertEquals("无线调试:已开启", d.debugText)
assertEquals(R.color.agent_primary_light, d.debugColor)
}
@Test fun displayDisabledUsesWarningColorAndNoWifiText() {
val d = DeviceConnectionPolicy.display(DeviceConnectionReading(null, WirelessDebugState.DISABLED))
assertEquals("Wi-Fi IP:未连接 Wi-Fi", d.wifiText)
assertEquals("无线调试:未开启", d.debugText)
assertEquals(R.color.agent_warning, d.debugColor)
}
@Test fun displayUnknownUsesMutedColor() {
val d = DeviceConnectionPolicy.display(DeviceConnectionReading(null, WirelessDebugState.UNKNOWN))
assertEquals("无线调试:无法读取", d.debugText)
assertEquals(R.color.agent_text_muted, d.debugColor)
}
}