

























@@ -1,11 +1,15 @@
11package ai.openclaw.app.gateway
223+import android.annotation.TargetApi
34import android.content.Context
45import android.net.ConnectivityManager
56import android.net.DnsResolver
7+import android.net.Network
68import android.net.NetworkCapabilities
9+import android.net.NetworkRequest
710import android.net.nsd.NsdManager
811import android.net.nsd.NsdServiceInfo
12+import android.os.Build
913import android.os.CancellationSignal
1014import android.util.Log
1115import kotlinx.coroutines.CoroutineScope
@@ -34,6 +38,7 @@ import org.xbill.DNS.TXTRecord
3438import org.xbill.DNS.TextParseException
3539import org.xbill.DNS.Type
3640import java.io.IOException
41+import java.net.InetAddress
3742import java.net.InetSocketAddress
3843import java.nio.ByteBuffer
3944import java.nio.charset.CodingErrorAction
@@ -44,7 +49,6 @@ import java.util.concurrent.Executors
4449import kotlin.coroutines.resume
4550import kotlin.coroutines.resumeWithException
465147-@Suppress("DEPRECATION")
4852class GatewayDiscovery(
4953context: Context,
5054private val scope: CoroutineScope,
@@ -66,11 +70,24 @@ class GatewayDiscovery(
66706771private var unicastJob: Job? = null
6872private val dnsExecutor: Executor = Executors.newCachedThreadPool()
73+private val availableNetworks = ConcurrentHashMap.newKeySet<Network>()
74+private val serviceInfoCallbacks = ConcurrentHashMap<String, Any>()
69757076 @Volatile private var lastWideAreaRcode: Int? = null
71777278 @Volatile private var lastWideAreaCount: Int = 0
737980+private val networkCallback =
81+object : ConnectivityManager.NetworkCallback() {
82+override fun onAvailable(network: Network) {
83+ availableNetworks.add(network)
84+ }
85+86+override fun onLost(network: Network) {
87+ availableNetworks.remove(network)
88+ }
89+ }
90+7491private val discoveryListener =
7592object : NsdManager.DiscoveryListener {
7693override fun onStartDiscoveryFailed(
@@ -96,17 +113,29 @@ class GatewayDiscovery(
96113val serviceName = BonjourEscapes.decode(serviceInfo.serviceName)
97114val id = stableId(serviceName, "local.")
98115 localById.remove(id)
116+ unregisterServiceInfoCallback(id)
99117 publish()
100118 }
101119 }
102120103121init {
122+ startNetworkTracking()
104123 startLocalDiscovery()
105124if (!wideAreaDomain.isNullOrBlank()) {
106125 startUnicastDiscovery(wideAreaDomain)
107126 }
108127 }
109128129+private fun startNetworkTracking() {
130+val cm = connectivity ?: return
131+ cm.activeNetwork?.let(availableNetworks::add)
132+try {
133+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), networkCallback)
134+ } catch (_: Throwable) {
135+// ignore (best-effort)
136+ }
137+ }
138+110139private fun startLocalDiscovery() {
111140try {
112141 nsd.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
@@ -138,46 +167,124 @@ class GatewayDiscovery(
138167 }
139168140169private fun resolve(serviceInfo: NsdServiceInfo) {
141- nsd.resolveService(
142- serviceInfo,
170+if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
171+ resolveWithServiceInfoCallback(serviceInfo)
172+ } else {
173+ resolveLegacy(serviceInfo)
174+ }
175+ }
176+177+ @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
178+private fun resolveWithServiceInfoCallback(serviceInfo: NsdServiceInfo) {
179+val serviceName = BonjourEscapes.decode(serviceInfo.serviceName)
180+val id = stableId(serviceName, "local.")
181+if (serviceInfoCallbacks.containsKey(id)) return
182+183+val callback =
184+object : NsdManager.ServiceInfoCallback {
185+override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) {
186+ serviceInfoCallbacks.remove(id, this)
187+ }
188+189+override fun onServiceInfoCallbackUnregistered() {
190+ serviceInfoCallbacks.remove(id, this)
191+ }
192+193+override fun onServiceLost() {
194+ localById.remove(id)
195+ publish()
196+ }
197+198+override fun onServiceUpdated(serviceInfo: NsdServiceInfo) {
199+ upsertResolvedService(serviceInfo)
200+ }
201+ }
202+203+ serviceInfoCallbacks[id] = callback
204+try {
205+ nsd.registerServiceInfoCallback(serviceInfo, dnsExecutor, callback)
206+ } catch (_: Throwable) {
207+ serviceInfoCallbacks.remove(id, callback)
208+ }
209+ }
210+211+private fun unregisterServiceInfoCallback(id: String) {
212+val callback = serviceInfoCallbacks.remove(id) ?: return
213+if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return
214+try {
215+ nsd.unregisterServiceInfoCallback(callback as NsdManager.ServiceInfoCallback)
216+ } catch (_: Throwable) {
217+// ignore (best-effort)
218+ }
219+ }
220+221+private fun resolveLegacy(serviceInfo: NsdServiceInfo) {
222+val listener =
143223object : NsdManager.ResolveListener {
144224override fun onResolveFailed(
145225serviceInfo: NsdServiceInfo,
146226errorCode: Int,
147227 ) {}
148228149229override fun onServiceResolved(resolved: NsdServiceInfo) {
150-val host = resolved.host?.hostAddress ?: return
151-val port = resolved.port
152-if (port <= 0) return
153-154-val rawServiceName = resolved.serviceName
155-val serviceName = BonjourEscapes.decode(rawServiceName)
156-val displayName = BonjourEscapes.decode(txt(resolved, "displayName") ?: serviceName)
157-val lanHost = txt(resolved, "lanHost")
158-val tailnetDns = txt(resolved, "tailnetDns")
159-val gatewayPort = txtInt(resolved, "gatewayPort")
160-val canvasPort = txtInt(resolved, "canvasPort")
161-val tlsEnabled = txtBool(resolved, "gatewayTls")
162-val tlsFingerprint = txt(resolved, "gatewayTlsSha256")
163-val id = stableId(serviceName, "local.")
164- localById[id] =
165-GatewayEndpoint(
166- stableId = id,
167- name = displayName,
168- host = host,
169- port = port,
170- lanHost = lanHost,
171- tailnetDns = tailnetDns,
172- gatewayPort = gatewayPort,
173- canvasPort = canvasPort,
174- tlsEnabled = tlsEnabled,
175- tlsFingerprintSha256 = tlsFingerprint,
176- )
177- publish()
230+ upsertResolvedService(resolved)
178231 }
179- },
180- )
232+ }
233+234+try {
235+NsdManager::class.java
236+ .getMethod("resolveService", NsdServiceInfo::class.java, NsdManager.ResolveListener::class.java)
237+ .invoke(nsd, serviceInfo, listener)
238+ } catch (_: Throwable) {
239+// ignore (best-effort)
240+ }
241+ }
242+243+private fun upsertResolvedService(resolved: NsdServiceInfo) {
244+val host = resolvedHostAddress(resolved) ?: return
245+val port = resolved.port
246+if (port <= 0) return
247+248+val rawServiceName = resolved.serviceName
249+val serviceName = BonjourEscapes.decode(rawServiceName)
250+val displayName = BonjourEscapes.decode(txt(resolved, "displayName") ?: serviceName)
251+val lanHost = txt(resolved, "lanHost")
252+val tailnetDns = txt(resolved, "tailnetDns")
253+val gatewayPort = txtInt(resolved, "gatewayPort")
254+val canvasPort = txtInt(resolved, "canvasPort")
255+val tlsEnabled = txtBool(resolved, "gatewayTls")
256+val tlsFingerprint = txt(resolved, "gatewayTlsSha256")
257+val id = stableId(serviceName, "local.")
258+ localById[id] =
259+GatewayEndpoint(
260+ stableId = id,
261+ name = displayName,
262+ host = host,
263+ port = port,
264+ lanHost = lanHost,
265+ tailnetDns = tailnetDns,
266+ gatewayPort = gatewayPort,
267+ canvasPort = canvasPort,
268+ tlsEnabled = tlsEnabled,
269+ tlsFingerprintSha256 = tlsFingerprint,
270+ )
271+ publish()
272+ }
273+274+private fun resolvedHostAddress(resolved: NsdServiceInfo): String? {
275+if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
276+return resolved.hostAddresses.firstOrNull()?.hostAddress
277+ }
278+return legacyHostAddress(resolved)
279+ }
280+281+private fun legacyHostAddress(resolved: NsdServiceInfo): String? {
282+return try {
283+val host = NsdServiceInfo::class.java.getMethod("getHost").invoke(resolved) as? InetAddress
284+ host?.hostAddress
285+ } catch (_: Throwable) {
286+null
287+ }
181288 }
182289183290private fun publish() {
@@ -422,21 +529,27 @@ class GatewayDiscovery(
422529val cm = connectivity ?: return null
423530424531// Prefer VPN (Tailscale) when present; otherwise use the active network.
425- cm.allNetworks
426- .firstOrNull { n ->
427-val caps = cm.getNetworkCapabilities(n) ?: return@firstOrNull false
428- caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
429- }?.let { return it }
532+ trackedNetworks(cm).firstOrNull { n ->
533+val caps = cm.getNetworkCapabilities(n) ?: return@firstOrNull false
534+ caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
535+ }?.let { return it }
430536431537return cm.activeNetwork
432538 }
433539540+private fun trackedNetworks(cm: ConnectivityManager): List<Network> {
541+return buildList {
542+ cm.activeNetwork?.let(::add)
543+ addAll(availableNetworks)
544+ }.distinct()
545+ }
546+434547private fun createDirectResolver(): Resolver? {
435548val cm = connectivity ?: return null
436549437550val candidateNetworks =
438551 buildList {
439-cm.allNetworks
552+trackedNetworks(cm)
440553 .firstOrNull { n ->
441554val caps = cm.getNetworkCapabilities(n) ?: return@firstOrNull false
442555 caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。