Separate OneUI lockscreen and AOD filtering
This commit is contained in:
+51
-33
@@ -2,7 +2,6 @@ package se.ajpanton.notificationsmaster.module
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.util.Log
|
||||
import io.github.libxposed.api.XposedInterface
|
||||
@@ -15,12 +14,13 @@ import java.util.WeakHashMap
|
||||
/** Filters OneUI's collapsed lockscreen projection without changing notification rows. */
|
||||
internal class OneUiLockscreenBackend(private val framework: XposedInterface) : VisibilitySurfaceBackend {
|
||||
private val coordinators = Collections.newSetFromMap(WeakHashMap<Any, Boolean>())
|
||||
private val replayingLockscreen = ThreadLocal<Boolean>()
|
||||
private lateinit var refresh: Method
|
||||
@Volatile private var blockedObserved = false
|
||||
|
||||
override fun install(classLoader: ClassLoader): Boolean = runCatching {
|
||||
val coordinator = Class.forName(COORDINATOR, false, classLoader)
|
||||
val managerHandler = Class.forName(MANAGER_HANDLER, false, classLoader)
|
||||
val iconsController = Class.forName(ICONS_CONTROLLER, false, classLoader)
|
||||
val info = Class.forName(NOTIFICATION_INFO, false, classLoader)
|
||||
val sbn = info.getDeclaredField("mSbn").also {
|
||||
require(it.type == StatusBarNotification::class.java)
|
||||
@@ -33,45 +33,40 @@ internal class OneUiLockscreenBackend(private val framework: XposedInterface) :
|
||||
it.name == "onLockScreenNotiStateChanged" &&
|
||||
it.parameterTypes.isEmpty() && it.returnType == Void.TYPE
|
||||
} ?: error("OneUI lockscreen refresh method not found")
|
||||
val handle = managerHandler.declaredMethods.singleOrNull {
|
||||
it.name == "handleMessage" &&
|
||||
it.parameterTypes.contentEquals(arrayOf(Message::class.java)) && it.returnType == Void.TYPE
|
||||
} ?: error("OneUI lockscreen update handler not found")
|
||||
val updateIcons = iconsController.declaredMethods.singleOrNull {
|
||||
it.name == "onNotificationInfoUpdated" &&
|
||||
it.parameterTypes.contentEquals(arrayOf(ArrayList::class.java)) && it.returnType == Void.TYPE
|
||||
} ?: error("OneUI lockscreen icon update method not found")
|
||||
attach.isAccessible = true
|
||||
refresh.isAccessible = true
|
||||
handle.isAccessible = true
|
||||
updateIcons.isAccessible = true
|
||||
framework.hook(attach).intercept { chain ->
|
||||
synchronized(coordinators) { coordinators += chain.thisObject }
|
||||
chain.proceed()
|
||||
}
|
||||
framework.hook(handle).intercept { chain ->
|
||||
val message = chain.args[0] as? Message ?: return@intercept chain.proceed()
|
||||
if (message.what != NOTIFICATION_INFO_UPDATED) return@intercept chain.proceed()
|
||||
val original = message.obj as? ArrayList<*> ?: return@intercept chain.proceed()
|
||||
framework.hook(updateIcons).intercept { chain ->
|
||||
if (replayingLockscreen.get() == true) return@intercept chain.proceed()
|
||||
val original = chain.args[0] as? ArrayList<*> ?: return@intercept chain.proceed()
|
||||
if (original.any { it != null && !info.isInstance(it) }) return@intercept chain.proceed()
|
||||
val filtered = original.filterTo(ArrayList(original.size)) { item ->
|
||||
val notification = item?.let {
|
||||
runCatching {
|
||||
VisibilityNotificationExtractor.from(sbn.get(it) as StatusBarNotification)
|
||||
}.getOrNull()
|
||||
}
|
||||
notification == null || !ProcessVisibilityPolicyCache.isBlocked(
|
||||
notification,
|
||||
NotificationSurface.LOCKSCREEN_COLLAPSED,
|
||||
)
|
||||
}
|
||||
if (filtered.size == original.size) return@intercept chain.proceed()
|
||||
if (!blockedObserved) {
|
||||
blockedObserved = true
|
||||
Log.i(TAG, "Filtered a OneUI collapsed-lockscreen notification")
|
||||
}
|
||||
message.obj = filtered
|
||||
val aod = filter(original, sbn, NotificationSurface.AOD)
|
||||
val lockscreen = filter(original, sbn, NotificationSurface.LOCKSCREEN_COLLAPSED)
|
||||
// OneUI builds AOD icons and lockscreen state in this same callback. Let
|
||||
// AOD consume its list, then replay only the lockscreen half.
|
||||
val result = chain.proceed(arrayOf(aod))
|
||||
if (lockscreen != aod) {
|
||||
replayingLockscreen.set(true)
|
||||
OneUiSurfaceDispatch.suppressAodIconUpdate.set(true)
|
||||
try {
|
||||
chain.proceed()
|
||||
updateIcons.invoke(chain.thisObject, lockscreen)
|
||||
} catch (error: ReflectiveOperationException) {
|
||||
throw error.cause ?: error
|
||||
} finally {
|
||||
message.obj = original
|
||||
OneUiSurfaceDispatch.suppressAodIconUpdate.remove()
|
||||
replayingLockscreen.remove()
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}.onSuccess {
|
||||
Log.i(TAG, "Installed OneUI collapsed-lockscreen notification backend")
|
||||
}.onFailure {
|
||||
@@ -89,13 +84,36 @@ internal class OneUiLockscreenBackend(private val framework: XposedInterface) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun filter(
|
||||
original: ArrayList<*>,
|
||||
sbn: java.lang.reflect.Field,
|
||||
surface: NotificationSurface,
|
||||
): ArrayList<*> {
|
||||
val filtered = original.filterTo(ArrayList(original.size)) { item ->
|
||||
val notification = item?.let {
|
||||
runCatching {
|
||||
VisibilityNotificationExtractor.from(sbn.get(it) as StatusBarNotification)
|
||||
}.getOrNull()
|
||||
}
|
||||
notification == null || !ProcessVisibilityPolicyCache.isBlocked(notification, surface)
|
||||
}
|
||||
if (filtered.size < original.size && !blockedObserved) {
|
||||
blockedObserved = true
|
||||
Log.i(TAG, "Filtered a OneUI ${surface.name.lowercase()} notification")
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TAG = "NotificationsMaster"
|
||||
const val NOTIFICATION_INFO_UPDATED = 101
|
||||
const val COORDINATOR =
|
||||
"com.android.systemui.statusbar.notification.collection.coordinator.LockScreenNotiIconCoordinator"
|
||||
const val MANAGER_HANDLER =
|
||||
"com.android.systemui.statusbar.LockscreenNotificationManager\$LockscreenNotificationMgrHandler"
|
||||
const val ICONS_CONTROLLER =
|
||||
"com.android.systemui.statusbar.iconsOnly.LockscreenNotificationIconsOnlyController"
|
||||
const val NOTIFICATION_INFO = "com.android.systemui.statusbar.LockscreenNotificationInfo"
|
||||
}
|
||||
}
|
||||
|
||||
internal object OneUiSurfaceDispatch {
|
||||
val suppressAodIconUpdate = ThreadLocal<Boolean>()
|
||||
}
|
||||
|
||||
+11
-6
@@ -148,7 +148,7 @@ internal class OneUiLockscreenCardsBackend(
|
||||
calculate.isAccessible = true
|
||||
framework.hook(calculate).intercept { chain ->
|
||||
val container = chain.thisObject as? ViewGroup ?: return@intercept chain.proceed()
|
||||
if (isDozeTransition() && isCardsMode(container)) {
|
||||
if (isDozeActive() && isCardsMode(container)) {
|
||||
gridRenderer.suspend(container, true)
|
||||
}
|
||||
if (!shouldFilterLockscreenCards(container)) return@intercept chain.proceed()
|
||||
@@ -209,7 +209,7 @@ internal class OneUiLockscreenCardsBackend(
|
||||
if (active) {
|
||||
gridRenderer.refresh(container, true, visibleCardKeys(container), gridConfig())
|
||||
} else {
|
||||
gridRenderer.suspend(container, isDozeTransition() && isCardsMode(container))
|
||||
gridRenderer.suspend(container, isDozeActive() && isCardsMode(container))
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -228,6 +228,9 @@ internal class OneUiLockscreenCardsBackend(
|
||||
statusBarStateKnown = true
|
||||
}
|
||||
(findField(controller.javaClass, "mIsDozing")?.get(controller) as? Boolean)?.let { dozing = it }
|
||||
(findField(controller.javaClass, "mDozeAmount")?.get(controller) as? Number)?.let {
|
||||
dozeAmount = it.toFloat().coerceIn(0f, 1f)
|
||||
}
|
||||
applyTrackedStacks()
|
||||
}
|
||||
|
||||
@@ -237,7 +240,7 @@ internal class OneUiLockscreenCardsBackend(
|
||||
::isLockscreenCards,
|
||||
::visibleCardKeys,
|
||||
gridConfig(),
|
||||
) { isDozeTransition() && isCardsMode(it) }
|
||||
) { isDozeActive() && isCardsMode(it) }
|
||||
}
|
||||
|
||||
private fun applyStack(stack: ViewGroup) {
|
||||
@@ -257,12 +260,14 @@ internal class OneUiLockscreenCardsBackend(
|
||||
}
|
||||
|
||||
private fun isLockscreenCards(view: View) =
|
||||
shouldFilterLockscreenCards(view) && !isDozeTransition()
|
||||
shouldFilterLockscreenCards(view) && !isDozeActive()
|
||||
|
||||
private fun shouldFilterLockscreenCards(view: View) =
|
||||
statusBarStateKnown && statusBarState == KEYGUARD && isCardsMode(view)
|
||||
statusBarStateKnown && statusBarState == KEYGUARD && !isSteadyAod() && isCardsMode(view)
|
||||
|
||||
private fun isDozeTransition() = dozing || dozeAmount > 0f
|
||||
private fun isDozeActive() = dozing || dozeAmount > 0f
|
||||
|
||||
private fun isSteadyAod() = dozing && dozeAmount >= 1f
|
||||
|
||||
private fun isCardsMode(view: View) = runCatching {
|
||||
Settings.System.getInt(view.context.contentResolver, LOCKSCREEN_STYLE) == CARDS
|
||||
|
||||
@@ -61,6 +61,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
||||
result
|
||||
}
|
||||
framework.hook(updateIcons).intercept { chain ->
|
||||
if (OneUiSurfaceDispatch.suppressAodIconUpdate.get() == true) return@intercept null
|
||||
val icons = imageViewList(chain.args[0]) ?: return@intercept chain.proceed()
|
||||
val result = chain.proceed()
|
||||
notificationIconViews = icons
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
android:id="@+id/show_lockscreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Collapsed lockscreen" />
|
||||
android:text="Lockscreen" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/show_unlocked"
|
||||
|
||||
Reference in New Issue
Block a user