diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/module/NotificationAttentionBridge.kt b/app/src/main/java/se/ajpanton/notificationsmaster/module/NotificationAttentionBridge.kt
index c8dfd46..87a5002 100644
--- a/app/src/main/java/se/ajpanton/notificationsmaster/module/NotificationAttentionBridge.kt
+++ b/app/src/main/java/se/ajpanton/notificationsmaster/module/NotificationAttentionBridge.kt
@@ -6,21 +6,13 @@ import android.util.Log
import io.github.libxposed.api.XposedInterface
import java.lang.reflect.Method
-/**
- * API 36's standard channel-attention boundary. It deliberately leaves the
- * notification itself untouched.
- */
+/** Version-specific native-alert boundary with one shared policy decision. */
internal class NotificationAttentionBridge(
private val framework: XposedInterface,
private val observer: (SystemNotificationEvent) -> Unit,
) {
fun install(classLoader: ClassLoader) {
- if (Build.VERSION.SDK_INT != 36) return
- val helperClass = runCatching { Class.forName(ATTENTION_HELPER, false, classLoader) }.getOrNull() ?: run {
- Log.w(TAG, "Notification attention helper was not found; leaving Android unchanged")
- return
- }
- val method = helperClass.declaredMethods.singleOrNull(::isSupportedAttention) ?: run {
+ val method = attentionMethod(classLoader) ?: run {
Log.w(TAG, "Notification attention method shape was not found; leaving Android unchanged")
return
}
@@ -35,9 +27,23 @@ internal class NotificationAttentionBridge(
chain.proceed()
}
}
- Log.i(TAG, "Installed API 36 notification attention bridge")
+ Log.i(TAG, "Installed API " + Build.VERSION.SDK_INT + " notification attention bridge")
}
+ private fun attentionMethod(classLoader: ClassLoader): Method? = when (Build.VERSION.SDK_INT) {
+ 34 -> findMethod(classLoader, NOTIFICATION_MANAGER_SERVICE, ::isApi34Attention)
+ 35, 36 -> findMethod(classLoader, ATTENTION_HELPER, ::isApi35PlusAttention)
+ else -> null
+ }
+
+ private fun findMethod(
+ classLoader: ClassLoader,
+ className: String,
+ matches: (Method) -> Boolean,
+ ): Method? = runCatching {
+ Class.forName(className, false, classLoader).declaredMethods.singleOrNull(matches)
+ }.getOrNull()
+
private fun eventFrom(record: Any?): SystemNotificationEvent? = runCatching {
val sbn = record?.javaClass?.getMethod("getSbn")?.invoke(record) as? StatusBarNotification ?: return null
SystemNotificationEvent.fromStatusBarNotification(sbn)
@@ -46,12 +52,20 @@ internal class NotificationAttentionBridge(
private companion object {
const val TAG = "NotificationsMaster"
const val ATTENTION_HELPER = "com.android.server.notification.NotificationAttentionHelper"
+ const val NOTIFICATION_MANAGER_SERVICE = "com.android.server.notification.NotificationManagerService"
+ const val NOTIFICATION_RECORD = "com.android.server.notification.NotificationRecord"
- fun isSupportedAttention(method: Method): Boolean =
+ fun isApi34Attention(method: Method): Boolean =
+ method.name == "buzzBeepBlinkLocked" &&
+ method.returnType == Int::class.javaPrimitiveType &&
+ method.parameterTypes.size == 1 &&
+ method.parameterTypes[0].name == NOTIFICATION_RECORD
+
+ fun isApi35PlusAttention(method: Method): Boolean =
method.name == "buzzBeepBlinkLocked" &&
method.returnType == Int::class.javaPrimitiveType &&
method.parameterTypes.size == 2 &&
- method.parameterTypes[0].name == "com.android.server.notification.NotificationRecord" &&
+ method.parameterTypes[0].name == NOTIFICATION_RECORD &&
method.parameterTypes[1].name.endsWith("NotificationAttentionHelper\$Signals")
}
}
diff --git a/test-helper/src/main/AndroidManifest.xml b/test-helper/src/main/AndroidManifest.xml
index 20a787f..7af0472 100644
--- a/test-helper/src/main/AndroidManifest.xml
+++ b/test-helper/src/main/AndroidManifest.xml
@@ -1 +1,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-helper/src/main/java/se/ajpanton/notificationsmaster/helper/BackgroundPostReceiver.kt b/test-helper/src/main/java/se/ajpanton/notificationsmaster/helper/BackgroundPostReceiver.kt
new file mode 100644
index 0000000..d76861f
--- /dev/null
+++ b/test-helper/src/main/java/se/ajpanton/notificationsmaster/helper/BackgroundPostReceiver.kt
@@ -0,0 +1,39 @@
+package se.ajpanton.notificationsmaster.helper
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioAttributes
+import android.media.RingtoneManager
+
+/** Posts an alerting test notification without bringing the helper activity forward. */
+class BackgroundPostReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != ACTION) return
+ val manager = context.getSystemService(NotificationManager::class.java)
+ manager.createNotificationChannel(
+ NotificationChannel(CHANNEL, "Test", NotificationManager.IMPORTANCE_HIGH).apply {
+ setSound(
+ RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
+ AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build(),
+ )
+ enableVibration(true)
+ vibrationPattern = longArrayOf(0, 200)
+ },
+ )
+ manager.notify(ID, Notification.Builder(context, CHANNEL)
+ .setSmallIcon(android.R.drawable.ic_dialog_info)
+ .setContentTitle("Helper")
+ .setContentText("Text message")
+ .build())
+ }
+
+ companion object {
+ const val ACTION = "se.ajpanton.notificationsmaster.helper.POST_TEXT"
+ private const val CHANNEL = "test"
+ private const val ID = 1
+ }
+}