From 543ad3c46d13ac224828367cf15cced8c0a77ad0 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 30 May 2026 20:04:55 +0200 Subject: [PATCH] =?UTF-8?q?fix(app):=20WakeLock=20auch=20im=20AriaPlayback?= =?UTF-8?q?Service=20=E2=80=94=20Pipeline-weiter=20Schutz?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stefan-Ergaenzung: nach Wake-Word muss Aufnahme, Senden und ARIA- Antwort + TTS auch im Hintergrund klappen, und danach soll das ganze wieder von vorne als Konversations-Schleife laufen. Vorher hielt nur OpenWakeWordModule einen WakeLock (commit 408d20a). Sobald Wake-Word erkannt wurde, ruft die JS-Seite OpenWakeWord.stop() fuer das Mic-Handover an audioService.startRecording() — und der WakeLock wurde released. Mid-Aufnahme konnte die CPU dann in Doze gehen, Audio-Chunks erreichten die JS-Bridge nicht zuverlaessig. Fix: AriaPlaybackService haelt selbst einen PARTIAL_WAKE_LOCK, solange der Foreground-Service aktiv ist. acquireBackgroundAudio() in der JS-Seite haelt den Service ueber alle Pipeline-Schritte (wake → rec → tts → wake) durchgehend — damit ist der WakeLock ueber die ganze Konversations-Schleife durchgehend aktiv. Doppelter Schutz (WakeLock auch im OpenWakeWordModule) bleibt drin als defense in depth — beide haben setReferenceCounted(false), also keine doppel-buchhaltung, einfach robuster gegen einzeln-failende acquires. APK neu bauen erforderlich (native Kotlin-Aenderung). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../com/ariacockpit/AriaPlaybackService.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/android/android/app/src/main/java/com/ariacockpit/AriaPlaybackService.kt b/android/android/app/src/main/java/com/ariacockpit/AriaPlaybackService.kt index 4f2121f..fbd35ed 100644 --- a/android/android/app/src/main/java/com/ariacockpit/AriaPlaybackService.kt +++ b/android/android/app/src/main/java/com/ariacockpit/AriaPlaybackService.kt @@ -5,9 +5,11 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service +import android.content.Context import android.content.Intent import android.os.Build import android.os.IBinder +import android.os.PowerManager import android.util.Log import androidx.core.app.NotificationCompat @@ -32,15 +34,26 @@ class AriaPlaybackService : Service() { private var currentReason: String = "" + // PARTIAL_WAKE_LOCK haelt die CPU wach solange der Foreground-Service + // aktiv ist. Damit bleibt die JS-Bridge im Doze ansprechbar und die + // gesamte Sprach-Pipeline (Wake → Aufnahme → POST → ARIA → TTS → wieder + // Wake) laeuft durchgehend im Hintergrund. Ein einziger Lock fuer den + // ganzen Foreground-Cycle, nicht pro Sub-Modul. + private var wakeLock: PowerManager.WakeLock? = null + override fun onCreate() { super.onCreate() ensureNotificationChannel() + acquireWakeLock() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val reason = intent?.getStringExtra(EXTRA_REASON) ?: "" currentReason = reason Log.i(TAG, "Foreground-Service start/update (reason=$reason)") + // Falls der Lock zwischendurch released wurde (z.B. nach onCreate- + // race oder OS-quirk), hier sicherheits-halber erneut anfordern. + acquireWakeLock() try { startForeground(NOTIFICATION_ID, buildNotification(reason)) } catch (e: Exception) { @@ -53,10 +66,36 @@ class AriaPlaybackService : Service() { } override fun onDestroy() { + releaseWakeLock() Log.i(TAG, "Foreground-Service gestoppt") super.onDestroy() } + private fun acquireWakeLock() { + if (wakeLock?.isHeld == true) return + try { + val pm = getSystemService(Context.POWER_SERVICE) as PowerManager + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "AriaCockpit:Pipeline").apply { + setReferenceCounted(false) + acquire(8 * 60 * 60 * 1000L) // 8h Sicherheits-Cap + } + Log.i(TAG, "WakeLock acquired (CPU bleibt wach im Hintergrund)") + } catch (e: Exception) { + Log.w(TAG, "WakeLock acquire fehlgeschlagen: ${e.message}") + } + } + + 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 + } + override fun onBind(intent: Intent?): IBinder? = null private fun ensureNotificationChannel() {