protocol: service discovery sends Gearslip's icon at three sizes and a session ID, like a real phone

This commit is contained in:
Seb3thehacker committed 2026-10-27 14:33:10 -07:00
1 parent 37d56be59a
commit 327d6fd40a
3 files changed
+50 -3

No files matched your search

@@ -401,6 +401,7 @@ class GearslipActivity : ComponentActivity(), Projection {
identityProvider = { CertProvider.load(this) },
projection = this,
vehicleProfileFor = { info -> VehicleProfiles.find(VehicleProfiles.load(this), info) },
phoneIcons = { PhoneIcons.pngs(this) },
)
this.runner = runner
worker = Thread { runner.run() }.apply { name = "gearslip"; start() }
@@ -423,6 +424,7 @@ class GearslipActivity : ComponentActivity(), Projection {
projection = this,
vehicleProfileFor = { info -> VehicleProfiles.find(VehicleProfiles.load(this), info) },
wireless = true,
phoneIcons = { PhoneIcons.pngs(this) },
)
this.runner = runner
worker = Thread { runner.run() }.apply { name = "gearslip"; start() }
@@ -48,6 +48,8 @@ class GearslipRunner(
private val vehicleProfileFor: (ServiceDiscovery.HeadUnitInfo) -> VehicleProfile? = { null },
/** Over Wi-Fi rather than USB: the video is sent smaller so the link keeps up. */
private val wireless: Boolean = false,
/** Gearslip's icon as 32, 64 and 128 px PNGs, for the car's phone tile. See [PhoneIcons]. */
private val phoneIcons: () -> List<ByteArray> = { emptyList() },
) {
private val log = GearslipLog.tagged("PROTO")
@@ -446,11 +448,20 @@ class GearslipRunner(
if (authenticationFailed) running = false
}
/**
* What a real phone sends here: its icon at three sizes (fields 1 to 3), a label, its model,
* and a session ID that's new each connection (field 6). A 2017 Hyundai on protocol 1.0
* never answered the request when it had only the label and model (issue #47).
*/
private fun sendServiceDiscoveryRequest() {
val body = Protobuf.stringField(4, "Gearslip") + // label_text
Protobuf.stringField(5, Build.MODEL) // device_name
val icons = runCatching(phoneIcons).getOrDefault(emptyList())
var body = ByteArray(0)
icons.take(3).forEachIndexed { i, png -> body += Protobuf.bytesField(i + 1, png) } // small, medium, large icon
body += Protobuf.stringField(4, "Gearslip") + // label_text
Protobuf.stringField(5, Build.MODEL) + // device_name
Protobuf.bytesField(6, Protobuf.stringField(1, java.util.UUID.randomUUID().toString())) // session ID
send(MSG_SERVICE_DISCOVERY_REQUEST, body, encrypted = true)
log.i("-> ServiceDiscoveryRequest (encrypted)")
log.i("-> ServiceDiscoveryRequest (encrypted, ${icons.size} icons, ${body.size} bytes)")
}
private fun onServiceDiscoveryResponse(body: ByteArray) {
@@ -0,0 +1,34 @@
package app.seb3thehacker.gearslip
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import java.io.ByteArrayOutputStream
/**
* Gearslip's own icon as the PNGs a phone hands the car in its ServiceDiscoveryRequest, at the
* 32, 64 and 128 px a real phone sends. Cars draw the phone's tile on their home screen from
* them, and older head units may wait for them before answering at all.
*/
object PhoneIcons {
private val SIZES = intArrayOf(32, 64, 128)
@Volatile private var cached: List<ByteArray>? = null
/** The three PNGs, smallest first; empty if the icon can't be drawn. */
fun pngs(context: Context): List<ByteArray> = cached ?: runCatching {
val icon = context.packageManager.getApplicationIcon(context.applicationInfo).mutate()
SIZES.map { size ->
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
icon.setBounds(0, 0, size, size)
icon.draw(Canvas(bitmap))
val png = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, png)
bitmap.recycle()
png.toByteArray()
}
}.onFailure { GearslipLog.w("phone icons: couldn't draw them (${it.message})") }
.getOrDefault(emptyList())
.also { if (it.isNotEmpty()) cached = it }
}