diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiMiscBackend.kt b/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiMiscBackend.kt index 26084ee..7f9554f 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiMiscBackend.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiMiscBackend.kt @@ -40,7 +40,10 @@ internal class OneUiMiscBackend(private val framework: XposedInterface) : Visibi return installed > 0 } - override fun onPolicyChanged() = aodBattery.refresh() + override fun onPolicyChanged() { + lockscreenBattery.refresh() + aodBattery.refresh() + } private companion object { const val TAG = "NotificationsMaster" } } @@ -128,39 +131,81 @@ private class AodCallVisibilityController(private val framework: XposedInterface } private class LockscreenBatteryController(private val framework: XposedInterface) { + private var controller: Any? = null + private var batteryEvent: Any? = null + private var restingEvent: Any? = null + private lateinit var addTimeout: Method + private lateinit var addResting: Method + private lateinit var remove: Method private var failed = false - fun install(classLoader: ClassLoader): Boolean { - val eventClass = classLoader.findClass("com.android.systemui.statusbar.IndicationEventType") ?: return false - val batteryEvent = eventClass.staticField("BATTERY") ?: return false - val restingEvent = eventClass.staticField("BATTERY_RESTING") ?: return false - val controller = classLoader.findClass( - "com.android.systemui.statusbar.KeyguardSecIndicationController", - ) ?: return false - val methods = controller.methodsNamed("addInitialIndication") - methods.forEach { method -> - framework.intercept(method) { chain -> - val result = chain.proceed() - runCatching { - val target = chain.thisObject - val context = target.field("mContext") as? Context ?: return@runCatching - val state = visibleLockscreenBattery(context, target) ?: return@runCatching - val text = state.formattedPercent() - val color = target.field("mInitialTextColorState") as? ColorStateList - target.invokeNamed("addIndicationTimeout", batteryEvent, text, color, false) - target.invokeNamed("addIndication", restingEvent, text) - }.onFailure(::logFailureOnce) - result - } + fun install(classLoader: ClassLoader): Boolean = runCatching { + val eventClass = requireNotNull(classLoader.findClass("com.android.systemui.statusbar.IndicationEventType")) + batteryEvent = requireNotNull(eventClass.staticField("BATTERY")) + restingEvent = requireNotNull(eventClass.staticField("BATTERY_RESTING")) + val controllerClass = requireNotNull( + classLoader.findClass("com.android.systemui.statusbar.KeyguardSecIndicationController"), + ) + addTimeout = controllerClass.getDeclaredMethod( + "addIndicationTimeout", + eventClass, + CharSequence::class.java, + ColorStateList::class.java, + Boolean::class.javaPrimitiveType, + ).also { it.isAccessible = true } + addResting = controllerClass.getDeclaredMethod( + "addIndication", + eventClass, + CharSequence::class.java, + ).also { it.isAccessible = true } + remove = controllerClass.getDeclaredMethod("removeIndication", eventClass).also { it.isAccessible = true } + val method = controllerClass.getDeclaredMethod("addInitialIndication").also { it.isAccessible = true } + framework.intercept(method) { chain -> + val result = chain.proceed() + controller = chain.thisObject + update(chain.thisObject) + result } - return methods.isNotEmpty() + classLoader.findClass("com.android.systemui.statusbar.KeyguardIndicationController\$4") + ?.getDeclaredMethod("onDozingChanged", Boolean::class.javaPrimitiveType) + ?.also { it.isAccessible = true } + ?.let { dozingChanged -> + framework.intercept(dozingChanged) { chain -> + val result = chain.proceed() + val target = chain.thisObject.field("this\$0") + if (chain.args.firstOrNull() == false && controllerClass.isInstance(target)) { + controller = target + update(target) + } + result + } + } + }.onFailure { error -> + Log.w(TAG, "OneUI lockscreen battery boundary is unavailable", error) + }.isSuccess + + fun refresh() { + val target = controller ?: return + (target.field("mIndicationArea") as? View)?.post { update(target) } ?: update(target) } - private fun visibleLockscreenBattery(context: Context, controller: Any): BatteryState? { - if (!ProcessVisibilityPolicyCache.systemUiMisc.showLockscreenBatteryPercentWhenUnplugged) return null - if (controller.field("mDozing") == true) return null - return currentBatteryState(context)?.takeUnless { it.plugged || it.charged } - } + private fun update(target: Any?) = runCatching { + if (target.field("mDozing") == true) return@runCatching + val context = target.field("mContext") as? Context ?: return@runCatching + val state = currentBatteryState(context) ?: return@runCatching + if (state.plugged || state.charged) return@runCatching + val battery = batteryEvent ?: return@runCatching + val resting = restingEvent ?: return@runCatching + if (!ProcessVisibilityPolicyCache.systemUiMisc.showLockscreenBatteryPercentWhenUnplugged) { + remove.invoke(target, battery) + remove.invoke(target, resting) + return@runCatching + } + val text = state.formattedPercent() + val color = target.field("mInitialTextColorState") as? ColorStateList + addTimeout.invoke(target, battery, text, color, false) + addResting.invoke(target, resting, text) + }.onFailure(::logFailureOnce) private fun logFailureOnce(error: Throwable) { if (failed) return