fix(app): WakeLock auch im AriaPlaybackService — Pipeline-weiter Schutz
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) <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,11 @@ import android.app.NotificationChannel
|
|||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.PowerManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
@@ -32,15 +34,26 @@ class AriaPlaybackService : Service() {
|
|||||||
|
|
||||||
private var currentReason: String = ""
|
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() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
ensureNotificationChannel()
|
ensureNotificationChannel()
|
||||||
|
acquireWakeLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
val reason = intent?.getStringExtra(EXTRA_REASON) ?: ""
|
val reason = intent?.getStringExtra(EXTRA_REASON) ?: ""
|
||||||
currentReason = reason
|
currentReason = reason
|
||||||
Log.i(TAG, "Foreground-Service start/update (reason=$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 {
|
try {
|
||||||
startForeground(NOTIFICATION_ID, buildNotification(reason))
|
startForeground(NOTIFICATION_ID, buildNotification(reason))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -53,10 +66,36 @@ class AriaPlaybackService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
releaseWakeLock()
|
||||||
Log.i(TAG, "Foreground-Service gestoppt")
|
Log.i(TAG, "Foreground-Service gestoppt")
|
||||||
super.onDestroy()
|
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
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
private fun ensureNotificationChannel() {
|
private fun ensureNotificationChannel() {
|
||||||
|
|||||||
Reference in New Issue
Block a user