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("JARVIS hört zu..."), ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE ) } else { startForeground(1, createNotification("JARVIS 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, "JARVIS 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("JARVIS 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() // Wir ignorieren leere Strings und bekannte Rausch-Wörter für das Log if (partialText.isNotEmpty() && partialText != "nun" && partialText.length > 2) { Log.d("JARVIS_DEBUG", "Vosk hört: $partialText") } // Unser Regex-Check (den passen wir gleich an) if (Regex("\\b(hey joe avis|hey gravis|bei gravis|hey joe|hey ich habe es|naja bis|welche avis|jarvis)\\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 }