package com.navi.client import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager 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 androidx.core.app.NotificationCompat /** * Foreground service that holds the process (and its WebView WebSocket) * alive while the app is minimized: partial wake lock + dataSync FGS * priority prevent Android from freezing the renderer and dropping the * socket. The WebView keeps owning the connection itself — the service * only protects the process. */ class BackgroundService : Service() { private var wakeLock: PowerManager.WakeLock? = null override fun onCreate() { super.onCreate() val nm = getSystemService(NotificationManager::class.java) nm.createNotificationChannel( NotificationChannel( CHANNEL_BG, "Navi · фоновый режим", NotificationManager.IMPORTANCE_LOW // silent presence in the shade ).apply { description = "Держит соединение с сервером открытым, когда приложение свёрнуто" } ) nm.createNotificationChannel( NotificationChannel( CHANNEL_MSG, "Navi · сообщения", NotificationManager.IMPORTANCE_HIGH ).apply { description = "Ответы Navi и реакции Synapse" } ) startForeground(NOTIF_ID, buildNotification()) val pm = getSystemService(PowerManager::class.java) wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "navi:ws").apply { setReferenceCounted(false) acquire() } } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent?.action == ACTION_STOP) { stopForeground(STOP_FOREGROUND_REMOVE) stopSelf() return START_NOT_STICKY } return START_STICKY } override fun onDestroy() { wakeLock?.takeIf { it.isHeld }?.release() wakeLock = null super.onDestroy() } override fun onBind(intent: Intent?): IBinder? = null private fun buildNotification(): Notification = NotificationCompat.Builder(this, CHANNEL_BG) .setSmallIcon(R.drawable.ic_notification) .setContentTitle("Navi активна") .setContentText("Соединение с сервером удерживается открытым") .setOngoing(true) // Stop action: tapping kills the service without the activity. .addAction( 0, "Отключить фон", android.app.PendingIntent.getService( this, 0, Intent(this, BackgroundService::class.java).setAction(ACTION_STOP), android.app.PendingIntent.FLAG_IMMUTABLE or android.app.PendingIntent.FLAG_UPDATE_CURRENT ) ) .build() companion object { const val CHANNEL_BG = "navi_background" const val CHANNEL_MSG = "navi_messages" const val ACTION_STOP = "com.navi.client.BACKGROUND_STOP" private const val NOTIF_ID = 1 fun start(context: Context) { val intent = Intent(context, BackgroundService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent) } else { context.startService(intent) } } fun stop(context: Context) { context.startService( Intent(context, BackgroundService::class.java).setAction(ACTION_STOP) ) } } }