Own OneUI Cards shelves during transitions

This commit is contained in:
ajp_anton
2026-08-30 12:11:35 +00:00
parent 0a0e7c7649
commit 192bdb5381
3 changed files with 224 additions and 65 deletions
@@ -11,6 +11,7 @@ import android.graphics.drawable.GradientDrawable
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import se.ajpanton.notificationsmaster.visibility.CardsGridLayout import se.ajpanton.notificationsmaster.visibility.CardsGridLayout
@@ -21,6 +22,7 @@ import java.util.WeakHashMap
/** Renders a OneUI Cards-mode icon grid from notification icon snapshots. */ /** Renders a OneUI Cards-mode icon grid from notification icon snapshots. */
internal class OneUiCardsGridRenderer { internal class OneUiCardsGridRenderer {
private val states = WeakHashMap<ViewGroup, State>() private val states = WeakHashMap<ViewGroup, State>()
private val nativeSurfaces = WeakHashMap<View, NativeSurface>()
fun updateSources(container: ViewGroup, sources: List<Source>) { fun updateSources(container: ViewGroup, sources: List<Source>) {
val unique = LinkedHashMap<String, Source>() val unique = LinkedHashMap<String, Source>()
@@ -33,6 +35,10 @@ internal class OneUiCardsGridRenderer {
state.active = active state.active = active
state.visibleCardKeys = visibleCardKeys state.visibleCardKeys = visibleCardKeys
state.config = config state.config = config
if (active) {
val native = if (config.hideShelf) findNativeSurface(container) else container
if (native !== container.rootView) hideNative(state, native)
}
container.post { render(container, state, state.active) } container.post { render(container, state, state.active) }
} }
@@ -40,27 +46,44 @@ internal class OneUiCardsGridRenderer {
active: (ViewGroup) -> Boolean, active: (ViewGroup) -> Boolean,
visibleCardKeys: (ViewGroup) -> Set<String>, visibleCardKeys: (ViewGroup) -> Set<String>,
config: Config, config: Config,
suppressNativeWhenInactive: (ViewGroup) -> Boolean = { false },
) { ) {
states.keys.toList().forEach { container -> states.keys.toList().forEach { container ->
refresh(container, active(container), visibleCardKeys(container), config) if (active(container)) {
refresh(container, true, visibleCardKeys(container), config)
} else {
state(container).config = config
suspend(container, suppressNativeWhenInactive(container))
}
} }
} }
fun deactivate(container: ViewGroup) { fun deactivate(container: ViewGroup) {
states[container]?.let { state -> states[container]?.let { suspend(container, false) }
state.active = false
failOpen(state)
}
} }
fun deactivateAll() { fun deactivateAll() {
states.keys.toList().forEach(::deactivate) states.keys.toList().forEach(::deactivate)
} }
fun suspend(container: ViewGroup, suppressNative: Boolean, nativeSurface: View? = null) {
val state = state(container)
state.active = false
state.host?.visibility = View.GONE
if (suppressNative) {
val native = nativeSurface
?: if (state.config.hideShelf) findNativeSurface(container) else container
if (native !== container.rootView) hideNative(state, native)
} else {
restoreNative(state)
}
}
fun release(container: ViewGroup) { fun release(container: ViewGroup) {
val state = states.remove(container) ?: return val state = states.remove(container) ?: return
restoreNative(state) restoreNative(state)
state.attachListener?.let(container::removeOnAttachStateChangeListener) state.attachListener?.let(container::removeOnAttachStateChangeListener)
removePreDrawListener(state)
state.layoutListener?.let { state.root?.removeOnLayoutChangeListener(it) } state.layoutListener?.let { state.root?.removeOnLayoutChangeListener(it) }
state.host?.let { host -> state.host?.let { host ->
clearHost(host) clearHost(host)
@@ -74,6 +97,13 @@ internal class OneUiCardsGridRenderer {
return return
} }
val root = container.rootView as? ViewGroup ?: return failOpen(state) val root = container.rootView as? ViewGroup ?: return failOpen(state)
if (state.config.settings.maxRows == 0) {
state.host?.let {
clearHost(it)
it.visibility = View.GONE
}
return
}
val remaining = state.sources.filterNot { it.key in state.visibleCardKeys } val remaining = state.sources.filterNot { it.key in state.visibleCardKeys }
if (remaining.isEmpty()) { if (remaining.isEmpty()) {
failOpen(state) failOpen(state)
@@ -234,12 +264,24 @@ internal class OneUiCardsGridRenderer {
private fun findNativeSurface(container: ViewGroup): View { private fun findNativeSurface(container: ViewGroup): View {
var current = container.parent as? View var current = container.parent as? View
while (current != null && current !== container.rootView) { while (current != null && current !== container.rootView) {
if (current.javaClass.name.contains("NotificationShelf")) return current if (current is ViewGroup && current.javaClass.name.contains("NotificationShelf")) {
return findShelfBackground(current) ?: container
}
current = current.parent as? View current = current.parent as? View
} }
return container return container
} }
private fun findShelfBackground(root: ViewGroup): View? {
val pending = ArrayDeque<View>().apply { add(root) }
while (pending.isNotEmpty()) {
val view = pending.removeFirst()
if (view.javaClass.name.contains("NotificationShelfBackground")) return view
if (view is ViewGroup) repeat(view.childCount) { pending += view.getChildAt(it) }
}
return null
}
private fun clearHost(host: ViewGroup) { private fun clearHost(host: ViewGroup) {
repeat(host.childCount) { index -> recycleIcon(host.getChildAt(index)) } repeat(host.childCount) { index -> recycleIcon(host.getChildAt(index)) }
host.removeAllViews() host.removeAllViews()
@@ -259,16 +301,47 @@ internal class OneUiCardsGridRenderer {
if (state.nativeSurface !== native) { if (state.nativeSurface !== native) {
restoreNative(state) restoreNative(state)
state.nativeSurface = native state.nativeSurface = native
state.nativeAlpha = native.alpha nativeSurfaces.getOrPut(native) { NativeSurface(native.alpha) }.owners += state
} }
state.nativeSuppressed = true
native.alpha = 0f native.alpha = 0f
} }
private fun restoreNative(state: State) { private fun restoreNative(state: State) {
state.nativeSurface?.alpha = state.nativeAlpha state.nativeSuppressed = false
state.nativeSurface?.let { native ->
nativeSurfaces[native]?.let { surface ->
surface.owners -= state
if (surface.owners.isEmpty()) {
native.alpha = surface.alpha
nativeSurfaces.remove(native)
}
}
}
state.nativeSurface = null state.nativeSurface = null
} }
private fun installPreDrawListener(container: ViewGroup, state: State) {
removePreDrawListener(state)
val observer = container.viewTreeObserver.takeIf(ViewTreeObserver::isAlive) ?: return
val listener = ViewTreeObserver.OnPreDrawListener {
if (state.nativeSuppressed) state.nativeSurface?.let { native ->
if (native.alpha != 0f) native.alpha = 0f
}
true
}
observer.addOnPreDrawListener(listener)
state.preDrawObserver = observer
state.preDrawListener = listener
}
private fun removePreDrawListener(state: State) {
val listener = state.preDrawListener ?: return
state.preDrawObserver?.takeIf(ViewTreeObserver::isAlive)?.removeOnPreDrawListener(listener)
state.preDrawObserver = null
state.preDrawListener = null
}
private fun findDrawable(view: View?): Drawable? = when (view) { private fun findDrawable(view: View?): Drawable? = when (view) {
is ImageView -> view.drawable is ImageView -> view.drawable
is ViewGroup -> (0 until view.childCount).firstNotNullOfOrNull { findDrawable(view.getChildAt(it)) } is ViewGroup -> (0 until view.childCount).firstNotNullOfOrNull { findDrawable(view.getChildAt(it)) }
@@ -297,9 +370,10 @@ internal class OneUiCardsGridRenderer {
private fun state(container: ViewGroup) = states.getOrPut(container) { private fun state(container: ViewGroup) = states.getOrPut(container) {
State().also { state -> State().also { state ->
state.attachListener = object : View.OnAttachStateChangeListener { state.attachListener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(view: View) = Unit override fun onViewAttachedToWindow(view: View) = installPreDrawListener(container, state)
override fun onViewDetachedFromWindow(view: View) = release(container) override fun onViewDetachedFromWindow(view: View) = release(container)
}.also(container::addOnAttachStateChangeListener) }.also(container::addOnAttachStateChangeListener)
if (container.isAttachedToWindow) installPreDrawListener(container, state)
} }
} }
@@ -326,11 +400,17 @@ internal class OneUiCardsGridRenderer {
var attachListener: View.OnAttachStateChangeListener? = null var attachListener: View.OnAttachStateChangeListener? = null
var active = false var active = false
var nativeSurface: View? = null var nativeSurface: View? = null
var nativeAlpha = 1f var nativeSuppressed = false
var preDrawObserver: ViewTreeObserver? = null
var preDrawListener: ViewTreeObserver.OnPreDrawListener? = null
var lastDiagnosticSignature = 0 var lastDiagnosticSignature = 0
var config = Config(CardsGridSettings.LOCKSCREEN_DEFAULT, showPills = true, hideShelf = true) var config = Config(CardsGridSettings.LOCKSCREEN_DEFAULT, showPills = true, hideShelf = true)
} }
private class NativeSurface(val alpha: Float) {
val owners = mutableSetOf<State>()
}
private companion object { private companion object {
const val HOST_TAG = "notifications-master:cards-grid" const val HOST_TAG = "notifications-master:cards-grid"
const val DEFAULT_PILL_COLOR = 0xAA202124.toInt() const val DEFAULT_PILL_COLOR = 0xAA202124.toInt()
@@ -32,7 +32,9 @@ internal class OneUiLockscreenCardsBackend(
@Volatile private var statusBarStateKnown = false @Volatile private var statusBarStateKnown = false
@Volatile private var statusBarState = SHADE @Volatile private var statusBarState = SHADE
@Volatile private var dozing = false @Volatile private var dozing = false
@Volatile private var dozeAmount = 0f
@Volatile private var blockedObserved = false @Volatile private var blockedObserved = false
@Volatile private var nativeShelfSuppressedObserved = false
override fun install(classLoader: ClassLoader): Boolean = runCatching { override fun install(classLoader: ClassLoader): Boolean = runCatching {
Class.forName(ONE_UI_LOCKSCREEN_COORDINATOR, false, classLoader) Class.forName(ONE_UI_LOCKSCREEN_COORDINATOR, false, classLoader)
@@ -85,9 +87,16 @@ internal class OneUiLockscreenCardsBackend(
method.isAccessible = true method.isAccessible = true
framework.hook(method).intercept { chain -> framework.hook(method).intercept { chain ->
val leavingLockscreen = method.name == "setState" && chain.args.firstOrNull() != KEYGUARD val leavingLockscreen = method.name == "setState" && chain.args.firstOrNull() != KEYGUARD
val enteringDoze = method.name == "setDozeAmountInternal" && if (method.name == "setDozeAmountInternal") {
((chain.args.firstOrNull() as? Number)?.toFloat() ?: 0f) > 0f dozeAmount = ((chain.args.firstOrNull() as? Number)?.toFloat() ?: 0f)
if (leavingLockscreen || enteringDoze) gridRenderer.deactivateAll() .coerceIn(0f, 1f)
}
val enteringDoze = dozeAmount > 0f
if (enteringDoze) {
gridRenderer.refreshAll({ false }, { emptySet() }, gridConfig(), ::isCardsMode)
} else if (leavingLockscreen) {
gridRenderer.deactivateAll()
}
val result = chain.proceed() val result = chain.proceed()
updateState(chain.thisObject) updateState(chain.thisObject)
result result
@@ -139,19 +148,29 @@ internal class OneUiLockscreenCardsBackend(
calculate.isAccessible = true calculate.isAccessible = true
framework.hook(calculate).intercept { chain -> framework.hook(calculate).intercept { chain ->
val container = chain.thisObject as? ViewGroup ?: return@intercept chain.proceed() val container = chain.thisObject as? ViewGroup ?: return@intercept chain.proceed()
if (!isLockscreenCards(container)) return@intercept chain.proceed() if (isDozeTransition() && isCardsMode(container)) {
gridRenderer.suspend(container, true)
}
if (!shouldFilterLockscreenCards(container)) return@intercept chain.proceed()
val children = List(container.childCount, container::getChildAt) 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<*, *> val states = shelfIconStates.get(container) as? Map<*, *>
?: return@intercept chain.proceed() ?: return@intercept chain.proceed()
val blocked = children.filter(::isBlockedShelfIcon)
blocked.forEach { icon -> states[icon]?.let(::hideShelfIconState) } blocked.forEach { icon -> states[icon]?.let(::hideShelfIconState) }
children.filterNot(blocked::contains).forEach(container::bringChildToFront) children.filterNot(blocked::contains).forEach(container::bringChildToFront)
try { val result = try {
chain.proceed() chain.proceed()
} finally { } finally {
children.forEach(container::bringChildToFront) children.forEach(container::bringChildToFront)
} }
// OneUI applies these states after this calculation returns. Suppress every
// stock shelf icon in Cards mode; the renderer owns the supplemental grid.
children.forEach { icon -> states[icon]?.let(::hideShelfIconState) }
if (!nativeShelfSuppressedObserved) {
nativeShelfSuppressedObserved = true
Log.i(TAG, "Suppressed the native OneUI Cards shelf icon states")
}
result
} }
} }
@@ -187,12 +206,11 @@ internal class OneUiLockscreenCardsBackend(
val result = chain.proceed(arguments) val result = chain.proceed(arguments)
gridRenderer.updateSources(container, sources) gridRenderer.updateSources(container, sources)
val active = isLockscreenCards(container) val active = isLockscreenCards(container)
gridRenderer.refresh( if (active) {
container, gridRenderer.refresh(container, true, visibleCardKeys(container), gridConfig())
active, } else {
if (active) visibleCardKeys(container) else emptySet(), gridRenderer.suspend(container, isDozeTransition() && isCardsMode(container))
gridConfig(), }
)
result result
} }
} }
@@ -210,17 +228,20 @@ internal class OneUiLockscreenCardsBackend(
statusBarStateKnown = true statusBarStateKnown = true
} }
(findField(controller.javaClass, "mIsDozing")?.get(controller) as? Boolean)?.let { dozing = it } (findField(controller.javaClass, "mIsDozing")?.get(controller) as? Boolean)?.let { dozing = it }
if (dozing || statusBarState != KEYGUARD) gridRenderer.deactivateAll()
applyTrackedStacks() applyTrackedStacks()
} }
private fun applyTrackedStacks() { private fun applyTrackedStacks() {
synchronized(stacks) { stacks.toList() }.forEach(::applyStack) synchronized(stacks) { stacks.toList() }.forEach(::applyStack)
gridRenderer.refreshAll(::isLockscreenCards, ::visibleCardKeys, gridConfig()) gridRenderer.refreshAll(
::isLockscreenCards,
::visibleCardKeys,
gridConfig(),
) { isDozeTransition() && isCardsMode(it) }
} }
private fun applyStack(stack: ViewGroup) { private fun applyStack(stack: ViewGroup) {
val active = isLockscreenCards(stack) val active = shouldFilterLockscreenCards(stack)
repeat(stack.childCount) { index -> repeat(stack.childCount) { index ->
val row = stack.getChildAt(index) val row = stack.getChildAt(index)
if (row.javaClass.name == ROW) { if (row.javaClass.name == ROW) {
@@ -231,12 +252,17 @@ internal class OneUiLockscreenCardsBackend(
private fun applyRow(row: View) { private fun applyRow(row: View) {
val stack = row.parent as? ViewGroup ?: return val stack = row.parent as? ViewGroup ?: return
val active = isLockscreenCards(stack) val active = shouldFilterLockscreenCards(stack)
if (active && isBlocked(row)) hideRow(row) else restoreRow(row) if (active && isBlocked(row)) hideRow(row) else restoreRow(row)
} }
private fun isLockscreenCards(view: View) = private fun isLockscreenCards(view: View) =
statusBarStateKnown && statusBarState == KEYGUARD && !dozing && isCardsMode(view) shouldFilterLockscreenCards(view) && !isDozeTransition()
private fun shouldFilterLockscreenCards(view: View) =
statusBarStateKnown && statusBarState == KEYGUARD && isCardsMode(view)
private fun isDozeTransition() = dozing || dozeAmount > 0f
private fun isCardsMode(view: View) = runCatching { private fun isCardsMode(view: View) = runCatching {
Settings.System.getInt(view.context.contentResolver, LOCKSCREEN_STYLE) == CARDS Settings.System.getInt(view.context.contentResolver, LOCKSCREEN_STYLE) == CARDS
@@ -303,8 +329,6 @@ internal class OneUiLockscreenCardsBackend(
} }
private fun visibleCardKeys(container: ViewGroup): Set<String> { private fun visibleCardKeys(container: ViewGroup): Set<String> {
val shelfLocation = IntArray(2)
runCatching { container.getLocationOnScreen(shelfLocation) }.getOrElse { return emptySet() }
val root = container.rootView val root = container.rootView
return synchronized(stacks) { stacks.toList() } return synchronized(stacks) { stacks.toList() }
.asSequence() .asSequence()
@@ -314,10 +338,6 @@ internal class OneUiLockscreenCardsBackend(
row.javaClass.name == ROW && row.visibility == View.VISIBLE && row.alpha > 0.05f && row.javaClass.name == ROW && row.visibility == View.VISIBLE && row.alpha > 0.05f &&
!booleanField(row, "mInShelf") && !booleanField(row, "mTransformingInShelf") !booleanField(row, "mInShelf") && !booleanField(row, "mTransformingInShelf")
} }
.filter { row ->
val location = IntArray(2)
runCatching { row.getLocationOnScreen(location) }.isSuccess && location[1] < shelfLocation[1]
}
.mapNotNull { row -> runCatching { entryForRow(row)?.let(sbnForEntry)?.key }.getOrNull() } .mapNotNull { row -> runCatching { entryForRow(row)?.let(sbnForEntry)?.key }.getOrNull() }
.toSet() .toSet()
} }
@@ -20,7 +20,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
private val managers = Collections.synchronizedMap(WeakHashMap<Any, State>()) private val managers = Collections.synchronizedMap(WeakHashMap<Any, State>())
private val cardsContainers = Collections.newSetFromMap(WeakHashMap<ViewGroup, Boolean>()) private val cardsContainers = Collections.newSetFromMap(WeakHashMap<ViewGroup, Boolean>())
private val cardsDozing = Collections.synchronizedMap(WeakHashMap<ViewGroup, Boolean>()) private val cardsDozing = Collections.synchronizedMap(WeakHashMap<ViewGroup, Boolean>())
private val cardsReadyAt = Collections.synchronizedMap(WeakHashMap<ViewGroup, Long>()) private val cardsTransitions = Collections.synchronizedMap(WeakHashMap<ViewGroup, CardsTransition>())
private val cardsLocations = Collections.synchronizedMap(WeakHashMap<ViewGroup, Pair<Int, Int>>()) private val cardsLocations = Collections.synchronizedMap(WeakHashMap<ViewGroup, Pair<Int, Int>>())
private val cardsRenderer = OneUiCardsGridRenderer() private val cardsRenderer = OneUiCardsGridRenderer()
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
@@ -107,18 +107,19 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
framework.hook(method).intercept { chain -> framework.hook(method).intercept { chain ->
val container = chain.thisObject as? ViewGroup val container = chain.thisObject as? ViewGroup
val dozing = chain.args.firstOrNull() == true val dozing = chain.args.firstOrNull() == true
val origin = container?.let(::screenLocation)
container?.let { container?.let {
trackCardsContainer(it) trackCardsContainer(it)
cardsDozing[it] = false cardsDozing[it] = false
cardsRenderer.deactivate(it) cardsRenderer.suspend(it, isCardsMode(it), it)
} }
val result = chain.proceed() val result = chain.proceed()
container?.let { view -> container?.let { view ->
cardsDozing[view] = dozing cardsDozing[view] = dozing
if (dozing) settleCardsGrid(view, DOZE_SETTLE_DELAY) else { if (dozing) beginDozeTransition(view, origin ?: screenLocation(view)) else {
cardsReadyAt.remove(view) cardsTransitions.remove(view)
cardsLocations.remove(view) cardsLocations.remove(view)
refreshCardsGrid() cardsRenderer.suspend(view, isCardsMode(view), view)
} }
} }
result result
@@ -146,37 +147,56 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
} }
container.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { container.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(view: View) { override fun onViewAttachedToWindow(view: View) {
if (cardsDozing[container] == true) settleCardsGrid(container) else refreshCardsGrid() if (cardsDozing[container] == true) beginLayoutSettle(container) else refreshCardsGrid()
} }
override fun onViewDetachedFromWindow(view: View) { override fun onViewDetachedFromWindow(view: View) {
cardsDozing[container] = false cardsDozing[container] = false
cardsReadyAt.remove(container) cardsTransitions.remove(container)
cardsLocations.remove(container) cardsLocations.remove(container)
cardsRenderer.release(container) cardsRenderer.release(container)
} }
}) })
container.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> container.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
if (cardsDozing[container] == true) settleCardsGrid(container) else refreshCardsGrid() if (cardsDozing[container] == true) {
if (cardsTransitions[container] == null) beginLayoutSettle(container) else refreshCardsGrid()
} else refreshCardsGrid()
} }
} }
refreshCardsGrid() refreshCardsGrid()
} }
private fun settleCardsGrid(container: ViewGroup, delay: Long = LAYOUT_SETTLE_DELAY) { private fun beginDozeTransition(container: ViewGroup, origin: Pair<Int, Int>) {
cardsRenderer.deactivate(container)
cardsReadyAt[container] = SystemClock.uptimeMillis() + delay
refreshCardsGrid()
}
private fun refreshCardsGrid() {
cardsRefreshTask?.let(mainHandler::removeCallbacks)
val now = SystemClock.uptimeMillis() val now = SystemClock.uptimeMillis()
val delay = synchronized(cardsReadyAt) { val current = screenLocation(container)
cardsReadyAt.entries cardsTransitions[container] = CardsTransition(
.filter { cardsDozing[it.key] == true } origin,
.maxOfOrNull { (it.value - now).coerceAtLeast(0) } ?: 0 current,
current != origin,
now,
now + TRANSITION_FALLBACK_DELAY,
)
cardsRenderer.suspend(container, isCardsMode(container), container)
refreshCardsGrid(POSITION_POLL_DELAY)
} }
private fun beginLayoutSettle(container: ViewGroup) {
val now = SystemClock.uptimeMillis()
val current = screenLocation(container)
val origin = cardsLocations[container] ?: current
cardsTransitions[container] = CardsTransition(
origin,
current,
moved = true,
stableSince = now,
deadline = now + TRANSITION_FALLBACK_DELAY,
)
cardsRenderer.suspend(container, isCardsMode(container), container)
refreshCardsGrid(POSITION_POLL_DELAY)
}
private fun refreshCardsGrid(delay: Long = 0L) {
cardsRefreshTask?.let(mainHandler::removeCallbacks)
cardsRefreshTask = Runnable { cardsRefreshTask = Runnable {
cardsRefreshTask = null cardsRefreshTask = null
renderCardsGrid() renderCardsGrid()
@@ -184,23 +204,53 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
} }
private fun renderCardsGrid() { private fun renderCardsGrid() {
val moving = synchronized(cardsContainers) { cardsContainers.toList() }.filter { container -> val ready = ArrayList<ViewGroup>()
if (cardsDozing[container] != true) return@filter false var pollAgain = false
synchronized(cardsContainers) { cardsContainers.toList() }.forEach { container ->
if (!container.isAttachedToWindow) return@forEach
if (!isCardsMode(container)) {
cardsTransitions.remove(container)
cardsRenderer.deactivate(container)
return@forEach
}
if (cardsDozing[container] != true) {
cardsTransitions.remove(container)
cardsRenderer.suspend(container, true, container)
return@forEach
}
val location = screenLocation(container) val location = screenLocation(container)
val previous = cardsLocations.put(container, location) val transition = cardsTransitions[container]
previous == null || previous != location if (transition == null) {
cardsLocations[container] = location
ready += container
return@forEach
} }
if (moving.isNotEmpty()) { cardsRenderer.suspend(container, true, container)
moving.forEach { settleCardsGrid(it) } val now = SystemClock.uptimeMillis()
return if (location != transition.lastLocation) {
transition.moved = transition.moved || location != transition.origin
transition.lastLocation = location
transition.stableSince = now
} }
if (transition.moved && now - transition.stableSince >= POSITION_STABLE_DELAY ||
now >= transition.deadline
) {
cardsTransitions.remove(container)
cardsLocations[container] = location
ready += container
} else {
pollAgain = true
}
}
if (pollAgain) refreshCardsGrid(POSITION_POLL_DELAY)
if (ready.isEmpty()) return
val notifications = visibleNotifications.distinctBy(StatusBarNotification::getKey) val notifications = visibleNotifications.distinctBy(StatusBarNotification::getKey)
val config = OneUiCardsGridRenderer.Config( val config = OneUiCardsGridRenderer.Config(
ProcessVisibilityPolicyCache.aodCards, ProcessVisibilityPolicyCache.aodCards,
showPills = false, showPills = false,
hideShelf = false, hideShelf = false,
) )
synchronized(cardsContainers) { cardsContainers.toList() }.forEach { container -> ready.forEach { container ->
val tint = runCatching { val tint = runCatching {
container.javaClass.getMethod("getIconColor").invoke(container) as Int container.javaClass.getMethod("getIconColor").invoke(container) as Int
}.getOrNull() }.getOrNull()
@@ -219,7 +269,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
cardsRenderer.updateSources(container, sources) cardsRenderer.updateSources(container, sources)
cardsRenderer.refresh( cardsRenderer.refresh(
container, container,
container.isAttachedToWindow && cardsDozing[container] == true && isCardsMode(container), active = true,
emptySet(), emptySet(),
config, config,
) )
@@ -288,11 +338,20 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
val type: Int, val type: Int,
) )
private data class CardsTransition(
val origin: Pair<Int, Int>,
var lastLocation: Pair<Int, Int>,
var moved: Boolean,
var stableSince: Long,
val deadline: Long,
)
private companion object { private companion object {
const val TAG = "NotificationsMaster" const val TAG = "NotificationsMaster"
const val CARDS = 0 const val CARDS = 0
const val LAYOUT_SETTLE_DELAY = 120L const val POSITION_POLL_DELAY = 80L
const val DOZE_SETTLE_DELAY = 220L const val POSITION_STABLE_DELAY = 180L
const val TRANSITION_FALLBACK_DELAY = 2_500L
const val NOTIFICATION_STYLE = "lockscreen_minimizing_notification" const val NOTIFICATION_STYLE = "lockscreen_minimizing_notification"
const val MANAGER = "com.samsung.android.uniform.plugins.notification.AODNotificationManager" const val MANAGER = "com.samsung.android.uniform.plugins.notification.AODNotificationManager"
const val AOD_ICON_CONTAINER = const val AOD_ICON_CONTAINER =