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.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.provider.Settings
|
||||||
import android.service.notification.StatusBarNotification
|
import android.service.notification.StatusBarNotification
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
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.Method
|
import java.lang.reflect.Method
|
||||||
@@ -13,10 +16,16 @@ import java.util.WeakHashMap
|
|||||||
/** Filters the notification lists handed to Samsung's AOD renderer. */
|
/** Filters the notification lists handed to Samsung's AOD renderer. */
|
||||||
internal class SamsungAodBackend(private val framework: XposedInterface) : VisibilitySurfaceBackend {
|
internal class SamsungAodBackend(private val framework: XposedInterface) : VisibilitySurfaceBackend {
|
||||||
private val managers = Collections.synchronizedMap(WeakHashMap<Any, State>())
|
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 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 cardsRefreshPending = false
|
||||||
|
|
||||||
override fun install(classLoader: ClassLoader): Boolean = runCatching {
|
override fun install(classLoader: ClassLoader): Boolean = runCatching {
|
||||||
val manager = Class.forName(MANAGER, false, classLoader)
|
val manager = Class.forName(MANAGER, false, classLoader)
|
||||||
@@ -27,6 +36,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
List::class.java,
|
List::class.java,
|
||||||
Int::class.javaPrimitiveType!!,
|
Int::class.javaPrimitiveType!!,
|
||||||
)
|
)
|
||||||
|
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()
|
||||||
if (refreshing.get() != true) state(chain.thisObject).active = raw
|
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 filteredFirst = filter(first)
|
||||||
val filteredSecond = filter(second)
|
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]))
|
chain.proceed(arrayOf(filteredFirst, filteredSecond, chain.args[2]))
|
||||||
}
|
}
|
||||||
|
refreshCardsGrid()
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
Log.i(TAG, "Installed Samsung AOD notification backend")
|
Log.i(TAG, "Installed Samsung AOD notification backend")
|
||||||
@@ -52,7 +65,7 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
|
|
||||||
override fun onPolicyChanged() {
|
override fun onPolicyChanged() {
|
||||||
blockedObserved = false
|
blockedObserved = false
|
||||||
Handler(Looper.getMainLooper()).post {
|
mainHandler.post {
|
||||||
val current = synchronized(managers) { managers.entries.map { it.key to it.value.copy() } }
|
val current = synchronized(managers) { managers.entries.map { it.key to it.value.copy() } }
|
||||||
refreshing.set(true)
|
refreshing.set(true)
|
||||||
try {
|
try {
|
||||||
@@ -67,9 +80,104 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
} finally {
|
} finally {
|
||||||
refreshing.remove()
|
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(
|
private fun proceedFiltered(
|
||||||
chain: XposedInterface.Chain,
|
chain: XposedInterface.Chain,
|
||||||
argument: Int,
|
argument: Int,
|
||||||
@@ -124,6 +232,10 @@ internal class SamsungAodBackend(private val framework: XposedInterface) : Visib
|
|||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val TAG = "NotificationsMaster"
|
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 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