diff --git a/android/app/src/main/java/com/yarisobd/app/DualNetworkTestScreen.kt b/android/app/src/main/java/com/yarisobd/app/DualNetworkTestScreen.kt index fb8190d..dc68bdf 100644 --- a/android/app/src/main/java/com/yarisobd/app/DualNetworkTestScreen.kt +++ b/android/app/src/main/java/com/yarisobd/app/DualNetworkTestScreen.kt @@ -26,8 +26,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request @@ -44,8 +46,8 @@ import okhttp3.Request */ @Composable fun DualNetworkTestScreen( - piClient: OkHttpClient?, - serverClient: OkHttpClient?, + piClient: OkHttpClient, + serverClient: OkHttpClient, ) { val scope = rememberCoroutineScope() val scroll = rememberScrollState() @@ -55,9 +57,6 @@ fun DualNetworkTestScreen( var bothResult by remember { mutableStateOf(null) } var isLoading by remember { mutableStateOf(false) } - val piAvailable = piClient != null - val serverAvailable = serverClient != null - Column( modifier = Modifier .fillMaxSize() @@ -96,16 +95,14 @@ fun DualNetworkTestScreen( ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Pi WiFi: ${if (piAvailable) "✓ Bound" else "✗ Not found"}", + text = "Pi WiFi: ✓ Connected", style = MaterialTheme.typography.bodySmall, - color = if (piAvailable) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.error + color = MaterialTheme.colorScheme.primary ) Text( - text = "Cellular: ${if (serverAvailable) "✓ Bound" else "✗ Not found (using default)"}", + text = "Server: ✓ OS-routed (WiFi for Pi, cellular for internet)", style = MaterialTheme.typography.bodySmall, - color = if (serverAvailable) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.error + color = MaterialTheme.colorScheme.primary ) } } @@ -140,7 +137,7 @@ fun DualNetworkTestScreen( isLoading = false } }, - enabled = piAvailable && !isLoading, + enabled = !isLoading, modifier = Modifier.weight(1f) ) { Text("Test Pi") @@ -155,7 +152,7 @@ fun DualNetworkTestScreen( Spacer(modifier = Modifier.height(12.dp)) - // Server cellular test + // Server test Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( @@ -164,7 +161,7 @@ fun DualNetworkTestScreen( ) { Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Remote Server (Cellular)", + text = "Remote Server (OS-routed)", style = MaterialTheme.typography.titleMedium ) Text( @@ -183,7 +180,7 @@ fun DualNetworkTestScreen( isLoading = false } }, - enabled = serverAvailable && !isLoading, + enabled = !isLoading, modifier = Modifier.weight(1f), colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary @@ -227,7 +224,7 @@ fun DualNetworkTestScreen( isLoading = false } }, - enabled = piAvailable && serverAvailable && !isLoading, + enabled = !isLoading, modifier = Modifier.fillMaxWidth() ) { Text(if (isLoading) "Testing..." else "Test Both Simultaneously") @@ -284,10 +281,8 @@ private fun ResultBlock(text: String) { // --- Network call helpers --- -private suspend fun callPi(client: OkHttpClient?): String { - if (client == null) return "✗ Pi WiFi network not available — connect to yaris 911 hotspot" - - return try { +private suspend fun callPi(client: OkHttpClient): String = withContext(Dispatchers.IO) { + try { val request = Request.Builder() .url("http://10.42.0.1/api/gps/status") .get() @@ -303,15 +298,13 @@ private suspend fun callPi(client: OkHttpClient?): String { } } -private suspend fun callServer(client: OkHttpClient?): String { - val c = client ?: OkHttpClient() - - return try { +private suspend fun callServer(client: OkHttpClient): String = withContext(Dispatchers.IO) { + try { val request = Request.Builder() .url("https://yaris.ceaf.dev/health") .get() .build() - val response = c.newCall(request).execute() + val response = client.newCall(request).execute() if (response.isSuccessful) { "✓ Server OK (${response.code}) — ${response.body?.string() ?: "no body"}" } else { diff --git a/android/app/src/main/java/com/yarisobd/app/MainActivity.kt b/android/app/src/main/java/com/yarisobd/app/MainActivity.kt index f3a5aa1..7a2f0d7 100644 --- a/android/app/src/main/java/com/yarisobd/app/MainActivity.kt +++ b/android/app/src/main/java/com/yarisobd/app/MainActivity.kt @@ -1,7 +1,6 @@ package com.yarisobd.app import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -10,20 +9,19 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier import com.yarisobd.app.ui.theme.YarisOBDTheme -import com.yarisobd.app.util.NetworkBinder import okhttp3.OkHttpClient class MainActivity : ComponentActivity() { - private var piClient: OkHttpClient? = null - private var serverClient: OkHttpClient? = null + // Single shared client — the OS routes correctly: + // 10.42.0.1 → WiFi (Pi hotspot subnet) + // yaris.ceaf.dev → cellular (WiFi has no internet, OS falls back) + private val client = OkHttpClient() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - discoverNetworks() - setContent { YarisOBDTheme { Surface( @@ -31,31 +29,11 @@ class MainActivity : ComponentActivity() { color = MaterialTheme.colorScheme.background ) { DualNetworkTestScreen( - piClient = piClient, - serverClient = serverClient, + piClient = client, + serverClient = client, ) } } } } - - private fun discoverNetworks() { - try { - val piNetwork = NetworkBinder.findPiHotspotNetwork(applicationContext) - piClient = piNetwork?.let { NetworkBinder.createBoundClient(it) } - Log.d(TAG, "Pi WiFi network: ${if (piClient != null) "bound" else "not found"}") - - val cellNetwork = NetworkBinder.findCellularNetwork(applicationContext) - ?: NetworkBinder.findDefaultNetwork(applicationContext) - serverClient = cellNetwork?.let { NetworkBinder.createBoundClient(it) } - Log.d(TAG, "Cellular/default network: ${if (serverClient != null) "bound" else "not found"}") - } catch (e: Exception) { - Log.e(TAG, "Network discovery failed", e) - // Both clients stay null — screen shows "not found" state - } - } - - companion object { - private const val TAG = "MainActivity" - } } diff --git a/android/app/src/main/java/com/yarisobd/app/util/NetworkBinder.kt b/android/app/src/main/java/com/yarisobd/app/util/NetworkBinder.kt index 25b7da1..a3827fa 100644 --- a/android/app/src/main/java/com/yarisobd/app/util/NetworkBinder.kt +++ b/android/app/src/main/java/com/yarisobd/app/util/NetworkBinder.kt @@ -21,17 +21,35 @@ object NetworkBinder { /** * Finds the WiFi network associated with the Pi hotspot ("yaris 911"). - * Identified by: TRANSPORT_WIFI + NO NET_CAPABILITY_INTERNET - * (the Pi hotspot has no internet gateway). + * Matches any TRANSPORT_WIFI network — on Pixel devices with "switch to + * mobile data automatically", the OS may still tag the WiFi with + * NET_CAPABILITY_INTERNET (relayed through cellular), so we can't + * require !INTERNET. + * + * Falls back to the active network if it's WiFi. */ fun findPiHotspotNetwork(context: Context): Network? { val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - return cm.allNetworks.firstOrNull { network -> + // First: find any WiFi network (the only WiFi network the phone is on + // should be the Pi hotspot when using this app) + val wifiNetwork = cm.allNetworks.firstOrNull { network -> val caps = cm.getNetworkCapabilities(network) ?: return@firstOrNull false - caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && - !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) } + + if (wifiNetwork != null) return wifiNetwork + + // Fallback: if active network is WiFi, use it + val active = cm.activeNetwork + if (active != null) { + val caps = cm.getNetworkCapabilities(active) + if (caps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) { + return active + } + } + + return null } /**