package com.wzp.service import android.app.Notification import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent import android.media.AudioManager import android.net.wifi.WifiManager import android.os.IBinder import android.os.PowerManager import androidx.core.app.NotificationCompat import com.wzp.WzpApplication import com.wzp.ui.call.CallActivity /** * Foreground service that keeps the VoIP call alive when the app is backgrounded. * * Responsibilities: * - Shows a persistent notification during the call * - Acquires a partial wake lock so the CPU stays on * - Acquires a Wi-Fi lock to prevent Wi-Fi from going to sleep * - Sets [AudioManager] mode to [AudioManager.MODE_IN_COMMUNICATION] * - Releases all resources when the call ends */ class CallService : Service() { private var wakeLock: PowerManager.WakeLock? = null private var wifiLock: WifiManager.WifiLock? = null private var previousAudioMode: Int = AudioManager.MODE_NORMAL // -- Lifecycle ------------------------------------------------------------ override fun onCreate() { super.onCreate() acquireWakeLock() acquireWifiLock() setAudioMode() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_STOP -> { onStopFromNotification?.invoke() stopSelf() return START_NOT_STICKY } } startForeground(NOTIFICATION_ID, buildNotification()) return START_STICKY } override fun onDestroy() { restoreAudioMode() releaseWifiLock() releaseWakeLock() super.onDestroy() } override fun onBind(intent: Intent?): IBinder? = null // -- Notification --------------------------------------------------------- private fun buildNotification(): Notification { // Tapping the notification returns to the call screen val contentIntent = PendingIntent.getActivity( this, 0, Intent(this, CallActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP }, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) // "End call" action button val stopIntent = PendingIntent.getService( this, 1, Intent(this, CallService::class.java).apply { action = ACTION_STOP }, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) return NotificationCompat.Builder(this, WzpApplication.CHANNEL_ID) .setContentTitle("WZ Phone") .setContentText("Call in progress") .setSmallIcon(android.R.drawable.ic_menu_call) .setOngoing(true) .setContentIntent(contentIntent) .addAction(android.R.drawable.ic_menu_close_clear_cancel, "End Call", stopIntent) .setCategory(NotificationCompat.CATEGORY_CALL) .setPriority(NotificationCompat.PRIORITY_LOW) .build() } // -- Wake lock ------------------------------------------------------------ private fun acquireWakeLock() { val pm = getSystemService(Context.POWER_SERVICE) as PowerManager wakeLock = pm.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, "wzp:call_wake_lock" ).apply { acquire(MAX_CALL_DURATION_MS) } } private fun releaseWakeLock() { wakeLock?.let { if (it.isHeld) it.release() } wakeLock = null } // -- Wi-Fi lock ----------------------------------------------------------- @Suppress("DEPRECATION") private fun acquireWifiLock() { val wm = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager wifiLock = wm.createWifiLock( WifiManager.WIFI_MODE_FULL_HIGH_PERF, "wzp:call_wifi_lock" ).apply { acquire() } } private fun releaseWifiLock() { wifiLock?.let { if (it.isHeld) it.release() } wifiLock = null } // -- Audio mode ----------------------------------------------------------- private fun setAudioMode() { val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager previousAudioMode = am.mode am.mode = AudioManager.MODE_IN_COMMUNICATION } private fun restoreAudioMode() { val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager am.mode = previousAudioMode } // -- Static helpers ------------------------------------------------------- companion object { private const val NOTIFICATION_ID = 1001 private const val ACTION_STOP = "com.wzp.service.STOP" private const val MAX_CALL_DURATION_MS = 4L * 60 * 60 * 1000 // 4 hours /** Called when the user taps "End Call" in the notification. */ var onStopFromNotification: (() -> Unit)? = null /** Start the foreground call service. */ fun start(context: Context) { val intent = Intent(context, CallService::class.java) context.startForegroundService(intent) } /** Stop the foreground call service. */ fun stop(context: Context) { val intent = Intent(context, CallService::class.java).apply { action = ACTION_STOP } context.startService(intent) } } }