Merge branch 'fix/remove-usb-key-receiver-duplication': remove dead code

This commit is contained in:
eddy 2026-07-26 16:21:59 +02:00
commit ea0718218c

View file

@ -1,318 +0,0 @@
package com.eddy.atomrelay
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.hardware.usb.UsbConstants
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbDeviceConnection
import android.hardware.usb.UsbEndpoint
import android.hardware.usb.UsbInterface
import android.hardware.usb.UsbManager
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
class UsbKeyReceiver : BroadcastReceiver() {
companion object {
private const val TAG = "UsbKeyReceiver"
private const val ACTION_USB_PERMISSION = "com.eddy.atomrelay.USB_PERMISSION"
private const val ESP_VENDOR_ID = 0x303a
private const val KEY_TIMEOUT_MS = 5000
private const val MAX_RETRIES = 5
private const val RETRY_DELAY_MS = 1000L
private const val CDC_DATA_INTERFACE_CLASS = 0x0A
private const val SET_KEY_TIMEOUT_MS = 3000
}
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE) ?: return
Log.d(TAG, "USB device attached: vid=0x${device.vendorId.toString(16)} pid=0x${device.productId.toString(16)}")
if (device.vendorId != ESP_VENDOR_ID) return
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
val currentDevice = findEspDevice(usbManager) ?: device
if (usbManager.hasPermission(currentDevice)) {
Thread { readKeyRaw(context, usbManager, currentDevice) }.start()
} else {
requestPermission(context, usbManager, currentDevice)
}
}
ACTION_USB_PERMISSION -> {
if (!intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
Log.w(TAG, "USB permission denied")
Handler(Looper.getMainLooper()).post {
Toast.makeText(context, "USB-toegang geweigerd", Toast.LENGTH_SHORT).show()
}
return
}
Log.d(TAG, "USB permission granted")
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
val device = findEspDevice(usbManager)
if (device != null) {
Thread { readKeyRaw(context, usbManager, device) }.start()
} else {
Log.e(TAG, "ESP device not found after permission grant")
Handler(Looper.getMainLooper()).post {
Toast.makeText(context, "ESP niet (meer) gevonden", Toast.LENGTH_SHORT).show()
}
}
}
}
}
private fun findEspDevice(usbManager: UsbManager): UsbDevice? {
return usbManager.deviceList.values.firstOrNull { it.vendorId == ESP_VENDOR_ID }
}
private fun generateRandomHexKey(): String {
val random = SecureRandom()
val bytes = ByteArray(32)
random.nextBytes(bytes)
return bytes.joinToString("") { "%02X".format(it) }
}
private fun requestPermission(context: Context, usbManager: UsbManager, device: UsbDevice) {
val flags = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
val permIntent = PendingIntent.getBroadcast(
context, 0,
Intent(context, UsbKeyReceiver::class.java)
.setAction(ACTION_USB_PERMISSION),
flags
)
usbManager.requestPermission(device, permIntent)
Log.d(TAG, "Requesting USB permission for ${device.deviceName}")
}
private fun readKeyRaw(context: Context, usbManager: UsbManager, device: UsbDevice) {
val handler = Handler(Looper.getMainLooper())
for (attempt in 1..MAX_RETRIES) {
Log.d(TAG, "Read attempt $attempt/$MAX_RETRIES")
var connection: UsbDeviceConnection? = null
try {
val currentDevice = findEspDevice(usbManager) ?: device
connection = usbManager.openDevice(currentDevice)
if (connection == null) {
Log.w(TAG, "Attempt $attempt: openDevice returned null")
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS)
continue
}
handler.post { Toast.makeText(context, "Kan USB-apparaat niet openen", Toast.LENGTH_SHORT).show() }
return
}
val dataInterface = findCdcDataInterface(currentDevice)
if (dataInterface == null) {
Log.w(TAG, "Attempt $attempt: no CDC data interface")
connection.close()
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS)
continue
}
handler.post { Toast.makeText(context, "Geen CDC interface gevonden op ESP", Toast.LENGTH_SHORT).show() }
return
}
if (!connection.claimInterface(dataInterface, true)) {
Log.w(TAG, "Attempt $attempt: failed to claim interface")
connection.close()
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS)
continue
}
handler.post { Toast.makeText(context, "Kan USB interface niet claimen", Toast.LENGTH_SHORT).show() }
return
}
val epOut = findBulkEndpoint(dataInterface, UsbConstants.USB_DIR_OUT)
val epIn = findBulkEndpoint(dataInterface, UsbConstants.USB_DIR_IN)
if (epOut == null || epIn == null) {
Log.w(TAG, "Attempt $attempt: missing endpoints")
connection.close()
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS)
continue
}
handler.post { Toast.makeText(context, "USB endpoints niet gevonden", Toast.LENGTH_SHORT).show() }
return
}
Log.d(TAG, "CDC interface claimed, endpoints ready (out max=${epOut.maxPacketSize}, in max=${epIn.maxPacketSize})")
val controlIntf = findCdcControlInterface(currentDevice)
if (controlIntf != null) {
connection.claimInterface(controlIntf, true)
connection.controlTransfer(
UsbConstants.USB_DIR_OUT or UsbConstants.USB_TYPE_CLASS or 0x01,
0x20,
0,
controlIntf.id,
byteArrayOf(0x00, 0xC2.toByte(), 0x01, 0x00, 0x00, 0x00, 0x08),
7,
500
)
connection.controlTransfer(
UsbConstants.USB_DIR_OUT or UsbConstants.USB_TYPE_CLASS or 0x01,
0x22,
3,
controlIntf.id,
null,
0,
500
)
Log.d(TAG, "CDC control interface configured (line coding + DTR/RTS)")
} else {
Log.w(TAG, "No CDC control interface found, trying without")
}
Thread.sleep(300)
val cmd = "KEY?\n".toByteArray(StandardCharsets.US_ASCII)
val written = connection.bulkTransfer(epOut, cmd, cmd.size, 1000)
Log.d(TAG, "Sent KEY? (${written} bytes)")
Thread.sleep(300)
val buf = ByteArray(512)
val sb = StringBuilder()
val startTime = System.currentTimeMillis()
while (System.currentTimeMillis() - startTime < KEY_TIMEOUT_MS) {
val len = connection.bulkTransfer(epIn, buf, buf.size, 500)
if (len > 0) {
sb.append(String(buf, 0, len, StandardCharsets.US_ASCII))
if ((sb.contains("HMAC_KEY:") || sb.contains("NO_KEY")) && sb.contains("\n")) {
break
}
}
}
val response = sb.toString()
Log.d(TAG, "Serial response: ${response.take(200)}")
val keyLine = response.lines().firstOrNull { it.startsWith("HMAC_KEY:") }
val noKey = response.lines().any { it.trim() == "NO_KEY" }
if (keyLine != null) {
val hexKey = keyLine.removePrefix("HMAC_KEY:").trim()
if (hexKey.length == 64 && hexKey.all { it in "0123456789ABCDEFabcdef" }) {
val upperKey = hexKey.uppercase()
val prefs = PrefsHelper.getPrefs(context)
prefs.edit().putString(PrefsHelper.PREF_KEY, upperKey).apply()
Log.d(TAG, "HMAC key saved (${upperKey.length} chars)")
handler.post {
Toast.makeText(context, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show()
}
return
} else {
Log.e(TAG, "Invalid key format: length=${hexKey.length}")
handler.post { Toast.makeText(context, "Ongeldig sleutel formaat", Toast.LENGTH_SHORT).show() }
return
}
} else if (noKey) {
Log.d(TAG, "ESP has no key — generating and provisioning")
val newKey = generateRandomHexKey()
Log.d(TAG, "Generated key (${newKey.length} chars)")
val setKeyCmd = "SET_KEY:$newKey\n".toByteArray(StandardCharsets.US_ASCII)
val written = connection.bulkTransfer(epOut, setKeyCmd, setKeyCmd.size, 1000)
Log.d(TAG, "Sent SET_KEY (${written} bytes)")
Thread.sleep(300)
val okBuf = ByteArray(256)
val okSb = StringBuilder()
val okStart = System.currentTimeMillis()
while (System.currentTimeMillis() - okStart < SET_KEY_TIMEOUT_MS) {
val len = connection.bulkTransfer(epIn, okBuf, okBuf.size, 500)
if (len > 0) {
okSb.append(String(okBuf, 0, len, StandardCharsets.US_ASCII))
if (okSb.contains("OK") && okSb.contains("\n")) break
}
}
Log.d(TAG, "SET_KEY response: ${okSb.toString().take(100)}")
if (okSb.contains("OK")) {
Log.d(TAG, "Key provisioned successfully, saving to prefs")
val prefs = PrefsHelper.getPrefs(context)
prefs.edit().putString(PrefsHelper.PREF_KEY, newKey).apply()
handler.post {
Toast.makeText(context, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show()
}
return
} else {
Log.w(TAG, "SET_KEY failed, no OK response")
connection.close()
connection = null
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS)
}
}
} else {
Log.w(TAG, "Attempt $attempt: no HMAC_KEY in response, will retry")
connection.close()
connection = null
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS)
}
}
} catch (e: Exception) {
Log.e(TAG, "Attempt $attempt error: ${e.message}", e)
if (attempt < MAX_RETRIES) {
Thread.sleep(RETRY_DELAY_MS)
} else {
handler.post { Toast.makeText(context, "Fout bij uitlezen: ${e.message}", Toast.LENGTH_SHORT).show() }
}
} finally {
try { connection?.close() } catch (e: Exception) {}
}
}
Log.e(TAG, "All $MAX_RETRIES attempts failed")
handler.post { Toast.makeText(context, "Sleutel niet gevonden na $MAX_RETRIES pogingen", Toast.LENGTH_SHORT).show() }
}
private fun findCdcDataInterface(device: UsbDevice): UsbInterface? {
for (i in 0 until device.interfaceCount) {
val intf = device.getInterface(i)
if (intf.interfaceClass == CDC_DATA_INTERFACE_CLASS) {
return intf
}
}
return null
}
private fun findCdcControlInterface(device: UsbDevice): UsbInterface? {
for (i in 0 until device.interfaceCount) {
val intf = device.getInterface(i)
if (intf.interfaceClass == 0x02) {
return intf
}
}
return null
}
private fun findBulkEndpoint(intf: UsbInterface, direction: Int): UsbEndpoint? {
for (i in 0 until intf.endpointCount) {
val ep = intf.getEndpoint(i)
if (ep.type == UsbConstants.USB_ENDPOINT_XFER_BULK && ep.direction == direction) {
return ep
}
}
return null
}
}