Model ongoing call notifications and independent foreground stop failures

This commit is contained in:
evgeny committed 2026-10-29 13:34:23 +01:00
1 parent a65385bbb1
commit 0a48cccd9a
3 files changed
+42 -4

No files matched your search

@@ -5,6 +5,7 @@ import android.content.Intent
import android.net.Uri
class Notification {
companion object { const val VISIBILITY_PUBLIC = 1 }
data class Action(val title: String, val intent: PendingIntent)
var channelId = ""
var title = ""
@@ -17,10 +18,14 @@ class Notification {
var contentIntent: PendingIntent? = null
var fullScreenIntent: PendingIntent? = null
var bigText: String? = null
var visibility = 0; var foregroundBehavior = 0; var showWhen = false; var chronometer = false; var whenMs = 0L
var person: androidx.core.app.Person? = null
var hangup: PendingIntent? = null
val actions = mutableListOf<Action>()
}
class NotificationChannel(val id: String, val name: String, val importance: Int) {
var lockscreenVisibility = 0
var description = ""
var vibration = true
var silent = false
@@ -30,7 +35,7 @@ class NotificationChannel(val id: String, val name: String, val importance: Int)
/** Host OS boundaries: отказы канала, разрешений и публикации независимы. */
class NotificationManager {
companion object { const val IMPORTANCE_HIGH = 4; const val IMPORTANCE_LOW = 2 }
companion object { const val IMPORTANCE_HIGH = 4; const val IMPORTANCE_LOW = 2; const val IMPORTANCE_DEFAULT = 3 }
var failures = emptySet<String>()
var notificationsEnabled = true
var fullScreenAllowed = true
@@ -45,6 +50,10 @@ class NotificationManager {
check("channel" !in failures) { "channel unavailable" }
channels[channel.id] = channel
}
fun getNotificationChannel(id: String): NotificationChannel? {
check("channel query" !in failures) { "channel query unavailable" }
return channels[id]
}
fun canUseFullScreenIntent(): Boolean {
fullScreenQueries++
check("full screen" !in failures) { "full-screen service unavailable" }
@@ -94,6 +103,7 @@ class PendingIntent private constructor(val kind: String, val requestCode: Int,
}
fun getActivity(context: Context, requestCode: Int, intent: Intent, flags: Int) = obtain("activity", requestCode, intent, flags)
fun getBroadcast(context: Context, requestCode: Int, intent: Intent, flags: Int) = obtain("broadcast", requestCode, intent, flags)
fun getService(context: Context, requestCode: Int, intent: Intent, flags: Int) = obtain("service", requestCode, intent, flags)
}
private var intent = Intent(intent)
var failCancel = false
@@ -13,6 +13,9 @@ open class Service : Context() {
var failForeground = false
var failForegroundAfter = false
var stops = 0
var foregroundStops = 0
var failForegroundStop = false
var failStopSelf = false
var destroys = 0
open fun onCreate() {}
open fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_NOT_STICKY
@@ -25,7 +28,11 @@ open class Service : Context() {
requireNotNull(notificationManager).notify(id, notification)
check(!failForegroundAfter) { "foreground promotion failed after effect" }
}
fun stopForeground(flags: Int) { if (flags == STOP_FOREGROUND_REMOVE && foregroundId != 0) notificationManager?.cancel(foregroundId) }
fun stopSelf() { stops++ }
fun stopSelf(startId: Int) { stops++ }
fun stopForeground(flags: Int) {
foregroundStops++
check(!failForegroundStop) { "foreground removal unavailable" }
if (flags == STOP_FOREGROUND_REMOVE && foregroundId != 0) notificationManager?.cancel(foregroundId)
}
fun stopSelf() { stops++; check(!failStopSelf) { "service stop unavailable" } }
fun stopSelf(startId: Int) { stopSelf() }
}
@@ -8,11 +8,16 @@ import android.content.Context
object NotificationCompat {
const val CATEGORY_CALL = "call"
const val PRIORITY_MAX = 2
const val VISIBILITY_PUBLIC = 1
const val FOREGROUND_SERVICE_IMMEDIATE = 1
var failBuild = false
class BigTextStyle {
var text = ""
fun bigText(value: String) = apply { text = value }
}
class CallStyle(val person: Person, val hangup: PendingIntent) {
companion object { fun forOngoingCall(person: Person, hangup: PendingIntent) = CallStyle(person, hangup) }
}
class Builder(context: Context, channelId: String) {
private val notification = Notification().apply { this.channelId = channelId }
fun setSmallIcon(value: Int) = this
@@ -26,11 +31,27 @@ object NotificationCompat {
fun setContentIntent(value: PendingIntent) = apply { notification.contentIntent = value }
fun setFullScreenIntent(value: PendingIntent, priority: Boolean) = apply { check(priority); notification.fullScreenIntent = value }
fun setStyle(value: BigTextStyle) = apply { notification.bigText = value.text }
fun setStyle(value: CallStyle) = apply { notification.person = value.person; notification.hangup = value.hangup }
fun setVisibility(value: Int) = apply { notification.visibility = value }
fun setForegroundServiceBehavior(value: Int) = apply { notification.foregroundBehavior = value }
fun addPerson(value: Person) = this
fun setShowWhen(value: Boolean) = apply { notification.showWhen = value }
fun setUsesChronometer(value: Boolean) = apply { notification.chronometer = value }
fun setWhen(value: Long) = apply { notification.whenMs = value }
fun addAction(icon: Int, title: String, intent: PendingIntent) = apply { notification.actions.add(Notification.Action(title, intent)) }
fun build(): Notification { check(!failBuild) { "notification build unavailable" }; return notification }
}
}
class Person private constructor(val name: String, val key: String) {
class Builder {
private var name = ""; private var key = ""
fun setName(value: String) = apply { name = value }
fun setKey(value: String) = apply { key = value }
fun build() = Person(name, key)
}
}
class NotificationManagerCompat private constructor(private val context: Context) {
companion object { fun from(context: Context) = NotificationManagerCompat(context) }
private val manager get() = requireNotNull(context.notificationManager) { "notification manager unavailable" }