From e116f2b49340b58e691d033014e3516eeed0a01b Mon Sep 17 00:00:00 2001 From: eddy Date: Sun, 26 Jul 2026 17:02:06 +0200 Subject: [PATCH] Make EspFlasher cancellable to prevent resource leak Add @Volatile cancelled flag with cancel() method. Check flag at 3 checkpoints (after initial sleep, after transport setup, before each flash step) so the thread exits early and closes the USB connection in the finally block. SettingsActivity.onDestroy now calls cancel(). Reviewed in codereview.md (recommendation 3.2). --- .../src/main/java/com/eddy/atomrelay/EspFlasher.kt | 11 +++++++++++ .../main/java/com/eddy/atomrelay/SettingsActivity.kt | 7 +++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt b/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt index 03e3eef..480c410 100644 --- a/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt +++ b/android/app/src/main/java/com/eddy/atomrelay/EspFlasher.kt @@ -25,7 +25,15 @@ class EspFlasher(private val context: Context) { private val handler = Handler(Looper.getMainLooper()) + @Volatile + private var cancelled = false + + fun cancel() { + cancelled = true + } + fun flashDevice(usbManager: UsbManager, device: UsbDevice, callback: FlashCallback) { + cancelled = false Thread { try { doFlash(usbManager, device, callback) @@ -47,6 +55,7 @@ class EspFlasher(private val context: Context) { private fun doFlash(usbManager: UsbManager, device: UsbDevice, callback: FlashCallback) { Thread.sleep(500) + if (cancelled) return val connection = usbManager.openDevice(device) ?: throw IOException("Kan USB-apparaat niet openen") @@ -67,6 +76,7 @@ class EspFlasher(private val context: Context) { postProgress(callback, 0, "Verbinden met ESP bootloader...") val transport = UsbSerialTransport(connection, epOut, epIn) + if (cancelled) return val assets = context.assets val bootloader = assets.open("firmware/bootloader.bin").readBytes() @@ -107,6 +117,7 @@ class EspFlasher(private val context: Context) { stub.eraseFlash() for (step in steps) { + if (cancelled) return val sizeStr = if (step.data.size > 1024) "${step.data.size / 1024}KB" else "${step.data.size} bytes" postProgress(callback, step.baseProgress, "${step.name} schrijven ($sizeStr)...") diff --git a/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt b/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt index ccfecb3..3b6563b 100644 --- a/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt +++ b/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt @@ -43,6 +43,7 @@ class SettingsActivity : AppCompatActivity() { private var currentAction = UsbAction.NONE private var progressDialog: AlertDialog? = null private var dialog: AlertDialog? = null + private var flasher: EspFlasher? = null private val handler = Handler(Looper.getMainLooper()) private val usbAttachedReceiver = object : BroadcastReceiver() { @@ -120,6 +121,8 @@ class SettingsActivity : AppCompatActivity() { override fun onDestroy() { super.onDestroy() currentAction = UsbAction.NONE + flasher?.cancel() + flasher = null try { unregisterReceiver(usbAttachedReceiver) } catch (e: Exception) {} try { unregisterReceiver(usbPermissionReceiver) } catch (e: Exception) {} try { unregisterReceiver(usbDetachedReceiver) } catch (e: Exception) {} @@ -203,8 +206,8 @@ class SettingsActivity : AppCompatActivity() { private fun doFlashing(usbManager: UsbManager, device: UsbDevice) { showProgressDialog("Firmware Flashen", "Initialiseren...", STYLE_HORIZONTAL) - val flasher = EspFlasher(this) - flasher.flashDevice(usbManager, device, object : EspFlasher.FlashCallback { + flasher = EspFlasher(this) + flasher?.flashDevice(usbManager, device, object : EspFlasher.FlashCallback { override fun onProgress(percent: Int, message: String) { runOnUiThread { val d = progressDialog ?: return@runOnUiThread