Files
jarvis-ai-stts/app/src/main/java/com/example/jarvis_stts/JarviceService.kt

185 lines
6.3 KiB
Kotlin

package com.example.jarvis_stts
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import org.vosk.Model
import org.vosk.Recognizer
import org.vosk.android.RecognitionListener
import org.vosk.android.SpeechService
import java.io.File
import android.content.pm.ServiceInfo
import org.json.JSONObject
class JarvisService : Service(), RecognitionListener {
private var voskService: SpeechService? = null
private var voskModel: Model? = null
private var isInteracting = false
companion object {
const val CHANNEL_ID = "JarvisServiceChannel"
const val ACTION_START = "ACTION_START"
const val ACTION_PAUSE = "ACTION_PAUSE"
const val ACTION_RESUME = "ACTION_RESUME"
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
// Für Android 14 (API 34) und höher müssen wir den Typ beim Starten mitgeben
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(
1,
createNotification("J.A.R.V.I.S. hört zu..."),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
)
} else {
startForeground(1, createNotification("J.A.R.V.I.S. hört zu..."))
}
initVosk()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_START -> resumeListening()
ACTION_PAUSE -> pauseListening()
ACTION_RESUME -> resumeListening()
}
return START_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"J.A.R.V.I.S. Background Service",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
private fun createNotification(text: String): android.app.Notification {
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("J.A.R.V.I.S. ist aktiv")
.setContentText(text)
.setSmallIcon(android.R.drawable.ic_btn_speak_now) // Standard Android Icon
.setContentIntent(pendingIntent)
.setOngoing(true)
.build()
}
private fun updateNotification(text: String) {
val manager = getSystemService(NotificationManager::class.java)
manager.notify(1, createNotification(text))
}
private fun initVosk() {
val baseDir = getExternalFilesDir(null)
val modelFolder = File(baseDir, "model/model-de")
if (modelFolder.exists()) {
Log.d("JARVIS", "Modell gefunden unter: ${modelFolder.absolutePath}")
try {
// Wir laden das Modell und speichern es in der Klassen-Variable 'voskModel'
voskModel = Model(modelFolder.absolutePath)
Log.d("JARVIS", "Modell bereit, starte Hintergrund-Dienst...")
// Jetzt, wo das Modell da ist, können wir das Zuhören starten
resumeListening()
} catch (e: Exception) {
Log.e("JARVIS", "Vosk Fehler beim Modell-Laden: ${e.message}")
updateNotification("Fehler: Modell konnte nicht geladen werden")
}
} else {
Log.e("JARVIS", "ORDNER NICHT GEFUNDEN! Pfad: ${modelFolder.absolutePath}")
updateNotification("Fehler: Modell-Ordner fehlt")
}
}
private fun pauseListening() {
Log.d("JARVIS", "Service: Pausiere Zuhören")
voskService?.stop()
updateNotification("Pausiert (verarbeitet Anfrage...)")
}
private fun resumeListening() {
if (voskModel == null) return
Log.d("JARVIS", "Service: Starte Zuhören (Volles Wörterbuch)")
voskService?.stop()
// GANZ WICHTIG: Keine Grammar (Wortliste) mehr übergeben!
// Wir nutzen das volle deutsche Vokabular, um Fehlinterpretationen zu vermeiden.
val rec = Recognizer(voskModel, 16000.0f)
voskService = SpeechService(rec, 16000.0f)
voskService?.startListening(this)
isInteracting = false
updateNotification("Warte auf 'Jarvis'")
}
override fun onPartialResult(hypothesis: String) {
if (isInteracting) return
try {
val json = JSONObject(hypothesis)
val partialText = json.optString("partial").lowercase().trim()
// HIER: Lass dir mal ausgeben, was er eigentlich versteht!
if (partialText.isNotEmpty()) {
Log.d("JARVIS_DEBUG", "Vosk hört: $partialText")
}
// Unser Regex-Check (den passen wir gleich an)
if (Regex("\\b(hey joe avis)\\b").containsMatchIn(partialText)) {
Log.d("JARVIS", "WAKE WORD ERKANNT: $partialText")
triggerJarvis()
}
} catch (e: Exception) {
Log.e("JARVIS", "Fehler beim Parsen: ${e.message}")
}
}
// onResult bleibt leer oder loggt nur zur Kontrolle
override fun onResult(hypothesis: String) {}
private fun triggerJarvis() {
isInteracting = true
pauseListening()
val intent = Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
putExtra("WAKE_WORD_TRIGGERED", true)
}
startActivity(intent)
}
override fun onFinalResult(hypothesis: String) {}
override fun onError(e: Exception) { Log.e("JARVIS", "Service Error: ${e.message}") }
override fun onTimeout() {}
override fun onDestroy() {
super.onDestroy()
voskService?.stop()
voskService?.shutdown()
}
override fun onBind(intent: Intent?): IBinder? = null
}