Render OneUI AOD Cards notification grid
This commit is contained in:
@@ -2,8 +2,11 @@ package se.ajpanton.notificationsmaster.module
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import io.github.libxposed.api.XposedInterface
|
||||
import se.ajpanton.notificationsmaster.visibility.NotificationSurface
|
||||
import java.lang.reflect.Method
|
||||
@@ -13,10 +16,16 @@ import java.util.WeakHashMap
|
||||
/** Filters the notification lists handed to Samsung's AOD renderer. */
|
||||
internal class SamsungAodBackend(private val framework: XposedInterface) : VisibilitySurfaceBackend {
|
||||
private val managers = Collections.synchronizedMap(WeakHashMap<Any, State>())
|
||||
private val cardsContainers = Collections.newSetFromMap(WeakHashMap<ViewGroup, Boolean>())
|
||||
private val cardsDozing = Collections.synchronizedMap(WeakHashMap<ViewGroup, Boolean>())
|
||||
private val cardsRenderer = OneUiCardsGridRenderer()
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
private val refreshing = ThreadLocal<Boolean>()
|
||||
private lateinit var updateActive: Method
|
||||
private lateinit var updateVisible: Method
|
||||
@Volatile private var blockedObserved = false
|
||||
@Volatile private var visibleNotifications = emptyList<StatusBarNotification>()
|
||||
@Volatile private var cardsRefreshPending = false
|
||||
|
||||
override fun install(classLoader: ClassLoader): Boolean = runCatching {
|
||||
val manager = Class.forName(MANAGER, false, classLoader)
|
||||
@@ -27,6 +36,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
||||
List::class.java,
|
||||
Int::class.javaPrimitiveType!!,
|
||||
)
|
||||
installCardsGrid(classLoader)
|
||||
framework.hook(updateActive).intercept { chain ->
|
||||
val raw = notificationList(chain.args[0]) ?: return@intercept chain.proceed()
|
||||
if (refreshing.get() != true) state(chain.thisObject).active = raw
|
||||
@@ -40,9 +50,12 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
||||
}
|
||||
val filteredFirst = filter(first)
|
||||
val filteredSecond = filter(second)
|
||||
if (filteredFirst === first && filteredSecond === second) chain.proceed() else {
|
||||
visibleNotifications = filteredSecond
|
||||
val result = if (filteredFirst === first && filteredSecond === second) chain.proceed() else {
|
||||
chain.proceed(arrayOf(filteredFirst, filteredSecond, chain.args[2]))
|
||||
}
|
||||
refreshCardsGrid()
|
||||
result
|
||||
}
|
||||
}.onSuccess {
|
||||
Log.i(TAG, "Installed Samsung AOD notification backend")
|
||||
@@ -52,7 +65,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
||||
|
||||
override fun onPolicyChanged() {
|
||||
blockedObserved = false
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
mainHandler.post {
|
||||
val current = synchronized(managers) { managers.entries.map { it.key to it.value.copy() } }
|
||||
refreshing.set(true)
|
||||
try {
|
||||
@@ -67,9 +80,104 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
||||
} finally {
|
||||
refreshing.remove()
|
||||
}
|
||||
refreshCardsGrid()
|
||||
}
|
||||
}
|
||||
|
||||
private fun installCardsGrid(classLoader: ClassLoader) {
|
||||
runCatching {
|
||||
val container = Class.forName(AOD_ICON_CONTAINER, false, classLoader)
|
||||
container.declaredConstructors.forEach { constructor ->
|
||||
constructor.isAccessible = true
|
||||
framework.hook(constructor).intercept { chain ->
|
||||
val result = chain.proceed()
|
||||
(chain.thisObject as? ViewGroup)?.let(::trackCardsContainer)
|
||||
result
|
||||
}
|
||||
}
|
||||
container.declaredMethods.filter {
|
||||
it.name == "setDozing" && it.parameterCount == 1 &&
|
||||
it.parameterTypes[0] == Boolean::class.javaPrimitiveType
|
||||
}.forEach { method ->
|
||||
method.isAccessible = true
|
||||
framework.hook(method).intercept { chain ->
|
||||
val result = chain.proceed()
|
||||
(chain.thisObject as? ViewGroup)?.let { view ->
|
||||
cardsDozing[view] = chain.args.firstOrNull() == true
|
||||
refreshCardsGrid()
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
container.declaredMethods.filter {
|
||||
(it.name == "setIconColor" || it.name == "setIconSize") && it.parameterCount == 1
|
||||
}.forEach { method ->
|
||||
method.isAccessible = true
|
||||
framework.hook(method).intercept { chain ->
|
||||
val result = chain.proceed()
|
||||
refreshCardsGrid()
|
||||
result
|
||||
}
|
||||
}
|
||||
}.onFailure { error ->
|
||||
Log.d(TAG, "Samsung AOD Cards grid boundary is unavailable", error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun trackCardsContainer(container: ViewGroup) {
|
||||
synchronized(cardsContainers) {
|
||||
if (!cardsContainers.add(container)) return
|
||||
}
|
||||
container.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
||||
override fun onViewAttachedToWindow(view: View) = refreshCardsGrid()
|
||||
override fun onViewDetachedFromWindow(view: View) {
|
||||
cardsDozing[container] = false
|
||||
cardsRenderer.release(container)
|
||||
}
|
||||
})
|
||||
container.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> refreshCardsGrid() }
|
||||
refreshCardsGrid()
|
||||
}
|
||||
|
||||
private fun refreshCardsGrid() {
|
||||
if (cardsRefreshPending) return
|
||||
cardsRefreshPending = true
|
||||
mainHandler.post {
|
||||
cardsRefreshPending = false
|
||||
val notifications = visibleNotifications.distinctBy(StatusBarNotification::getKey)
|
||||
val config = OneUiCardsGridRenderer.Config(
|
||||
ProcessVisibilityPolicyCache.aodCards,
|
||||
showPills = false,
|
||||
hideShelf = false,
|
||||
)
|
||||
synchronized(cardsContainers) { cardsContainers.toList() }.forEach { container ->
|
||||
val tint = runCatching {
|
||||
container.javaClass.getMethod("getIconColor").invoke(container) as Int
|
||||
}.getOrNull()
|
||||
val sources = notifications.map { sbn ->
|
||||
OneUiCardsGridRenderer.Source(
|
||||
sbn.key,
|
||||
view = null,
|
||||
sbn.notification.smallIcon,
|
||||
sbn.packageName,
|
||||
tint,
|
||||
)
|
||||
}
|
||||
cardsRenderer.updateSources(container, sources)
|
||||
cardsRenderer.refresh(
|
||||
container,
|
||||
container.isAttachedToWindow && cardsDozing[container] == true && isCardsMode(container),
|
||||
emptySet(),
|
||||
config,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCardsMode(view: View) = runCatching {
|
||||
Settings.System.getInt(view.context.contentResolver, NOTIFICATION_STYLE) == CARDS
|
||||
}.getOrDefault(false)
|
||||
|
||||
private fun proceedFiltered(
|
||||
chain: XposedInterface.Chain,
|
||||
argument: Int,
|
||||
@@ -124,6 +232,10 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
||||
|
||||
private companion object {
|
||||
const val TAG = "NotificationsMaster"
|
||||
const val CARDS = 0
|
||||
const val NOTIFICATION_STYLE = "lockscreen_minimizing_notification"
|
||||
const val MANAGER = "com.samsung.android.uniform.plugins.notification.AODNotificationManager"
|
||||
const val AOD_ICON_CONTAINER =
|
||||
"com.samsung.android.uniform.widget.notification.NotificationIconsOnlyContainer"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user