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)
)
}
}
}