package com.sherpaonnxofflinestt import android.app.Notification 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.content.pm.ServiceInfo import android.os.Build import android.os.IBinder import android.os.PowerManager import androidx.core.app.NotificationCompat class STTForegroundService : Service() { companion object { const val CHANNEL_ID = "stt_recording_channel" const val NOTIFICATION_ID = 1001 const val ACTION_START = "com.sherpaonnxofflinestt.START_RECORDING" const val ACTION_STOP = "com.sherpaonnxofflinestt.STOP_RECORDING" const val WAKELOCK_TAG = "STTForegroundService::WakeLock" var isRunning: Boolean = false private set } private var wakeLock: PowerManager.WakeLock? = null override fun onCreate() { super.onCreate() createNotificationChannel() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_START -> startForegroundRecording() ACTION_STOP -> stopForegroundRecording() } return START_STICKY } override fun onBind(intent: Intent?): IBinder? = null private fun startForegroundRecording() { val notification = createNotification() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE) } else { startForeground(NOTIFICATION_ID, notification) } // Acquire wake lock to keep CPU running acquireWakeLock() isRunning = true android.util.Log.i("STTForegroundService", "Foreground service started with wake lock") } private fun stopForegroundRecording() { isRunning = false // Release wake lock releaseWakeLock() stopForeground(STOP_FOREGROUND_REMOVE) stopSelf() android.util.Log.i("STTForegroundService", "Foreground service stopped") } private fun acquireWakeLock() { if (wakeLock == null) { val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager wakeLock = powerManager.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG ).apply { setReferenceCounted(false) acquire(60 * 60 * 1000L) // 1 hour max } android.util.Log.i("STTForegroundService", "Wake lock acquired") } } private fun releaseWakeLock() { wakeLock?.let { if (it.isHeld) { it.release() android.util.Log.i("STTForegroundService", "Wake lock released") } } wakeLock = null } private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, "STT Recording", NotificationManager.IMPORTANCE_LOW ).apply { description = "Shows when speech-to-text is recording in background" setShowBadge(false) } val notificationManager = getSystemService(NotificationManager::class.java) notificationManager.createNotificationChannel(channel) } } private fun createNotification(): Notification { // Intent to open the app when notification is tapped val launchIntent = packageManager.getLaunchIntentForPackage(packageName) val pendingIntent = PendingIntent.getActivity( this, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) // Stop action val stopIntent = Intent(this, STTForegroundService::class.java).apply { action = ACTION_STOP } val stopPendingIntent = PendingIntent.getService( this, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Recording in progress") .setContentText("Speech-to-text is listening...") .setSmallIcon(android.R.drawable.ic_btn_speak_now) .setOngoing(true) .setContentIntent(pendingIntent) .addAction(android.R.drawable.ic_media_pause, "Stop", stopPendingIntent) .setPriority(NotificationCompat.PRIORITY_LOW) .setCategory(NotificationCompat.CATEGORY_SERVICE) .build() } override fun onDestroy() { isRunning = false releaseWakeLock() super.onDestroy() } }