Separate OneUI lockscreen and AOD filtering
This commit is contained in:
+51
-33
@@ -2,7 +2,6 @@ package se.ajpanton.notificationsmaster.module
|
|||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.Message
|
|
||||||
import android.service.notification.StatusBarNotification
|
import android.service.notification.StatusBarNotification
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import io.github.libxposed.api.XposedInterface
|
import io.github.libxposed.api.XposedInterface
|
||||||
@@ -15,12 +14,13 @@ import java.util.WeakHashMap
|
|||||||
/** Filters OneUI's collapsed lockscreen projection without changing notification rows. */
|
/** Filters OneUI's collapsed lockscreen projection without changing notification rows. */
|
||||||
internal class OneUiLockscreenBackend(private val framework: XposedInterface) : VisibilitySurfaceBackend {
|
internal class OneUiLockscreenBackend(private val framework: XposedInterface) : VisibilitySurfaceBackend {
|
||||||
private val coordinators = Collections.newSetFromMap(WeakHashMap<Any, Boolean>())
|
private val coordinators = Collections.newSetFromMap(WeakHashMap<Any, Boolean>())
|
||||||
|
private val replayingLockscreen = ThreadLocal<Boolean>()
|
||||||
private lateinit var refresh: Method
|
private lateinit var refresh: Method
|
||||||
@Volatile private var blockedObserved = false
|
@Volatile private var blockedObserved = false
|
||||||
|
|
||||||
override fun install(classLoader: ClassLoader): Boolean = runCatching {
|
override fun install(classLoader: ClassLoader): Boolean = runCatching {
|
||||||
val coordinator = Class.forName(COORDINATOR, false, classLoader)
|
val coordinator = Class.forName(COORDINATOR, false, classLoader)
|
||||||
val managerHandler = Class.forName(MANAGER_HANDLER, false, classLoader)
|
val iconsController = Class.forName(ICONS_CONTROLLER, false, classLoader)
|
||||||
val info = Class.forName(NOTIFICATION_INFO, false, classLoader)
|
val info = Class.forName(NOTIFICATION_INFO, false, classLoader)
|
||||||
val sbn = info.getDeclaredField("mSbn").also {
|
val sbn = info.getDeclaredField("mSbn").also {
|
||||||
require(it.type == StatusBarNotification::class.java)
|
require(it.type == StatusBarNotification::class.java)
|
||||||
@@ -33,45 +33,40 @@ internal class OneUiLockscreenBackend(private val framework: XposedInterface) :
|
|||||||
it.name == "onLockScreenNotiStateChanged" &&
|
it.name == "onLockScreenNotiStateChanged" &&
|
||||||
it.parameterTypes.isEmpty() && it.returnType == Void.TYPE
|
it.parameterTypes.isEmpty() && it.returnType == Void.TYPE
|
||||||
} ?: error("OneUI lockscreen refresh method not found")
|
} ?: error("OneUI lockscreen refresh method not found")
|
||||||
val handle = managerHandler.declaredMethods.singleOrNull {
|
val updateIcons = iconsController.declaredMethods.singleOrNull {
|
||||||
it.name == "handleMessage" &&
|
it.name == "onNotificationInfoUpdated" &&
|
||||||
it.parameterTypes.contentEquals(arrayOf(Message::class.java)) && it.returnType == Void.TYPE
|
it.parameterTypes.contentEquals(arrayOf(ArrayList::class.java)) && it.returnType == Void.TYPE
|
||||||
} ?: error("OneUI lockscreen update handler not found")
|
} ?: error("OneUI lockscreen icon update method not found")
|
||||||
attach.isAccessible = true
|
attach.isAccessible = true
|
||||||
refresh.isAccessible = true
|
refresh.isAccessible = true
|
||||||
handle.isAccessible = true
|
updateIcons.isAccessible = true
|
||||||
framework.hook(attach).intercept { chain ->
|
framework.hook(attach).intercept { chain ->
|
||||||
synchronized(coordinators) { coordinators += chain.thisObject }
|
synchronized(coordinators) { coordinators += chain.thisObject }
|
||||||
chain.proceed()
|
chain.proceed()
|
||||||
}
|
}
|
||||||
framework.hook(handle).intercept { chain ->
|
framework.hook(updateIcons).intercept { chain ->
|
||||||
val message = chain.args[0] as? Message ?: return@intercept chain.proceed()
|
if (replayingLockscreen.get() == true) return@intercept chain.proceed()
|
||||||
if (message.what != NOTIFICATION_INFO_UPDATED) return@intercept chain.proceed()
|
val original = chain.args[0] as? ArrayList<*> ?: return@intercept chain.proceed()
|
||||||
val original = message.obj as? ArrayList<*> ?: return@intercept chain.proceed()
|
|
||||||
if (original.any { it != null && !info.isInstance(it) }) return@intercept chain.proceed()
|
if (original.any { it != null && !info.isInstance(it) }) return@intercept chain.proceed()
|
||||||
val filtered = original.filterTo(ArrayList(original.size)) { item ->
|
val aod = filter(original, sbn, NotificationSurface.AOD)
|
||||||
val notification = item?.let {
|
val lockscreen = filter(original, sbn, NotificationSurface.LOCKSCREEN_COLLAPSED)
|
||||||
runCatching {
|
// OneUI builds AOD icons and lockscreen state in this same callback. Let
|
||||||
VisibilityNotificationExtractor.from(sbn.get(it) as StatusBarNotification)
|
// AOD consume its list, then replay only the lockscreen half.
|
||||||
}.getOrNull()
|
val result = chain.proceed(arrayOf(aod))
|
||||||
}
|
if (lockscreen != aod) {
|
||||||
notification == null || !ProcessVisibilityPolicyCache.isBlocked(
|
replayingLockscreen.set(true)
|
||||||
notification,
|
OneUiSurfaceDispatch.suppressAodIconUpdate.set(true)
|
||||||
NotificationSurface.LOCKSCREEN_COLLAPSED,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (filtered.size == original.size) return@intercept chain.proceed()
|
|
||||||
if (!blockedObserved) {
|
|
||||||
blockedObserved = true
|
|
||||||
Log.i(TAG, "Filtered a OneUI collapsed-lockscreen notification")
|
|
||||||
}
|
|
||||||
message.obj = filtered
|
|
||||||
try {
|
try {
|
||||||
chain.proceed()
|
updateIcons.invoke(chain.thisObject, lockscreen)
|
||||||
|
} catch (error: ReflectiveOperationException) {
|
||||||
|
throw error.cause ?: error
|
||||||
} finally {
|
} finally {
|
||||||
message.obj = original
|
OneUiSurfaceDispatch.suppressAodIconUpdate.remove()
|
||||||
|
replayingLockscreen.remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
Log.i(TAG, "Installed OneUI collapsed-lockscreen notification backend")
|
Log.i(TAG, "Installed OneUI collapsed-lockscreen notification backend")
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
@@ -89,13 +84,36 @@ internal class OneUiLockscreenBackend(private val framework: XposedInterface) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun filter(
|
||||||
|
original: ArrayList<*>,
|
||||||
|
sbn: java.lang.reflect.Field,
|
||||||
|
surface: NotificationSurface,
|
||||||
|
): ArrayList<*> {
|
||||||
|
val filtered = original.filterTo(ArrayList(original.size)) { item ->
|
||||||
|
val notification = item?.let {
|
||||||
|
runCatching {
|
||||||
|
VisibilityNotificationExtractor.from(sbn.get(it) as StatusBarNotification)
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
notification == null || !ProcessVisibilityPolicyCache.isBlocked(notification, surface)
|
||||||
|
}
|
||||||
|
if (filtered.size < original.size && !blockedObserved) {
|
||||||
|
blockedObserved = true
|
||||||
|
Log.i(TAG, "Filtered a OneUI ${surface.name.lowercase()} notification")
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val TAG = "NotificationsMaster"
|
const val TAG = "NotificationsMaster"
|
||||||
const val NOTIFICATION_INFO_UPDATED = 101
|
|
||||||
const val COORDINATOR =
|
const val COORDINATOR =
|
||||||
"com.android.systemui.statusbar.notification.collection.coordinator.LockScreenNotiIconCoordinator"
|
"com.android.systemui.statusbar.notification.collection.coordinator.LockScreenNotiIconCoordinator"
|
||||||
const val MANAGER_HANDLER =
|
const val ICONS_CONTROLLER =
|
||||||
"com.android.systemui.statusbar.LockscreenNotificationManager\$LockscreenNotificationMgrHandler"
|
"com.android.systemui.statusbar.iconsOnly.LockscreenNotificationIconsOnlyController"
|
||||||
const val NOTIFICATION_INFO = "com.android.systemui.statusbar.LockscreenNotificationInfo"
|
const val NOTIFICATION_INFO = "com.android.systemui.statusbar.LockscreenNotificationInfo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal object OneUiSurfaceDispatch {
|
||||||
|
val suppressAodIconUpdate = ThreadLocal<Boolean>()
|
||||||
|
}
|
||||||
|
|||||||
+11
-6
@@ -148,7 +148,7 @@ 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 (isDozeTransition() && isCardsMode(container)) {
|
if (isDozeActive() && isCardsMode(container)) {
|
||||||
gridRenderer.suspend(container, true)
|
gridRenderer.suspend(container, true)
|
||||||
}
|
}
|
||||||
if (!shouldFilterLockscreenCards(container)) return@intercept chain.proceed()
|
if (!shouldFilterLockscreenCards(container)) return@intercept chain.proceed()
|
||||||
@@ -209,7 +209,7 @@ internal class OneUiLockscreenCardsBackend(
|
|||||||
if (active) {
|
if (active) {
|
||||||
gridRenderer.refresh(container, true, visibleCardKeys(container), gridConfig())
|
gridRenderer.refresh(container, true, visibleCardKeys(container), gridConfig())
|
||||||
} else {
|
} else {
|
||||||
gridRenderer.suspend(container, isDozeTransition() && isCardsMode(container))
|
gridRenderer.suspend(container, isDozeActive() && isCardsMode(container))
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@@ -228,6 +228,9 @@ 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 }
|
||||||
|
(findField(controller.javaClass, "mDozeAmount")?.get(controller) as? Number)?.let {
|
||||||
|
dozeAmount = it.toFloat().coerceIn(0f, 1f)
|
||||||
|
}
|
||||||
applyTrackedStacks()
|
applyTrackedStacks()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +240,7 @@ internal class OneUiLockscreenCardsBackend(
|
|||||||
::isLockscreenCards,
|
::isLockscreenCards,
|
||||||
::visibleCardKeys,
|
::visibleCardKeys,
|
||||||
gridConfig(),
|
gridConfig(),
|
||||||
) { isDozeTransition() && isCardsMode(it) }
|
) { isDozeActive() && isCardsMode(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyStack(stack: ViewGroup) {
|
private fun applyStack(stack: ViewGroup) {
|
||||||
@@ -257,12 +260,14 @@ internal class OneUiLockscreenCardsBackend(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isLockscreenCards(view: View) =
|
private fun isLockscreenCards(view: View) =
|
||||||
shouldFilterLockscreenCards(view) && !isDozeTransition()
|
shouldFilterLockscreenCards(view) && !isDozeActive()
|
||||||
|
|
||||||
private fun shouldFilterLockscreenCards(view: View) =
|
private fun shouldFilterLockscreenCards(view: View) =
|
||||||
statusBarStateKnown && statusBarState == KEYGUARD && isCardsMode(view)
|
statusBarStateKnown && statusBarState == KEYGUARD && !isSteadyAod() && isCardsMode(view)
|
||||||
|
|
||||||
private fun isDozeTransition() = dozing || dozeAmount > 0f
|
private fun isDozeActive() = dozing || dozeAmount > 0f
|
||||||
|
|
||||||
|
private fun isSteadyAod() = dozing && dozeAmount >= 1f
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
framework.hook(updateIcons).intercept { chain ->
|
framework.hook(updateIcons).intercept { chain ->
|
||||||
|
if (OneUiSurfaceDispatch.suppressAodIconUpdate.get() == true) return@intercept null
|
||||||
val icons = imageViewList(chain.args[0]) ?: return@intercept chain.proceed()
|
val icons = imageViewList(chain.args[0]) ?: return@intercept chain.proceed()
|
||||||
val result = chain.proceed()
|
val result = chain.proceed()
|
||||||
notificationIconViews = icons
|
notificationIconViews = icons
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
android:id="@+id/show_lockscreen"
|
android:id="@+id/show_lockscreen"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Collapsed lockscreen" />
|
android:text="Lockscreen" />
|
||||||
|
|
||||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
android:id="@+id/show_unlocked"
|
android:id="@+id/show_unlocked"
|
||||||
|
|||||||
Reference in New Issue
Block a user