Add fail-open LSPosed notification probe
This commit is contained in:
@@ -92,5 +92,8 @@ dependencies {
|
||||
implementation(libs.swiperefreshlayout)
|
||||
implementation(libs.lifecycle.runtime.ktx)
|
||||
|
||||
// Present only when the user enables this APK as an LSPosed module.
|
||||
compileOnly(libs.libxposed.api)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package se.ajpanton.notificationsmaster.module
|
||||
|
||||
import android.app.Notification
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import io.github.libxposed.api.XposedInterface
|
||||
import java.lang.reflect.Method
|
||||
|
||||
/**
|
||||
* Milestone-one probe for the system-server notification path.
|
||||
*
|
||||
* It deliberately observes only this repository's test helper and always
|
||||
* calls through. No notification is modified, cancelled, or suppressed.
|
||||
*/
|
||||
internal class NotificationEnqueueProbe(private val framework: XposedInterface) {
|
||||
fun install(classLoader: ClassLoader) {
|
||||
val managerClass = Class.forName(NOTIFICATION_MANAGER_SERVICE, false, classLoader)
|
||||
val method = managerClass.declaredMethods.singleOrNull {
|
||||
it.name == ENQUEUE_NOTIFICATION &&
|
||||
it.returnType == Boolean::class.javaPrimitiveType &&
|
||||
it.parameterTypes.any { type -> type.name.endsWith("PostNotificationTracker") }
|
||||
} ?: error("No final $ENQUEUE_NOTIFICATION implementation found")
|
||||
hook(method)
|
||||
Log.i(TAG, "Installed fail-open notification enqueue probe")
|
||||
}
|
||||
|
||||
private fun hook(method: Method) {
|
||||
method.isAccessible = true
|
||||
framework.hook(method).intercept { chain ->
|
||||
logHelperNotification(chain.args)
|
||||
chain.proceed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun logHelperNotification(args: List<Any?>) {
|
||||
val notification = args.filterIsInstance<Notification>().firstOrNull() ?: return
|
||||
val packageName = args.filterIsInstance<String>().firstOrNull() ?: return
|
||||
if (packageName != TEST_HELPER_PACKAGE) return
|
||||
|
||||
val extras: Bundle = notification.extras ?: Bundle.EMPTY
|
||||
Log.i(
|
||||
TAG,
|
||||
"Observed helper notification before enqueue: title=${extras.getCharSequence(Notification.EXTRA_TITLE)} " +
|
||||
"text=${extras.getCharSequence(Notification.EXTRA_TEXT)}",
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TAG = "NotificationsMaster"
|
||||
const val NOTIFICATION_MANAGER_SERVICE = "com.android.server.notification.NotificationManagerService"
|
||||
const val ENQUEUE_NOTIFICATION = "enqueueNotificationInternal"
|
||||
const val TEST_HELPER_PACKAGE = "se.ajpanton.notificationsmaster.helper"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package se.ajpanton.notificationsmaster.module
|
||||
|
||||
import android.util.Log
|
||||
import io.github.libxposed.api.XposedModule
|
||||
import io.github.libxposed.api.XposedModuleInterface
|
||||
|
||||
/**
|
||||
* Optional libxposed entry point. It is inert unless the user enables this APK
|
||||
* in a compatible framework and grants it a system-server scope.
|
||||
*/
|
||||
class NotificationsMasterModule : XposedModule() {
|
||||
override fun onSystemServerStarting(param: XposedModuleInterface.SystemServerStartingParam) {
|
||||
runCatching {
|
||||
NotificationEnqueueProbe(this).install(param.classLoader)
|
||||
}.onFailure { error ->
|
||||
Log.e(TAG, "Notification interception probe was not installed; leaving Android unchanged", error)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TAG = "NotificationsMaster"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
se.ajpanton.notificationsmaster.module.NotificationsMasterModule
|
||||
@@ -0,0 +1,3 @@
|
||||
minApiVersion=101
|
||||
targetApiVersion=101
|
||||
staticScope=true
|
||||
@@ -0,0 +1,2 @@
|
||||
android
|
||||
system
|
||||
@@ -10,6 +10,7 @@ recyclerView = "1.3.2"
|
||||
swipeRefreshLayout = "1.1.0"
|
||||
lifecycle = "2.8.7"
|
||||
junit = "4.13.2"
|
||||
libxposed = "101.0.1"
|
||||
|
||||
[libraries]
|
||||
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
@@ -21,6 +22,7 @@ recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version
|
||||
swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swipeRefreshLayout" }
|
||||
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
libxposed-api = { group = "io.github.libxposed", name = "api", version.ref = "libxposed" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
Reference in New Issue
Block a user