Remove dead UsbKeyReceiver code
UsbKeyReceiver was never registered in AndroidManifest or dynamically, making it 318 lines of dead code duplicating SettingsActivity key-exchange logic and UsbUtils USB helpers. Reviewed in codereview.md (recommendation #3).
This commit is contained in:
parent
3b5831364d
commit
3616f7162f
1 changed files with 0 additions and 318 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue