Trim SystemUI rendering infrastructure

This commit is contained in:
ajp_anton
2026-09-03 00:55:12 +00:00
parent 1d2144e72c
commit eef433ce88
7 changed files with 77 additions and 143 deletions
@@ -29,11 +29,7 @@ internal class AospLockscreenBackend(
private lateinit var calculateShelfIcons: Method private lateinit var calculateShelfIcons: Method
private lateinit var shelfNotification: Field private lateinit var shelfNotification: Field
private lateinit var shelfIconStates: Field private lateinit var shelfIconStates: Field
private lateinit var shelfClampedAmount: Field private lateinit var iconState: IconStateAccess
private lateinit var shelfAppearAmount: Field
private lateinit var shelfAlpha: Field
private lateinit var shelfHidden: Field
private lateinit var shelfVisibleState: Field
@Volatile private var blockedObserved = false @Volatile private var blockedObserved = false
override fun install(classLoader: ClassLoader): Boolean { override fun install(classLoader: ClassLoader): Boolean {
@@ -108,13 +104,13 @@ internal class AospLockscreenBackend(
val entryMethod = rowClass.methods.firstOrNull { it.name == "getEntry" && it.parameterCount == 0 } val entryMethod = rowClass.methods.firstOrNull { it.name == "getEntry" && it.parameterCount == 0 }
val entryField = findField(rowClass, "mEntry", "entry") val entryField = findField(rowClass, "mEntry", "entry")
require(entryMethod != null || entryField != null) { "AOSP notification row entry is unavailable" } require(entryMethod != null || entryField != null) { "AOSP notification row entry is unavailable" }
entryForRow = accessor(entryMethod, entryField) entryForRow = memberAccessor(entryMethod, entryField)
val entryClass = entryMethod?.returnType ?: entryField!!.type val entryClass = entryMethod?.returnType ?: entryField!!.type
val sbnMethod = entryClass.methods.firstOrNull { it.name == "getSbn" && it.parameterCount == 0 } val sbnMethod = entryClass.methods.firstOrNull { it.name == "getSbn" && it.parameterCount == 0 }
val sbnField = findField(entryClass, "mSbn", "sbn", "mStatusBarNotification") val sbnField = findField(entryClass, "mSbn", "sbn", "mStatusBarNotification")
require(sbnMethod != null || sbnField != null) { "AOSP notification entry SBN is unavailable" } require(sbnMethod != null || sbnField != null) { "AOSP notification entry SBN is unavailable" }
val accessor = accessor(sbnMethod, sbnField) val accessor = memberAccessor(sbnMethod, sbnField)
sbnForEntry = { accessor(it) as? StatusBarNotification } sbnForEntry = { accessor(it) as? StatusBarNotification }
val attachedChildren = rowClass.methods.firstOrNull { val attachedChildren = rowClass.methods.firstOrNull {
it.name == "getAttachedChildren" && it.parameterCount == 0 it.name == "getAttachedChildren" && it.parameterCount == 0
@@ -139,17 +135,12 @@ internal class AospLockscreenBackend(
private fun installShelfFilter(classLoader: ClassLoader) { private fun installShelfFilter(classLoader: ClassLoader) {
val iconContainer = Class.forName(ICON_CONTAINER, false, classLoader) val iconContainer = Class.forName(ICON_CONTAINER, false, classLoader)
val iconView = Class.forName(STATUS_BAR_ICON_VIEW, false, classLoader) val iconView = Class.forName(STATUS_BAR_ICON_VIEW, false, classLoader)
val iconState = Class.forName(ICON_STATE, false, classLoader) iconState = IconStateAccess(Class.forName(ICON_STATE, false, classLoader))
calculateShelfIcons = iconContainer.declaredMethods.singleOrNull { calculateShelfIcons = iconContainer.declaredMethods.singleOrNull {
it.name == "calculateIconXTranslations" && it.parameterCount == 0 it.name == "calculateIconXTranslations" && it.parameterCount == 0
}?.also { it.isAccessible = true } ?: error("AOSP shelf icon layout method not found") }?.also { it.isAccessible = true } ?: error("AOSP shelf icon layout method not found")
shelfNotification = requireField(iconView, "mNotification") shelfNotification = requireField(iconView, "mNotification")
shelfIconStates = requireField(iconContainer, "mIconStates") shelfIconStates = requireField(iconContainer, "mIconStates")
shelfClampedAmount = requireField(iconState, "clampedAppearAmount")
shelfAppearAmount = requireField(iconState, "iconAppearAmount")
shelfAlpha = requireField(iconState, "mAlpha")
shelfHidden = requireField(iconState, "hidden")
shelfVisibleState = requireField(iconState, "visibleState")
framework.hook(calculateShelfIcons).intercept { chain -> framework.hook(calculateShelfIcons).intercept { chain ->
val container = chain.thisObject as? ViewGroup val container = chain.thisObject as? ViewGroup
@@ -161,14 +152,14 @@ internal class AospLockscreenBackend(
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) val blocked = children.filter(::isBlockedShelfIcon)
blocked.forEach { states[it]?.let(::hideShelfIconState) } blocked.forEach { states[it]?.let(iconState::hide) }
children.filterNot(blocked::contains).forEach(container::bringChildToFront) children.filterNot(blocked::contains).forEach(container::bringChildToFront)
val result = try { val result = try {
chain.proceed() chain.proceed()
} finally { } finally {
children.forEach(container::bringChildToFront) children.forEach(container::bringChildToFront)
} }
blocked.forEach { states[it]?.let(::hideShelfIconState) } blocked.forEach { states[it]?.let(iconState::hide) }
result result
} }
} }
@@ -245,14 +236,6 @@ internal class AospLockscreenBackend(
return runCatching { onKeyguard.invoke(stack) as Boolean }.getOrDefault(false) return runCatching { onKeyguard.invoke(stack) as Boolean }.getOrDefault(false)
} }
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) { private fun hideRow(row: View) {
if (row.visibility == View.GONE && row.alpha == 0f) return if (row.visibility == View.GONE && row.alpha == 0f) return
hiddenRows.putIfAbsent(row, RowState(row.visibility, row.alpha)) hiddenRows.putIfAbsent(row, RowState(row.visibility, row.alpha))
@@ -281,29 +264,6 @@ internal class AospLockscreenBackend(
setGroupCount(row, current.size) setGroupCount(row, current.size)
} }
private fun accessor(method: Method?, field: Field?): (Any) -> Any? {
method?.isAccessible = true
field?.isAccessible = true
return if (method != null) ({ target -> method.invoke(target) }) else ({ target -> field!!.get(target) })
}
private fun findField(type: Class<*>, vararg names: String): Field? {
var current: Class<*>? = type
while (current != null) {
names.forEach { name ->
runCatching { current.getDeclaredField(name) }.getOrNull()?.let {
it.isAccessible = true
return it
}
}
current = current.superclass
}
return null
}
private fun requireField(type: Class<*>, name: String) =
findField(type, name) ?: error("AOSP $name field is unavailable")
private data class RowState(val visibility: Int, val alpha: Float) private data class RowState(val visibility: Int, val alpha: Float)
private companion object { private companion object {
@@ -314,6 +274,5 @@ internal class AospLockscreenBackend(
const val ICON_CONTAINER = "com.android.systemui.statusbar.phone.NotificationIconContainer" const val ICON_CONTAINER = "com.android.systemui.statusbar.phone.NotificationIconContainer"
const val ICON_STATE = "$ICON_CONTAINER\$IconState" const val ICON_STATE = "$ICON_CONTAINER\$IconState"
const val STATUS_BAR_ICON_VIEW = "com.android.systemui.statusbar.StatusBarIconView" const val STATUS_BAR_ICON_VIEW = "com.android.systemui.statusbar.StatusBarIconView"
const val ICON_HIDDEN = 2
} }
} }
@@ -37,13 +37,11 @@ internal class OneUiCardsGridRenderer {
suspend(container, false) suspend(container, false)
return return
} }
state.active = active state.active = true
state.visibleCardKeys = visibleCardKeys state.visibleCardKeys = visibleCardKeys
state.config = config state.config = config
if (active) { 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)
}
scheduleRender(container, state) scheduleRender(container, state)
} }
@@ -8,7 +8,6 @@ import android.view.ViewGroup
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.Field import java.lang.reflect.Field
import java.lang.reflect.Method
import java.util.Collections import java.util.Collections
import java.util.WeakHashMap import java.util.WeakHashMap
import java.util.function.Function import java.util.function.Function
@@ -24,11 +23,7 @@ internal class OneUiLockscreenCardsBackend(
private lateinit var sbnForEntry: (Any) -> StatusBarNotification? private lateinit var sbnForEntry: (Any) -> StatusBarNotification?
private lateinit var shelfNotification: Field private lateinit var shelfNotification: Field
private lateinit var shelfIconStates: Field private lateinit var shelfIconStates: Field
private lateinit var shelfClampedAmount: Field private lateinit var iconState: IconStateAccess
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 statusBarStateKnown = false
@Volatile private var statusBarState = SHADE @Volatile private var statusBarState = SHADE
@Volatile private var dozing = false @Volatile private var dozing = false
@@ -117,13 +112,13 @@ internal class OneUiLockscreenCardsBackend(
val entryMethod = rowClass.methods.firstOrNull { it.name == "getEntry" && it.parameterCount == 0 } val entryMethod = rowClass.methods.firstOrNull { it.name == "getEntry" && it.parameterCount == 0 }
val entryField = findField(rowClass, "mEntry", "entry") val entryField = findField(rowClass, "mEntry", "entry")
require(entryMethod != null || entryField != null) { "OneUI notification row entry is unavailable" } require(entryMethod != null || entryField != null) { "OneUI notification row entry is unavailable" }
entryForRow = accessor(entryMethod, entryField) entryForRow = memberAccessor(entryMethod, entryField)
val entryClass = entryMethod?.returnType ?: entryField!!.type val entryClass = entryMethod?.returnType ?: entryField!!.type
val sbnMethod = entryClass.methods.firstOrNull { it.name == "getSbn" && it.parameterCount == 0 } val sbnMethod = entryClass.methods.firstOrNull { it.name == "getSbn" && it.parameterCount == 0 }
val sbnField = findField(entryClass, "mSbn", "sbn", "mStatusBarNotification") val sbnField = findField(entryClass, "mSbn", "sbn", "mStatusBarNotification")
require(sbnMethod != null || sbnField != null) { "OneUI notification entry SBN is unavailable" } require(sbnMethod != null || sbnField != null) { "OneUI notification entry SBN is unavailable" }
val accessor = accessor(sbnMethod, sbnField) val accessor = memberAccessor(sbnMethod, sbnField)
sbnForEntry = { accessor(it) as? StatusBarNotification } sbnForEntry = { accessor(it) as? StatusBarNotification }
} }
@@ -136,12 +131,7 @@ internal class OneUiLockscreenCardsBackend(
} }
shelfIconStates = findField(shelfIcons, "mIconStates") shelfIconStates = findField(shelfIcons, "mIconStates")
?: error("OneUI shelf icon states are unavailable") ?: error("OneUI shelf icon states are unavailable")
val iconState = Class.forName(ICON_STATE, false, classLoader) iconState = IconStateAccess(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 { val calculate = shelfIcons.declaredMethods.singleOrNull {
it.name == "calculateIconXTranslations" && it.parameterCount == 0 it.name == "calculateIconXTranslations" && it.parameterCount == 0
} ?: error("OneUI shelf icon layout method not found") } ?: error("OneUI shelf icon layout method not found")
@@ -156,7 +146,7 @@ internal class OneUiLockscreenCardsBackend(
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) val blocked = children.filter(::isBlockedShelfIcon)
blocked.forEach { icon -> states[icon]?.let(::hideShelfIconState) } blocked.forEach { icon -> states[icon]?.let(iconState::hide) }
children.filterNot(blocked::contains).forEach(container::bringChildToFront) children.filterNot(blocked::contains).forEach(container::bringChildToFront)
val result = try { val result = try {
chain.proceed() chain.proceed()
@@ -165,7 +155,7 @@ internal class OneUiLockscreenCardsBackend(
} }
// OneUI applies these states after this calculation returns. Suppress every // OneUI applies these states after this calculation returns. Suppress every
// stock shelf icon in Cards mode; the renderer owns the supplemental grid. // stock shelf icon in Cards mode; the renderer owns the supplemental grid.
children.forEach { icon -> states[icon]?.let(::hideShelfIconState) } children.forEach { icon -> states[icon]?.let(iconState::hide) }
if (!nativeShelfSuppressedObserved) { if (!nativeShelfSuppressedObserved) {
nativeShelfSuppressedObserved = true nativeShelfSuppressedObserved = true
Log.i(TAG, "Suppressed the native OneUI Cards shelf icon states") Log.i(TAG, "Suppressed the native OneUI Cards shelf icon states")
@@ -216,12 +206,6 @@ internal class OneUiLockscreenCardsBackend(
} }
} }
private fun accessor(method: Method?, field: Field?): (Any) -> Any? {
method?.isAccessible = true
field?.isAccessible = true
return if (method != null) ({ target -> method.invoke(target) }) else ({ target -> field!!.get(target) })
}
private fun updateState(controller: Any) { private fun updateState(controller: Any) {
(findField(controller.javaClass, "mState")?.get(controller) as? Int)?.let { (findField(controller.javaClass, "mState")?.get(controller) as? Int)?.let {
statusBarState = it statusBarState = it
@@ -297,14 +281,6 @@ 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) { private fun hideRow(row: View) {
if (row.visibility == View.GONE && row.alpha == 0f) return if (row.visibility == View.GONE && row.alpha == 0f) return
hiddenRows.putIfAbsent(row, RowState(row.visibility, row.alpha)) hiddenRows.putIfAbsent(row, RowState(row.visibility, row.alpha))
@@ -350,23 +326,6 @@ internal class OneUiLockscreenCardsBackend(
private fun booleanField(target: Any, name: String) = private fun booleanField(target: Any, name: String) =
runCatching { findField(target.javaClass, name)?.getBoolean(target) ?: false }.getOrDefault(false) runCatching { findField(target.javaClass, name)?.getBoolean(target) ?: false }.getOrDefault(false)
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) {
names.forEach { name ->
runCatching { current.getDeclaredField(name) }.getOrNull()?.let {
it.isAccessible = true
return it
}
}
current = current.superclass
}
return null
}
private fun gridConfig() = OneUiCardsGridRenderer.Config( private fun gridConfig() = OneUiCardsGridRenderer.Config(
ProcessVisibilityPolicyCache.lockscreenCards, ProcessVisibilityPolicyCache.lockscreenCards,
showPills = true, showPills = true,
@@ -380,7 +339,6 @@ internal class OneUiLockscreenCardsBackend(
const val SHADE = 0 const val SHADE = 0
const val KEYGUARD = 1 const val KEYGUARD = 1
const val CARDS = 0 const val CARDS = 0
const val ICON_HIDDEN = 2
const val LOCKSCREEN_STYLE = "lockscreen_minimizing_notification" const val LOCKSCREEN_STYLE = "lockscreen_minimizing_notification"
const val STACK = "com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout" const val STACK = "com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout"
const val ROW = "com.android.systemui.statusbar.notification.row.ExpandableNotificationRow" const val ROW = "com.android.systemui.statusbar.notification.row.ExpandableNotificationRow"
@@ -18,7 +18,6 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
import io.github.libxposed.api.XposedInterface import io.github.libxposed.api.XposedInterface
import java.lang.reflect.Field
import java.lang.reflect.Method import java.lang.reflect.Method
import java.text.NumberFormat import java.text.NumberFormat
import java.util.WeakHashMap import java.util.WeakHashMap
@@ -390,11 +389,7 @@ private data class BatteryState(val level: Int, val plugged: Boolean, val charge
private fun currentBatteryState(context: Context): BatteryState? { private fun currentBatteryState(context: Context): BatteryState? {
val app = context.applicationContext val app = context.applicationContext
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val intent = app.registerReceiver(null, filter, Context.RECEIVER_EXPORTED) ?: return null
app.registerReceiver(null, filter, Context.RECEIVER_EXPORTED)
} else {
app.registerReceiver(null, filter)
} ?: return null
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100) val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)
if (level < 0 || scale <= 0) return null if (level < 0 || scale <= 0) return null
@@ -405,31 +400,15 @@ private fun currentBatteryState(context: Context): BatteryState? {
return BatteryState(percent, plugged, status == BatteryManager.BATTERY_STATUS_FULL || percent >= 100) return BatteryState(percent, plugged, status == BatteryManager.BATTERY_STATUS_FULL || percent >= 100)
} }
private fun currentApplicationContext(): Context? = runCatching {
Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null) as? Context
}.getOrNull()
private fun ClassLoader.findClass(name: String) = runCatching { Class.forName(name, false, this) }.getOrNull() private fun ClassLoader.findClass(name: String) = runCatching { Class.forName(name, false, this) }.getOrNull()
private fun Class<*>.methodsNamed(name: String): List<Method> = declaredMethods.filter { it.name == name }.onEach { private fun Class<*>.methodsNamed(name: String): List<Method> = declaredMethods.filter { it.name == name }.onEach {
it.isAccessible = true it.isAccessible = true
} }
private fun Class<*>.staticField(name: String): Any? = findField(name)?.get(null) private fun Class<*>.staticField(name: String): Any? = findField(this, name)?.get(null)
private fun Any?.field(name: String): Any? = this?.javaClass?.findField(name)?.get(this) private fun Any?.field(name: String): Any? = this?.let { findField(it.javaClass, name)?.get(it) }
private fun Class<*>.findField(name: String): Field? {
var type: Class<*>? = this
while (type != null) {
runCatching { type.getDeclaredField(name) }.getOrNull()?.let {
it.isAccessible = true
return it
}
type = type.superclass
}
return null
}
private fun Any?.invokeNamed(name: String, vararg args: Any?): Any? { private fun Any?.invokeNamed(name: String, vararg args: Any?): Any? {
val target = this ?: return null val target = this ?: return null
@@ -73,12 +73,6 @@ internal object ProcessVisibilityPolicyCache {
} }
} }
private fun currentApplicationContext(): Context? = runCatching {
Class.forName("android.app.ActivityThread")
.getMethod("currentApplication")
.invoke(null) as? Context
}.getOrNull()
private fun retryOrGiveUp() { private fun retryOrGiveUp() {
if (++attempts < MAX_ATTEMPTS) { if (++attempts < MAX_ATTEMPTS) {
Handler(Looper.getMainLooper()).postDelayed(::tryInstall, RETRY_DELAY_MILLIS) Handler(Looper.getMainLooper()).postDelayed(::tryInstall, RETRY_DELAY_MILLIS)
@@ -170,26 +170,26 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
} }
private fun beginDozeTransition(container: ViewGroup, origin: Pair<Int, Int>) { private fun beginDozeTransition(container: ViewGroup, origin: Pair<Int, Int>) {
val now = SystemClock.uptimeMillis()
val current = screenLocation(container) val current = screenLocation(container)
cardsTransitions[container] = CardsTransition( beginTransition(container, origin, current, current != origin)
origin,
current,
current != origin,
now,
now + TRANSITION_FALLBACK_DELAY,
)
cardsRenderer.suspend(container, isCardsMode(container), container)
refreshCardsGrid(POSITION_POLL_DELAY)
} }
private fun beginAttachedTransition(container: ViewGroup) { private fun beginAttachedTransition(container: ViewGroup) {
val now = SystemClock.uptimeMillis()
val current = screenLocation(container) val current = screenLocation(container)
beginTransition(container, current, current, true)
}
private fun beginTransition(
container: ViewGroup,
origin: Pair<Int, Int>,
current: Pair<Int, Int>,
moved: Boolean,
) {
val now = SystemClock.uptimeMillis()
cardsTransitions[container] = CardsTransition( cardsTransitions[container] = CardsTransition(
origin,
current, current,
current, moved,
moved = true,
stableSince = now, stableSince = now,
deadline = now + TRANSITION_FALLBACK_DELAY, deadline = now + TRANSITION_FALLBACK_DELAY,
) )
@@ -0,0 +1,46 @@
package se.ajpanton.notificationsmaster.module
import android.content.Context
import java.lang.reflect.Field
import java.lang.reflect.Method
internal fun findField(type: Class<*>, vararg names: String): Field? {
var current: Class<*>? = type
while (current != null) {
names.firstNotNullOfOrNull { runCatching { current.getDeclaredField(it) }.getOrNull() }?.let {
it.isAccessible = true
return it
}
current = current.superclass
}
return null
}
internal fun memberAccessor(method: Method?, field: Field?): (Any) -> Any? {
method?.isAccessible = true
field?.isAccessible = true
return if (method != null) ({ method.invoke(it) }) else ({ field!!.get(it) })
}
internal fun requireField(type: Class<*>, name: String) =
findField(type, name) ?: error("${type.name}.$name is unavailable")
internal class IconStateAccess(type: Class<*>) {
private val clampedAmount = requireField(type, "clampedAppearAmount")
private val appearAmount = requireField(type, "iconAppearAmount")
private val alpha = requireField(type, "mAlpha")
private val hidden = requireField(type, "hidden")
private val visibleState = requireField(type, "visibleState")
fun hide(state: Any) {
clampedAmount.setFloat(state, 0f)
appearAmount.setFloat(state, 0f)
alpha.setFloat(state, 0f)
hidden.setBoolean(state, true)
visibleState.setInt(state, 2)
}
}
internal fun currentApplicationContext(): Context? = runCatching {
Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null) as? Context
}.getOrNull()