Add privileged alert policy endpoint
This commit is contained in:
@@ -37,6 +37,10 @@
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".module.AlertPolicyService"
|
||||
android:exported="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
+1
-15
@@ -11,21 +11,7 @@ object AlertNotificationDispatcher {
|
||||
event: AlertEvent,
|
||||
isRoutineUpdate: Boolean,
|
||||
): List<QueuedAlert> {
|
||||
if (!configuration.enabled ||
|
||||
event.source == AlertSource.NOTIFICATION_UPDATE &&
|
||||
isRoutineUpdate &&
|
||||
configuration.ignoreRoutineUpdates
|
||||
) {
|
||||
return emptyList()
|
||||
}
|
||||
val app = configuration.appSettings.firstOrNull { it.packageName == event.packageName }
|
||||
?: AlertAppSettings(event.packageName)
|
||||
return AlertRuleEvaluator.evaluate(
|
||||
app,
|
||||
configuration.rules,
|
||||
configuration.profiles.associateBy { it.id },
|
||||
event,
|
||||
).decisions.mapNotNull { decision ->
|
||||
return AlertPolicyEvaluator.evaluate(configuration, event, isRoutineUpdate).decisions.mapNotNull { decision ->
|
||||
decision.snapshot()?.let { QueuedAlert(decision.ruleId, it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package se.ajpanton.notificationsmaster.alerts
|
||||
|
||||
/** Evaluates saved alert policy without deciding how a caller will play it. */
|
||||
object AlertPolicyEvaluator {
|
||||
fun evaluate(
|
||||
configuration: AlertConfiguration,
|
||||
event: AlertEvent,
|
||||
isRoutineUpdate: Boolean,
|
||||
): AlertEvaluation {
|
||||
if (!configuration.enabled ||
|
||||
event.source == AlertSource.NOTIFICATION_UPDATE &&
|
||||
isRoutineUpdate &&
|
||||
configuration.ignoreRoutineUpdates
|
||||
) {
|
||||
return AlertEvaluation(emptyList(), false)
|
||||
}
|
||||
val app = configuration.appSettings.firstOrNull { it.packageName == event.packageName }
|
||||
?: AlertAppSettings(event.packageName)
|
||||
return AlertRuleEvaluator.evaluate(app, configuration.rules, configuration.profiles.associateBy { it.id }, event)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package se.ajpanton.notificationsmaster.module
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import se.ajpanton.notificationsmaster.alerts.AlertConfigurationStore
|
||||
import se.ajpanton.notificationsmaster.alerts.AlertEvent
|
||||
import se.ajpanton.notificationsmaster.alerts.AlertPolicyEvaluator
|
||||
import se.ajpanton.notificationsmaster.alerts.AlertSource
|
||||
import se.ajpanton.notificationsmaster.alerts.snapshot
|
||||
|
||||
/**
|
||||
* A deliberately narrow endpoint for the optional system-server module.
|
||||
* It is exported only so system_server can bind. A bind itself is not a
|
||||
* sensitive operation; every transaction is authenticated before encrypted
|
||||
* app data is read.
|
||||
*/
|
||||
class AlertPolicyService : Service() {
|
||||
override fun onBind(intent: Intent): IBinder = PolicyBinder(applicationContext)
|
||||
|
||||
private class PolicyBinder(context: android.content.Context) : Binder() {
|
||||
private val configurationStore = AlertConfigurationStore(context)
|
||||
|
||||
init { attachInterface(null, DESCRIPTOR) }
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) return false
|
||||
if (code == INTERFACE_TRANSACTION) {
|
||||
reply?.writeString(DESCRIPTOR)
|
||||
return true
|
||||
}
|
||||
if (code != TRANSACTION_EVALUATE || reply == null) return false
|
||||
data.enforceInterface(DESCRIPTOR)
|
||||
val packageName = data.readString() ?: return false
|
||||
val source = runCatching { AlertSource.valueOf(data.readString() ?: return false) }.getOrNull() ?: return false
|
||||
val event = AlertEvent(packageName, source, data.readString(), data.readString(), data.readString())
|
||||
val routineUpdate = data.readBoolean()
|
||||
val result = AlertPolicyEvaluator.evaluate(configurationStore.load(), event, routineUpdate)
|
||||
reply.writeNoException()
|
||||
reply.writeBoolean(result.silenceUnmatchedDirectAlert)
|
||||
reply.writeInt(result.decisions.size)
|
||||
result.decisions.forEach { decision ->
|
||||
reply.writeString(decision.ruleId)
|
||||
reply.writeString(decision.outcome.name)
|
||||
val snapshot = decision.snapshot()
|
||||
reply.writeBoolean(snapshot != null)
|
||||
snapshot?.let {
|
||||
reply.writeString(it.soundUri)
|
||||
reply.writeLongArray(it.vibrationPattern.toLongArray())
|
||||
reply.writeBoolean(it.playToCompletion)
|
||||
reply.writeBoolean(it.allowDuringDnd)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DESCRIPTOR = "se.ajpanton.notificationsmaster.AlertPolicy"
|
||||
const val TRANSACTION_EVALUATE = IBinder.FIRST_CALL_TRANSACTION
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package se.ajpanton.notificationsmaster.alerts
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class AlertPolicyEvaluatorTest {
|
||||
private val profile = AlertProfile("profile", "Profile", vibrationPattern = listOf(0, 10))
|
||||
private val rule = AlertRule("rule", "chat.app", 0, setOf(AlertSource.NOTIFICATION_POST), outcome = AlertOutcome.PLAY_PROFILE, profileId = profile.id)
|
||||
|
||||
@Test fun `returns no decision when alerting is disabled`() {
|
||||
assertTrue(AlertPolicyEvaluator.evaluate(AlertConfiguration(enabled = false, profiles = listOf(profile), rules = listOf(rule)), event(), false).decisions.isEmpty())
|
||||
}
|
||||
|
||||
@Test fun `returns the evaluated decision for a post`() {
|
||||
assertEquals(listOf("rule"), AlertPolicyEvaluator.evaluate(AlertConfiguration(profiles = listOf(profile), rules = listOf(rule)), event(), false).decisions.map { it.ruleId })
|
||||
}
|
||||
|
||||
private fun event() = AlertEvent("chat.app", AlertSource.NOTIFICATION_POST)
|
||||
}
|
||||
Reference in New Issue
Block a user