Fix OneUI lockscreen card overflow filtering

This commit is contained in:
ajp_anton
2026-08-29 20:40:02 +00:00
parent c831e9adfb
commit c14b2c5e61
@@ -20,6 +20,13 @@ internal class OneUiLockscreenCardsBackend(
private val hiddenRows = WeakHashMap<View, RowState>()
private lateinit var entryForRow: (View) -> Any?
private lateinit var sbnForEntry: (Any) -> StatusBarNotification?
private lateinit var shelfNotification: Field
private lateinit var shelfIconStates: Field
private lateinit var shelfClampedAmount: Field
private lateinit var shelfAppearAmount: Field
private lateinit var shelfAlpha: Field
private lateinit var shelfHidden: Field
private lateinit var shelfVisibleState: Field
@Volatile private var statusBarStateKnown = false
@Volatile private var statusBarState = SHADE
@Volatile private var dozing = false
@@ -31,6 +38,7 @@ internal class OneUiLockscreenCardsBackend(
val rowClass = Class.forName(ROW, false, classLoader)
val stateController = Class.forName(STATE_CONTROLLER, false, classLoader)
prepareNotificationAccess(rowClass)
installShelfFilter(classLoader)
stackClass.declaredMethods.filter { it.name == "onViewAdded" }.forEach { method ->
method.isAccessible = true
@@ -103,6 +111,43 @@ internal class OneUiLockscreenCardsBackend(
sbnForEntry = { accessor(it) as? StatusBarNotification }
}
private fun installShelfFilter(classLoader: ClassLoader) {
val shelfIcons = Class.forName(SHELF_ICONS, false, classLoader)
val iconView = Class.forName(STATUS_BAR_ICON_VIEW, false, classLoader)
shelfNotification = iconView.getDeclaredField("mNotification").also {
require(it.type == StatusBarNotification::class.java)
it.isAccessible = true
}
shelfIconStates = findField(shelfIcons, "mIconStates")
?: error("OneUI shelf icon states are unavailable")
val iconState = Class.forName(ICON_STATE, false, classLoader)
shelfClampedAmount = requireField(iconState, "clampedAppearAmount")
shelfAppearAmount = requireField(iconState, "iconAppearAmount")
shelfAlpha = requireField(iconState, "mAlpha")
shelfHidden = requireField(iconState, "hidden")
shelfVisibleState = requireField(iconState, "visibleState")
val calculate = shelfIcons.declaredMethods.singleOrNull {
it.name == "calculateIconXTranslations" && it.parameterCount == 0
} ?: error("OneUI shelf icon layout method not found")
calculate.isAccessible = true
framework.hook(calculate).intercept { chain ->
val container = chain.thisObject as? ViewGroup ?: return@intercept chain.proceed()
if (!isLockscreenCards(container)) return@intercept chain.proceed()
val children = List(container.childCount, container::getChildAt)
val blocked = children.filter(::isBlockedShelfIcon)
if (blocked.isEmpty()) return@intercept chain.proceed()
val states = shelfIconStates.get(container) as? Map<*, *>
?: return@intercept chain.proceed()
blocked.forEach { icon -> states[icon]?.let(::hideShelfIconState) }
children.filterNot(blocked::contains).forEach(container::bringChildToFront)
try {
chain.proceed()
} finally {
children.forEach(container::bringChildToFront)
}
}
}
private fun accessor(method: Method?, field: Field?): (Any) -> Any? {
method?.isAccessible = true
field?.isAccessible = true
@@ -123,7 +168,7 @@ internal class OneUiLockscreenCardsBackend(
}
private fun applyStack(stack: ViewGroup) {
val active = statusBarStateKnown && statusBarState == KEYGUARD && !dozing && isCardsMode(stack)
val active = isLockscreenCards(stack)
repeat(stack.childCount) { index ->
val row = stack.getChildAt(index)
if (row.javaClass.name == ROW) {
@@ -134,16 +179,30 @@ internal class OneUiLockscreenCardsBackend(
private fun applyRow(row: View) {
val stack = row.parent as? ViewGroup ?: return
val active = statusBarStateKnown && statusBarState == KEYGUARD && !dozing && isCardsMode(stack)
val active = isLockscreenCards(stack)
if (active && isBlocked(row)) hideRow(row) else restoreRow(row)
}
private fun isLockscreenCards(view: View) =
statusBarStateKnown && statusBarState == KEYGUARD && !dozing && isCardsMode(view)
private fun isCardsMode(view: View) = runCatching {
Settings.System.getInt(view.context.contentResolver, LOCKSCREEN_STYLE) == CARDS
}.getOrDefault(false)
private fun isBlocked(row: View): Boolean {
val sbn = runCatching { entryForRow(row)?.let(sbnForEntry) }.getOrNull() ?: return false
return isBlocked(sbn)
}
private fun isBlockedShelfIcon(icon: View): Boolean {
if (icon.javaClass.name != STATUS_BAR_ICON_VIEW) return false
val sbn = runCatching { shelfNotification.get(icon) as? StatusBarNotification }.getOrNull()
?: return false
return isBlocked(sbn)
}
private fun isBlocked(sbn: StatusBarNotification): Boolean {
return ProcessVisibilityPolicyCache.isBlocked(
VisibilityNotificationExtractor.from(sbn),
NotificationSurface.LOCKSCREEN_COLLAPSED,
@@ -155,6 +214,14 @@ internal class OneUiLockscreenCardsBackend(
}
}
private fun hideShelfIconState(state: Any) {
shelfClampedAmount.setFloat(state, 0f)
shelfAppearAmount.setFloat(state, 0f)
shelfAlpha.setFloat(state, 0f)
shelfHidden.setBoolean(state, true)
shelfVisibleState.setInt(state, ICON_HIDDEN)
}
private fun hideRow(row: View) {
if (row.visibility == View.GONE && row.alpha == 0f) return
hiddenRows.putIfAbsent(row, RowState(row.visibility, row.alpha))
@@ -183,6 +250,9 @@ internal class OneUiLockscreenCardsBackend(
}
}
private fun requireField(type: Class<*>, name: String) =
findField(type, name) ?: error("OneUI $name field is unavailable")
private fun findField(type: Class<*>, vararg names: String): Field? {
var current: Class<*>? = type
while (current != null) {
@@ -204,10 +274,14 @@ internal class OneUiLockscreenCardsBackend(
const val SHADE = 0
const val KEYGUARD = 1
const val CARDS = 0
const val ICON_HIDDEN = 2
const val LOCKSCREEN_STYLE = "lockscreen_minimizing_notification"
const val STACK = "com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout"
const val ROW = "com.android.systemui.statusbar.notification.row.ExpandableNotificationRow"
const val STATE_CONTROLLER = "com.android.systemui.statusbar.StatusBarStateControllerImpl"
const val SHELF_ICONS = "com.android.systemui.statusbar.phone.SecShelfNotificationIconContainer"
const val STATUS_BAR_ICON_VIEW = "com.android.systemui.statusbar.StatusBarIconView"
const val ICON_STATE = "com.android.systemui.statusbar.phone.NotificationIconContainer\$IconState"
const val ONE_UI_LOCKSCREEN_COORDINATOR =
"com.android.systemui.statusbar.notification.collection.coordinator.LockScreenNotiIconCoordinator"
}