Add custom alert queue controller
This commit is contained in:
@@ -150,6 +150,22 @@ data class AlertDecision(
|
||||
val allowDuringDnd: Boolean,
|
||||
)
|
||||
|
||||
/** Resolved at match time so queued alerts never point at mutable settings. */
|
||||
data class AlertSnapshot(
|
||||
val soundUri: String?,
|
||||
val vibrationPattern: List<Long>,
|
||||
val playToCompletion: Boolean,
|
||||
val allowDuringDnd: Boolean,
|
||||
) {
|
||||
init {
|
||||
require(soundUri != null || vibrationPattern.any { it > 0 })
|
||||
}
|
||||
}
|
||||
|
||||
fun AlertDecision.snapshot(): AlertSnapshot? = profile?.let {
|
||||
AlertSnapshot(it.soundUri, it.vibrationPattern, playToCompletion, allowDuringDnd)
|
||||
}
|
||||
|
||||
data class AlertEvaluation(
|
||||
val decisions: List<AlertDecision>,
|
||||
val silenceUnmatchedDirectAlert: Boolean,
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package se.ajpanton.notificationsmaster.alerts
|
||||
|
||||
data class QueuedAlert(
|
||||
val ruleId: String,
|
||||
val snapshot: AlertSnapshot,
|
||||
)
|
||||
|
||||
/** The first alert is playing; any following alerts are waiting. */
|
||||
data class AlertQueue(
|
||||
val alerts: List<QueuedAlert> = emptyList(),
|
||||
) {
|
||||
val current get() = alerts.firstOrNull()
|
||||
}
|
||||
|
||||
data class AlertQueueTransition(
|
||||
val queue: AlertQueue,
|
||||
val started: QueuedAlert? = null,
|
||||
val interrupted: QueuedAlert? = null,
|
||||
val dropped: QueuedAlert? = null,
|
||||
val completed: QueuedAlert? = null,
|
||||
)
|
||||
|
||||
object AlertQueueController {
|
||||
fun enqueue(
|
||||
queue: AlertQueue,
|
||||
incoming: QueuedAlert,
|
||||
settings: AlertQueueSettings,
|
||||
): AlertQueueTransition {
|
||||
val alerts = queue.alerts
|
||||
if (alerts.isEmpty()) return AlertQueueTransition(AlertQueue(listOf(incoming)), started = incoming)
|
||||
|
||||
val tail = alerts.last()
|
||||
val appended = if (tail.snapshot.playToCompletion) alerts + incoming else alerts.dropLast(1) + incoming
|
||||
val interrupted = tail.takeUnless { tail.snapshot.playToCompletion }
|
||||
if (appended.size <= settings.maximumLength) {
|
||||
return AlertQueueTransition(
|
||||
AlertQueue(appended),
|
||||
started = incoming.takeIf { tail == alerts.first() && interrupted != null },
|
||||
interrupted = interrupted,
|
||||
)
|
||||
}
|
||||
return when (settings.overflow) {
|
||||
AlertQueueOverflow.DROP_NEWEST -> AlertQueueTransition(queue, dropped = incoming)
|
||||
AlertQueueOverflow.DROP_OLDEST -> {
|
||||
val next = appended.drop(1)
|
||||
AlertQueueTransition(
|
||||
AlertQueue(next),
|
||||
started = next.first(),
|
||||
interrupted = alerts.first(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun completeCurrent(queue: AlertQueue): AlertQueueTransition {
|
||||
val current = queue.current ?: return AlertQueueTransition(queue)
|
||||
val next = queue.alerts.drop(1)
|
||||
return AlertQueueTransition(AlertQueue(next), started = next.firstOrNull(), completed = current)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package se.ajpanton.notificationsmaster.alerts
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
class AlertQueueControllerTest {
|
||||
private val normal = alert("normal")
|
||||
private val protected = alert("protected", protected = true)
|
||||
private val settings = AlertQueueSettings()
|
||||
|
||||
@Test
|
||||
fun startsImmediatelyWhenQueueIsEmpty() {
|
||||
val result = AlertQueueController.enqueue(AlertQueue(), normal, settings)
|
||||
|
||||
assertEquals(listOf(normal), result.queue.alerts)
|
||||
assertEquals(normal, result.started)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replacesAndInterruptsAnInterruptibleCurrentAlert() {
|
||||
val result = AlertQueueController.enqueue(AlertQueue(listOf(normal)), alert("next"), settings)
|
||||
|
||||
assertEquals(listOf("next"), result.queue.alerts.map { it.ruleId })
|
||||
assertEquals(normal, result.interrupted)
|
||||
assertEquals("next", result.started?.ruleId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keepsProtectedCurrentAlertAndReplacesOnlyInterruptibleTail() {
|
||||
val first = protected
|
||||
val replaced = alert("replaced")
|
||||
val result = AlertQueueController.enqueue(AlertQueue(listOf(first, normal)), replaced, settings)
|
||||
|
||||
assertEquals(listOf("protected", "replaced"), result.queue.alerts.map { it.ruleId })
|
||||
assertEquals(normal, result.interrupted)
|
||||
assertNull(result.started)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun droppingOldestInterruptsCurrentAndStartsNextAlert() {
|
||||
val first = protected
|
||||
val second = alert("second", protected = true)
|
||||
val third = alert("third", protected = true)
|
||||
val incoming = alert("incoming")
|
||||
val queue = AlertQueue(listOf(first, second, third))
|
||||
|
||||
val result = AlertQueueController.enqueue(queue, incoming, settings)
|
||||
|
||||
assertEquals(listOf("second", "third", "incoming"), result.queue.alerts.map { it.ruleId })
|
||||
assertEquals(first, result.interrupted)
|
||||
assertEquals(second, result.started)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun droppingNewestKeepsExistingQueue() {
|
||||
val queue = AlertQueue(listOf(protected))
|
||||
val incoming = alert("incoming")
|
||||
val result = AlertQueueController.enqueue(
|
||||
queue,
|
||||
incoming,
|
||||
AlertQueueSettings(1, AlertQueueOverflow.DROP_NEWEST),
|
||||
)
|
||||
|
||||
assertEquals(queue, result.queue)
|
||||
assertEquals(incoming, result.dropped)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun completingCurrentStartsNextWaitingAlert() {
|
||||
val next = alert("next")
|
||||
val result = AlertQueueController.completeCurrent(AlertQueue(listOf(protected, next)))
|
||||
|
||||
assertEquals(listOf(next), result.queue.alerts)
|
||||
assertEquals(protected, result.completed)
|
||||
assertEquals(next, result.started)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snapshotDoesNotPointAtTheOriginalProfile() {
|
||||
val profile = AlertProfile("profile", "Original", "content://original")
|
||||
val decision = AlertDecision("rule", AlertOutcome.PLAY_PROFILE, profile, false, false)
|
||||
|
||||
assertEquals("content://original", decision.snapshot()?.soundUri)
|
||||
}
|
||||
|
||||
private fun alert(id: String, protected: Boolean = false) = QueuedAlert(
|
||||
id,
|
||||
AlertSnapshot("content://" + id, emptyList(), protected, false),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user