From 408d20a087c450b644eb2db7c13f6ee0076853fb Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 30 May 2026 20:03:08 +0200 Subject: [PATCH] =?UTF-8?q?fix(app):=20PARTIAL=5FWAKE=5FLOCK=20fuer=20Wake?= =?UTF-8?q?-Word=20=E2=80=94=20JS-Bridge=20bleibt=20im=20Hintergrund=20akt?= =?UTF-8?q?iv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stefan-Bug-Report: Wake-Word wird im Hintergrund erkannt (Spotify pausiert sofort), aber der Gong + Aufnahme-Start kommen erst wenn die App in den Vordergrund geholt wird. Akku-Optimierung war bereits deaktiviert ("Hintergrund aktiv"). Ursache: Foreground-Service haelt den App-Prozess am Leben + erlaubt mic-Zugriff via foregroundServiceType=microphone. Aber: ohne expliziten WakeLock kann die CPU im Doze-Mode (Display aus / Telefon idle) die Auslieferung von DeviceEvents an die React-Native-JS-Bridge pausieren. Folge: Native erkennt Wake-Word, ruft emit("WakeWordDetected"), aber das Event queued sich nur — der JS-Listener (onWakeDetected → start- Recording + playWakeReadySound) feuert erst beim naechsten JS-Tick, und der kommt erst beim App-Resume. Fix: - AndroidManifest: WAKE_LOCK Permission hinzu (kein User-Prompt noetig, ist eine "normal" Permission). - OpenWakeWordModule.kt: PowerManager.PARTIAL_WAKE_LOCK in start() acquired (8h Cap als Sicherheit), in stop() + dispose() released. Lock-Tag "AriaCockpit:WakeWordRecord" damit der in adb shell dumpsys power sichtbar ist. Wirkung: solange Wake-Word "armed" ist, bleibt die CPU wach und die JS-Bridge verarbeitet die Detection-Events live — Gong, Mic-Start, ARIA-Antwort kommen ohne Foreground-Resume durch. APK muss neu gebaut werden (native Kotlin-Aenderung). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../android/app/src/main/AndroidManifest.xml | 5 +++ .../com/ariacockpit/OpenWakeWordModule.kt | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/android/android/app/src/main/AndroidManifest.xml b/android/android/app/src/main/AndroidManifest.xml index c104164..b8d0d85 100644 --- a/android/android/app/src/main/AndroidManifest.xml +++ b/android/android/app/src/main/AndroidManifest.xml @@ -17,6 +17,11 @@ + + = ArrayList(256) // Liste von 32-dim Frames private var melProcessedIdx: Int = 0 @@ -198,6 +207,21 @@ class OpenWakeWordModule(reactContext: ReactApplicationContext) : ReactContextBa running.set(true) record.startRecording() + // PARTIAL_WAKE_LOCK greifen damit die CPU nicht in Doze geht und + // die JS-Bridge die emit("WakeWordDetected")-Events live verarbeitet. + // 8h Cap als Sicherheit gegen forgotten-release. + try { + val pm = reactApplicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "AriaCockpit:WakeWordRecord").apply { + setReferenceCounted(false) + acquire(8 * 60 * 60 * 1000L) + } + Log.i(TAG, "WakeLock acquired") + } catch (e: Exception) { + Log.w(TAG, "WakeLock acquire fehlgeschlagen: ${e.message}") + } + captureThread = Thread({ captureLoop() }, "OpenWakeWordCapture").apply { isDaemon = true start() @@ -232,6 +256,7 @@ class OpenWakeWordModule(reactContext: ReactApplicationContext) : ReactContextBa try { audioRecord?.release() } catch (_: Exception) {} audioRecord = null releaseAudioEffects() + releaseWakeLock() Log.i(TAG, "Lauschen gestoppt") promise.resolve(true) } @@ -245,10 +270,21 @@ class OpenWakeWordModule(reactContext: ReactApplicationContext) : ReactContextBa try { audioRecord?.release() } catch (_: Exception) {} audioRecord = null releaseAudioEffects() + releaseWakeLock() disposeSessions() promise.resolve(true) } + private fun releaseWakeLock() { + try { + wakeLock?.takeIf { it.isHeld }?.release() + if (wakeLock != null) Log.i(TAG, "WakeLock released") + } catch (e: Exception) { + Log.w(TAG, "WakeLock release fehlgeschlagen: ${e.message}") + } + wakeLock = null + } + @ReactMethod fun isAvailable(promise: Promise) { // Wake-Word ist immer verfuegbar (kein API-Key, alles on-device)