Keep debug notifications independent
This commit is contained in:
@@ -151,7 +151,9 @@ object DebugNotifications {
|
||||
private fun cancelAbove(context: Context, manager: NotificationManager, desired: Int) {
|
||||
manager.activeNotifications.filter { it.packageName == context.packageName }.forEach { sbn ->
|
||||
val number = sbn.id - ID_BASE
|
||||
if (number in 1..MAX_COUNT && number > desired) cancel(manager, sbn)
|
||||
val autoSummary = !isDebug(sbn.notification) &&
|
||||
sbn.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0
|
||||
if (autoSummary || number in 1..MAX_COUNT && number > desired) cancel(manager, sbn)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package se.ajpanton.notificationsmaster.module
|
||||
|
||||
import android.app.Notification
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.util.Log
|
||||
import io.github.libxposed.api.XposedInterface
|
||||
import java.lang.reflect.Method
|
||||
|
||||
/** Prevents Android from collapsing this app's marked layout-test notifications. */
|
||||
internal class DebugNotificationAutoGroupBridge(private val framework: XposedInterface) {
|
||||
fun install(classLoader: ClassLoader) {
|
||||
val groupHelper = findClass(GROUP_HELPER, classLoader) ?: return unavailable()
|
||||
val recordClass = findClass(NOTIFICATION_RECORD, classLoader) ?: return unavailable()
|
||||
val notificationManager = findClass(NOTIFICATION_MANAGER, classLoader) ?: return unavailable()
|
||||
|
||||
val blockGrouping = XposedInterface.Hooker { chain ->
|
||||
if (!isDebugRecord(chain.args.firstOrNull())) return@Hooker chain.proceed()
|
||||
if ((chain.executable as? Method)?.returnType == Boolean::class.javaPrimitiveType) false else null
|
||||
}
|
||||
GROUP_METHODS.forEach { hookMethods(groupHelper, it, blockGrouping) }
|
||||
hookMethods(recordClass, "setOverrideGroupKey") { chain ->
|
||||
if (isDebugRecord(chain.thisObject)) null else chain.proceed()
|
||||
}
|
||||
val dropSummary = XposedInterface.Hooker { chain ->
|
||||
val info = notificationInfo(chain.args.toTypedArray())
|
||||
if (info?.packageName == APP_PACKAGE && !info.isDebug && info.isGroupSummary) null
|
||||
else chain.proceed()
|
||||
}
|
||||
hookMethods(notificationManager, "enqueueNotificationInternal", dropSummary)
|
||||
hookMethods(notificationManager, "enqueueNotificationInternalLocked", dropSummary)
|
||||
Log.i(TAG, "Installed debug-notification auto-group guard")
|
||||
}
|
||||
|
||||
private fun hookMethods(type: Class<*>, name: String, hooker: XposedInterface.Hooker) {
|
||||
type.declaredMethods.filter { it.name == name }.forEach { method ->
|
||||
method.isAccessible = true
|
||||
framework.hook(method).intercept(hooker)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDebugRecord(record: Any?): Boolean {
|
||||
val sbn = invoke(record, "getSbn") as? StatusBarNotification
|
||||
val packageName = sbn?.packageName ?: invoke(record, "getPackageName") as? String
|
||||
val notification = sbn?.notification ?: invoke(record, "getNotification") as? Notification
|
||||
return packageName == APP_PACKAGE && notification?.extras?.getBoolean(DEBUG_EXTRA, false) == true
|
||||
}
|
||||
|
||||
private fun notificationInfo(args: Array<Any?>): NotificationInfo? {
|
||||
var packageName: String? = null
|
||||
var notification: Notification? = null
|
||||
args.forEach { arg ->
|
||||
when (arg) {
|
||||
is StatusBarNotification -> {
|
||||
packageName = arg.packageName
|
||||
notification = arg.notification
|
||||
}
|
||||
is Notification -> notification = arg
|
||||
is String -> if (packageName == null && arg == APP_PACKAGE) packageName = arg
|
||||
else -> if (arg?.javaClass?.name?.contains("NotificationRecord") == true) {
|
||||
val sbn = invoke(arg, "getSbn") as? StatusBarNotification
|
||||
packageName = sbn?.packageName ?: invoke(arg, "getPackageName") as? String ?: packageName
|
||||
notification = sbn?.notification ?: invoke(arg, "getNotification") as? Notification ?: notification
|
||||
}
|
||||
}
|
||||
}
|
||||
val value = notification ?: return null
|
||||
return NotificationInfo(
|
||||
packageName ?: return null,
|
||||
value.extras?.getBoolean(DEBUG_EXTRA, false) == true,
|
||||
value.flags and Notification.FLAG_GROUP_SUMMARY != 0,
|
||||
)
|
||||
}
|
||||
|
||||
private fun invoke(instance: Any?, name: String): Any? {
|
||||
var type = instance?.javaClass
|
||||
while (type != null) {
|
||||
val method = runCatching { type.getDeclaredMethod(name) }.getOrNull()
|
||||
if (method != null) return runCatching {
|
||||
method.isAccessible = true
|
||||
method.invoke(instance)
|
||||
}.getOrNull()
|
||||
type = type.superclass
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findClass(name: String, classLoader: ClassLoader) =
|
||||
runCatching { Class.forName(name, false, classLoader) }.getOrNull()
|
||||
|
||||
private fun unavailable() {
|
||||
Log.w(TAG, "Debug-notification auto-group boundary is unavailable")
|
||||
}
|
||||
|
||||
private data class NotificationInfo(
|
||||
val packageName: String,
|
||||
val isDebug: Boolean,
|
||||
val isGroupSummary: Boolean,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val TAG = "NotificationsMaster"
|
||||
const val APP_PACKAGE = "se.ajpanton.notificationsmaster"
|
||||
const val DEBUG_EXTRA = "se.ajpanton.notificationsmaster.DEBUG_NOTIFICATION"
|
||||
const val GROUP_HELPER = "com.android.server.notification.GroupHelper"
|
||||
const val NOTIFICATION_RECORD = "com.android.server.notification.NotificationRecord"
|
||||
const val NOTIFICATION_MANAGER = "com.android.server.notification.NotificationManagerService"
|
||||
val GROUP_METHODS = listOf(
|
||||
"onNotificationPosted",
|
||||
"onNotificationRemoved",
|
||||
"maybeGroup",
|
||||
"maybeUngroup",
|
||||
"autoGroup",
|
||||
"autogroup",
|
||||
"maybeAutoGroup",
|
||||
"shouldAutoGroup",
|
||||
"isEligibleForAutoGroup",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,9 @@ class NotificationsMasterModule : XposedModule() {
|
||||
override fun onSystemServerStarting(param: XposedModuleInterface.SystemServerStartingParam) {
|
||||
SystemAlertPolicyCache.installWhenReady()
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
installBridge("debug notification auto-group") {
|
||||
DebugNotificationAutoGroupBridge(this).install(param.classLoader)
|
||||
}
|
||||
installBridge("notification attention") {
|
||||
NotificationAttentionBridge(this, ::diagnoseAttentionHelperEvent).install(param.classLoader)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user