fix: stabilize OneUI AOD notification grids
This commit is contained in:
@@ -39,7 +39,7 @@ internal class OneUiCardsGridRenderer {
|
|||||||
val native = if (config.hideShelf) findNativeSurface(container) else container
|
val native = if (config.hideShelf) findNativeSurface(container) else container
|
||||||
if (native !== container.rootView) hideNative(state, native)
|
if (native !== container.rootView) hideNative(state, native)
|
||||||
}
|
}
|
||||||
container.post { render(container, state, state.active) }
|
scheduleRender(container, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshAll(
|
fun refreshAll(
|
||||||
@@ -69,6 +69,7 @@ internal class OneUiCardsGridRenderer {
|
|||||||
fun suspend(container: ViewGroup, suppressNative: Boolean, nativeSurface: View? = null) {
|
fun suspend(container: ViewGroup, suppressNative: Boolean, nativeSurface: View? = null) {
|
||||||
val state = state(container)
|
val state = state(container)
|
||||||
state.active = false
|
state.active = false
|
||||||
|
cancelRender(container, state)
|
||||||
state.host?.visibility = View.GONE
|
state.host?.visibility = View.GONE
|
||||||
if (suppressNative) {
|
if (suppressNative) {
|
||||||
val native = nativeSurface
|
val native = nativeSurface
|
||||||
@@ -84,6 +85,7 @@ internal class OneUiCardsGridRenderer {
|
|||||||
restoreNative(state)
|
restoreNative(state)
|
||||||
state.attachListener?.let(container::removeOnAttachStateChangeListener)
|
state.attachListener?.let(container::removeOnAttachStateChangeListener)
|
||||||
removePreDrawListener(state)
|
removePreDrawListener(state)
|
||||||
|
cancelRender(container, 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)
|
||||||
@@ -91,11 +93,24 @@ internal class OneUiCardsGridRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun render(container: ViewGroup, state: State, active: Boolean) {
|
private fun scheduleRender(container: ViewGroup, state: State) {
|
||||||
if (!active || !container.isAttachedToWindow) {
|
if (state.renderTask != null) return
|
||||||
failOpen(state)
|
val task = Runnable {
|
||||||
return
|
state.renderTask = null
|
||||||
|
if (states[container] === state) render(container, state)
|
||||||
}
|
}
|
||||||
|
state.renderTask = task
|
||||||
|
if (!container.post(task)) state.renderTask = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelRender(container: ViewGroup, state: State) {
|
||||||
|
state.renderTask?.let(container::removeCallbacks)
|
||||||
|
state.renderTask = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun render(container: ViewGroup, state: State) {
|
||||||
|
if (!state.active) return
|
||||||
|
if (!container.isAttachedToWindow) return failOpen(state)
|
||||||
val root = container.rootView as? ViewGroup ?: return failOpen(state)
|
val root = container.rootView as? ViewGroup ?: return failOpen(state)
|
||||||
if (state.config.settings.maxRows == 0) {
|
if (state.config.settings.maxRows == 0) {
|
||||||
state.host?.let {
|
state.host?.let {
|
||||||
@@ -128,6 +143,10 @@ internal class OneUiCardsGridRenderer {
|
|||||||
|
|
||||||
clearHost(host)
|
clearHost(host)
|
||||||
host.visibility = View.VISIBLE
|
host.visibility = View.VISIBLE
|
||||||
|
host.alpha = 1f
|
||||||
|
host.translationX = 0f
|
||||||
|
host.translationY = 0f
|
||||||
|
state.anchorLocation = location
|
||||||
val rowGap = dp(root, 8)
|
val rowGap = dp(root, 8)
|
||||||
val nativeSurface = if (state.config.hideShelf) findNativeSurface(container) else container
|
val nativeSurface = if (state.config.hideShelf) findNativeSurface(container) else container
|
||||||
if (nativeSurface === root) return failOpen(state)
|
if (nativeSurface === root) return failOpen(state)
|
||||||
@@ -152,6 +171,7 @@ internal class OneUiCardsGridRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hideNative(state, nativeSurface)
|
hideNative(state, nativeSurface)
|
||||||
|
syncSourceMovement(container, state)
|
||||||
val diagnosticSignature = listOf(remaining.size, state.visibleCardKeys.size, rows.visibleSlots,
|
val diagnosticSignature = listOf(remaining.size, state.visibleCardKeys.size, rows.visibleSlots,
|
||||||
settings.maxRows, settings.maxIconsPerRow, iconSize).hashCode()
|
settings.maxRows, settings.maxIconsPerRow, iconSize).hashCode()
|
||||||
if (diagnosticSignature != state.lastDiagnosticSignature) {
|
if (diagnosticSignature != state.lastDiagnosticSignature) {
|
||||||
@@ -294,7 +314,12 @@ internal class OneUiCardsGridRenderer {
|
|||||||
|
|
||||||
private fun failOpen(state: State) {
|
private fun failOpen(state: State) {
|
||||||
restoreNative(state)
|
restoreNative(state)
|
||||||
state.host?.visibility = View.GONE
|
state.host?.let {
|
||||||
|
it.visibility = View.GONE
|
||||||
|
it.translationX = 0f
|
||||||
|
it.translationY = 0f
|
||||||
|
}
|
||||||
|
state.anchorLocation = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideNative(state: State, native: View) {
|
private fun hideNative(state: State, native: View) {
|
||||||
@@ -328,6 +353,7 @@ internal class OneUiCardsGridRenderer {
|
|||||||
if (state.nativeSuppressed) state.nativeSurface?.let { native ->
|
if (state.nativeSuppressed) state.nativeSurface?.let { native ->
|
||||||
if (native.alpha != 0f) native.alpha = 0f
|
if (native.alpha != 0f) native.alpha = 0f
|
||||||
}
|
}
|
||||||
|
if (state.active && state.config.followSourceMovement) syncSourceMovement(container, state)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
observer.addOnPreDrawListener(listener)
|
observer.addOnPreDrawListener(listener)
|
||||||
@@ -367,6 +393,32 @@ internal class OneUiCardsGridRenderer {
|
|||||||
|
|
||||||
private fun dp(view: View, value: Int) = (value * view.resources.displayMetrics.density).toInt()
|
private fun dp(view: View, value: Int) = (value * view.resources.displayMetrics.density).toInt()
|
||||||
|
|
||||||
|
private fun syncSourceMovement(container: ViewGroup, state: State) {
|
||||||
|
val root = state.root ?: return
|
||||||
|
val host = state.host ?: return
|
||||||
|
val anchor = state.anchorLocation ?: return
|
||||||
|
container.getLocationOnScreen(state.sourceLocation)
|
||||||
|
root.getLocationOnScreen(state.rootLocation)
|
||||||
|
val x = (state.sourceLocation[0] - state.rootLocation[0] - anchor.first).toFloat()
|
||||||
|
val y = (state.sourceLocation[1] - state.rootLocation[1] - anchor.second).toFloat()
|
||||||
|
val alpha = effectiveAncestorAlpha(container, root)
|
||||||
|
if (host.translationX != x) host.translationX = x
|
||||||
|
if (host.translationY != y) host.translationY = y
|
||||||
|
if (host.alpha != alpha) host.alpha = alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun effectiveAncestorAlpha(source: View, root: View): Float {
|
||||||
|
if (source.visibility != View.VISIBLE) return 0f
|
||||||
|
var alpha = 1f
|
||||||
|
var current = source.parent as? View
|
||||||
|
while (current != null && current !== root) {
|
||||||
|
if (current.visibility != View.VISIBLE) return 0f
|
||||||
|
alpha *= current.alpha
|
||||||
|
current = current.parent as? View
|
||||||
|
}
|
||||||
|
return alpha
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -389,6 +441,7 @@ internal class OneUiCardsGridRenderer {
|
|||||||
val settings: CardsGridSettings,
|
val settings: CardsGridSettings,
|
||||||
val showPills: Boolean,
|
val showPills: Boolean,
|
||||||
val hideShelf: Boolean,
|
val hideShelf: Boolean,
|
||||||
|
val followSourceMovement: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
private class State {
|
private class State {
|
||||||
@@ -403,6 +456,10 @@ internal class OneUiCardsGridRenderer {
|
|||||||
var nativeSuppressed = false
|
var nativeSuppressed = false
|
||||||
var preDrawObserver: ViewTreeObserver? = null
|
var preDrawObserver: ViewTreeObserver? = null
|
||||||
var preDrawListener: ViewTreeObserver.OnPreDrawListener? = null
|
var preDrawListener: ViewTreeObserver.OnPreDrawListener? = null
|
||||||
|
var renderTask: Runnable? = null
|
||||||
|
var anchorLocation: Pair<Int, Int>? = null
|
||||||
|
val sourceLocation = IntArray(2)
|
||||||
|
val rootLocation = IntArray(2)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package se.ajpanton.notificationsmaster.module
|
package se.ajpanton.notificationsmaster.module
|
||||||
|
|
||||||
import android.graphics.drawable.Icon
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
@@ -9,6 +8,7 @@ import android.service.notification.StatusBarNotification
|
|||||||
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.widget.ImageView
|
||||||
import io.github.libxposed.api.XposedInterface
|
import io.github.libxposed.api.XposedInterface
|
||||||
import se.ajpanton.notificationsmaster.visibility.NotificationSurface
|
import se.ajpanton.notificationsmaster.visibility.NotificationSurface
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
@@ -21,14 +21,13 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
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 cardsTransitions = Collections.synchronizedMap(WeakHashMap<ViewGroup, CardsTransition>())
|
private val cardsTransitions = Collections.synchronizedMap(WeakHashMap<ViewGroup, CardsTransition>())
|
||||||
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())
|
||||||
private val refreshing = ThreadLocal<Boolean>()
|
private val refreshing = ThreadLocal<Boolean>()
|
||||||
private lateinit var updateActive: Method
|
private lateinit var updateActive: Method
|
||||||
private lateinit var updateVisible: Method
|
private lateinit var updateVisible: Method
|
||||||
@Volatile private var blockedObserved = false
|
@Volatile private var blockedObserved = false
|
||||||
@Volatile private var visibleNotifications = emptyList<StatusBarNotification>()
|
@Volatile private var notificationIconViews = emptyList<ImageView>()
|
||||||
private var cardsRefreshTask: Runnable? = null
|
private var cardsRefreshTask: Runnable? = null
|
||||||
|
|
||||||
override fun install(classLoader: ClassLoader): Boolean = runCatching {
|
override fun install(classLoader: ClassLoader): Boolean = runCatching {
|
||||||
@@ -40,6 +39,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
List::class.java,
|
List::class.java,
|
||||||
Int::class.javaPrimitiveType!!,
|
Int::class.javaPrimitiveType!!,
|
||||||
)
|
)
|
||||||
|
val updateIcons = manager.exactMethod("updateNotification", ArrayList::class.java)
|
||||||
installCardsGrid(classLoader)
|
installCardsGrid(classLoader)
|
||||||
framework.hook(updateActive).intercept { chain ->
|
framework.hook(updateActive).intercept { chain ->
|
||||||
val raw = notificationList(chain.args[0]) ?: return@intercept chain.proceed()
|
val raw = notificationList(chain.args[0]) ?: return@intercept chain.proceed()
|
||||||
@@ -54,17 +54,23 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
}
|
}
|
||||||
val filteredFirst = filter(first)
|
val filteredFirst = filter(first)
|
||||||
val filteredSecond = filter(second)
|
val filteredSecond = filter(second)
|
||||||
visibleNotifications = filteredSecond
|
|
||||||
val result = if (filteredFirst === first && filteredSecond === second) chain.proceed() else {
|
val result = if (filteredFirst === first && filteredSecond === second) chain.proceed() else {
|
||||||
chain.proceed(arrayOf(filteredFirst, filteredSecond, chain.args[2]))
|
chain.proceed(arrayOf(filteredFirst, filteredSecond, chain.args[2]))
|
||||||
}
|
}
|
||||||
refreshCardsGrid()
|
refreshCardsGrid()
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
framework.hook(updateIcons).intercept { chain ->
|
||||||
|
val icons = imageViewList(chain.args[0]) ?: return@intercept chain.proceed()
|
||||||
|
val result = chain.proceed()
|
||||||
|
notificationIconViews = icons
|
||||||
|
refreshCardsGrid()
|
||||||
|
result
|
||||||
|
}
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
Log.i(TAG, "Installed Samsung AOD notification backend")
|
Log.i(TAG, "Installed Samsung AOD notification backend")
|
||||||
}.onFailure {
|
}.onFailure { error ->
|
||||||
Log.d(TAG, "Samsung AOD notification boundary is unavailable")
|
Log.w(TAG, "Samsung AOD notification boundary is unavailable", error)
|
||||||
}.isSuccess
|
}.isSuccess
|
||||||
|
|
||||||
override fun onPolicyChanged() {
|
override fun onPolicyChanged() {
|
||||||
@@ -118,7 +124,6 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
cardsDozing[view] = dozing
|
cardsDozing[view] = dozing
|
||||||
if (dozing) beginDozeTransition(view, origin ?: screenLocation(view)) else {
|
if (dozing) beginDozeTransition(view, origin ?: screenLocation(view)) else {
|
||||||
cardsTransitions.remove(view)
|
cardsTransitions.remove(view)
|
||||||
cardsLocations.remove(view)
|
|
||||||
cardsRenderer.suspend(view, isCardsMode(view), view)
|
cardsRenderer.suspend(view, isCardsMode(view), view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,20 +152,17 @@ 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) beginLayoutSettle(container) else refreshCardsGrid()
|
if (cardsDozing[container] == true) beginAttachedTransition(container) else refreshCardsGrid()
|
||||||
}
|
}
|
||||||
override fun onViewDetachedFromWindow(view: View) {
|
override fun onViewDetachedFromWindow(view: View) {
|
||||||
cardsDozing[container] = false
|
cardsDozing[container] = false
|
||||||
cardsTransitions.remove(container)
|
cardsTransitions.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 (right - left != oldRight - oldLeft || bottom - top != oldBottom - oldTop) {
|
||||||
if (cardsDozing[container] == true) {
|
refreshCardsGrid()
|
||||||
if (cardsTransitions[container] == null) beginLayoutSettle(container) else refreshCardsGrid()
|
|
||||||
} else refreshCardsGrid()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshCardsGrid()
|
refreshCardsGrid()
|
||||||
@@ -180,12 +182,11 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
refreshCardsGrid(POSITION_POLL_DELAY)
|
refreshCardsGrid(POSITION_POLL_DELAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun beginLayoutSettle(container: ViewGroup) {
|
private fun beginAttachedTransition(container: ViewGroup) {
|
||||||
val now = SystemClock.uptimeMillis()
|
val now = SystemClock.uptimeMillis()
|
||||||
val current = screenLocation(container)
|
val current = screenLocation(container)
|
||||||
val origin = cardsLocations[container] ?: current
|
|
||||||
cardsTransitions[container] = CardsTransition(
|
cardsTransitions[container] = CardsTransition(
|
||||||
origin,
|
current,
|
||||||
current,
|
current,
|
||||||
moved = true,
|
moved = true,
|
||||||
stableSince = now,
|
stableSince = now,
|
||||||
@@ -221,7 +222,6 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
val location = screenLocation(container)
|
val location = screenLocation(container)
|
||||||
val transition = cardsTransitions[container]
|
val transition = cardsTransitions[container]
|
||||||
if (transition == null) {
|
if (transition == null) {
|
||||||
cardsLocations[container] = location
|
|
||||||
ready += container
|
ready += container
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,6 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
now >= transition.deadline
|
now >= transition.deadline
|
||||||
) {
|
) {
|
||||||
cardsTransitions.remove(container)
|
cardsTransitions.remove(container)
|
||||||
cardsLocations[container] = location
|
|
||||||
ready += container
|
ready += container
|
||||||
} else {
|
} else {
|
||||||
pollAgain = true
|
pollAgain = true
|
||||||
@@ -244,26 +243,18 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
}
|
}
|
||||||
if (pollAgain) refreshCardsGrid(POSITION_POLL_DELAY)
|
if (pollAgain) refreshCardsGrid(POSITION_POLL_DELAY)
|
||||||
if (ready.isEmpty()) return
|
if (ready.isEmpty()) return
|
||||||
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,
|
||||||
|
followSourceMovement = true,
|
||||||
)
|
)
|
||||||
ready.forEach { container ->
|
ready.forEach { container ->
|
||||||
val tint = runCatching {
|
val sources = notificationIconViews.map { icon ->
|
||||||
container.javaClass.getMethod("getIconColor").invoke(container) as Int
|
|
||||||
}.getOrNull()
|
|
||||||
val sources = notifications.map { sbn ->
|
|
||||||
val icon = sbn.notification.smallIcon
|
|
||||||
OneUiCardsGridRenderer.Source(
|
OneUiCardsGridRenderer.Source(
|
||||||
sbn.key,
|
"aod@${System.identityHashCode(icon)}",
|
||||||
view = null,
|
|
||||||
icon,
|
icon,
|
||||||
sbn.packageName,
|
smallIcon = null,
|
||||||
tint.takeUnless {
|
|
||||||
icon.type == Icon.TYPE_BITMAP || icon.type == Icon.TYPE_ADAPTIVE_BITMAP
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cardsRenderer.updateSources(container, sources)
|
cardsRenderer.updateSources(container, sources)
|
||||||
@@ -290,8 +281,8 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
chain: XposedInterface.Chain,
|
chain: XposedInterface.Chain,
|
||||||
argument: Int,
|
argument: Int,
|
||||||
raw: List<StatusBarNotification>,
|
raw: List<StatusBarNotification>,
|
||||||
|
filtered: List<StatusBarNotification> = filter(raw),
|
||||||
): Any? {
|
): Any? {
|
||||||
val filtered = filter(raw)
|
|
||||||
if (filtered === raw) return chain.proceed()
|
if (filtered === raw) return chain.proceed()
|
||||||
val args = chain.args.toTypedArray()
|
val args = chain.args.toTypedArray()
|
||||||
args[argument] = filtered
|
args[argument] = filtered
|
||||||
@@ -322,6 +313,13 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
return ArrayList(list as List<StatusBarNotification>)
|
return ArrayList(list as List<StatusBarNotification>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun imageViewList(value: Any?): List<ImageView>? {
|
||||||
|
val list = value as? ArrayList<*> ?: return null
|
||||||
|
if (list.any { it !is ImageView }) return null
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return ArrayList(list as List<ImageView>)
|
||||||
|
}
|
||||||
|
|
||||||
private fun state(manager: Any) = synchronized(managers) { managers.getOrPut(manager, ::State) }
|
private fun state(manager: Any) = synchronized(managers) { managers.getOrPut(manager, ::State) }
|
||||||
|
|
||||||
private fun Class<*>.exactMethod(name: String, vararg parameters: Class<*>) =
|
private fun Class<*>.exactMethod(name: String, vararg parameters: Class<*>) =
|
||||||
|
|||||||
Reference in New Issue
Block a user