Own OneUI Cards shelves during transitions
This commit is contained in:
+90
-10
@@ -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()
|
||||||
|
|||||||
+44
-24
@@ -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 =
|
||||||
|
|||||||
Reference in New Issue
Block a user