wireless: wait for the car to connect when it publishes no Android Auto service

This commit is contained in:
Seb3thehacker committed 2026-10-11 17:22:58 -07:00
1 parent 669bf1f7ce
commit 04b820cb22
1 file changed
+47 -7
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothServerSocket
import android.bluetooth.BluetoothSocket
import android.content.Context
import android.net.ConnectivityManager
@@ -69,6 +70,7 @@ object WirelessLink {
private const val MSG_PING = 8
private const val MSG_PONG = 9
private const val LISTEN_MS = 120_000 // time to reconnect the phone from the car's Bluetooth menu
private const val TCP_TRIES = 12 // about a minute: a car's projection listener can start well after its Wi-Fi
private const val WIFI_WAIT_MS = 60_000
@@ -76,6 +78,7 @@ object WirelessLink {
@Volatile var onConnected: ((Socket) -> Unit)? = null
@Volatile private var rfcomm: BluetoothSocket? = null
@Volatile private var listener: BluetoothServerSocket? = null
@Volatile private var tcp: Socket? = null
@Volatile private var network: ConnectivityManager.NetworkCallback? = null
@Volatile private var worker: Thread? = null
@@ -109,6 +112,8 @@ object WirelessLink {
fun stop(context: Context) {
worker?.interrupt()
worker = null
runCatching { listener?.close() }
listener = null
runCatching { rfcomm?.close() }
rfcomm = null
runCatching { tcp?.close() }
@@ -143,13 +148,10 @@ object WirelessLink {
)
return
} catch (e: IOException) {
log.e("Bluetooth: could not open the Android Auto channel on $name", e)
SessionStatus.failed(
"Car didn't answer over Bluetooth",
"$name has no wireless Android Auto service, or it's turned off in the car. Check that " +
"the car is paired and wireless Android Auto is on.",
)
return
// Most factory head units publish no service of their own: they look for the phone's
// and connect to it, so wait for the car instead.
log.i("Bluetooth: $name has no Android Auto service to call (${e.message}); waiting for it to call us")
waitForCar(context, name) ?: return
}
rfcomm = socket
log.i("Bluetooth: channel open")
@@ -220,6 +222,44 @@ object WirelessLink {
}
}
/**
* Publishes the Android Auto service on the phone, the way Android Auto itself does, and waits
* for the car to connect to it. A car looks for the service when the phone connects over
* Bluetooth, so the driver may have to reconnect the phone from the car's menu.
*/
@SuppressLint("MissingPermission")
private fun waitForCar(context: Context, name: String): BluetoothSocket? {
val adapter = context.getSystemService(BluetoothManager::class.java)?.adapter ?: return null
val server = try {
adapter.listenUsingRfcommWithServiceRecord("Android Auto", AA_UUID)
} catch (e: Exception) {
log.e("Bluetooth: couldn't publish the Android Auto service", e)
SessionStatus.failed("Couldn't publish the Android Auto service", "${e.javaClass.simpleName}: ${e.message}")
return null
}
listener = server
SessionStatus.connecting("Waiting for $name. If nothing happens, disconnect the phone in the car's Bluetooth menu and connect it again")
log.i("Bluetooth: Android Auto service published, waiting ${LISTEN_MS / 1000}s for $name")
return try {
server.accept(LISTEN_MS).also {
val from = runCatching { it.remoteDevice.name }.getOrNull() ?: "a device"
log.i("Bluetooth: $from connected to the Android Auto service")
}
} catch (e: IOException) {
if (listener == null) return null // stopped
log.w("Bluetooth: no car connected within ${LISTEN_MS / 1000}s (${e.message})")
SessionStatus.failed(
"Car didn't connect over Bluetooth",
"$name didn't open Android Auto with the phone. Check that wireless Android Auto is " +
"on in the car's settings, then reconnect the phone from the car's Bluetooth menu.",
)
null
} finally {
runCatching { server.close() }
listener = null
}
}
private fun send(output: OutputStream, id: Int, body: ByteArray) {
val frame = ByteArray(4 + body.size)
frame[0] = (body.size shr 8).toByte(); frame[1] = body.size.toByte()