feat: notification dots follow AOSP badge count semantics
NotificationBadgeService now sums Notification.number (or 1 when unset) per package across counted notifications, matching Launcher3 badge behavior. A single K-9 notification with number=14 (unread count) now yields a dot, not just one notification = one dot.
This commit is contained in:
@@ -3,6 +3,7 @@ package com.github.droidworksstudio.launcher.service
|
||||
import android.app.Notification
|
||||
import android.service.notification.NotificationListenerService
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@@ -16,36 +17,43 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
*/
|
||||
class NotificationBadgeService : NotificationListenerService() {
|
||||
|
||||
/** Notification key -> "userId/packageName". Keyed to dedupe updates. */
|
||||
private val keyToPackage = HashMap<String, String>()
|
||||
/** Notification key -> which app it belongs to and its badge number. */
|
||||
private val keyToEntry = HashMap<String, BadgeEntry>()
|
||||
|
||||
private data class BadgeEntry(val packageKey: String, val number: Int)
|
||||
|
||||
override fun onListenerConnected() {
|
||||
super.onListenerConnected()
|
||||
synchronized(keyToPackage) {
|
||||
keyToPackage.clear()
|
||||
synchronized(keyToEntry) {
|
||||
keyToEntry.clear()
|
||||
activeNotifications.forEach { sbn ->
|
||||
if (isCounted(sbn)) keyToPackage[sbn.key] = userIdKey(sbn)
|
||||
if (isCounted(sbn)) keyToEntry[sbn.key] = entryFor(sbn)
|
||||
}
|
||||
publish()
|
||||
}
|
||||
Log.d("BadgeService", "connected, active=" + activeNotifications.size +
|
||||
" counts=" + notificationCounts.value)
|
||||
}
|
||||
|
||||
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
||||
if (!isCounted(sbn)) return
|
||||
synchronized(keyToPackage) {
|
||||
synchronized(keyToEntry) {
|
||||
// A posted notification with an existing key is just an update.
|
||||
if (keyToPackage.containsKey(sbn.key)) return
|
||||
keyToPackage[sbn.key] = userIdKey(sbn)
|
||||
if (keyToEntry.containsKey(sbn.key)) return
|
||||
keyToEntry[sbn.key] = entryFor(sbn)
|
||||
publish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNotificationRemoved(sbn: StatusBarNotification) {
|
||||
synchronized(keyToPackage) {
|
||||
if (keyToPackage.remove(sbn.key) != null) publish()
|
||||
synchronized(keyToEntry) {
|
||||
if (keyToEntry.remove(sbn.key) != null) publish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun entryFor(sbn: StatusBarNotification) =
|
||||
BadgeEntry(userIdKey(sbn), sbn.notification.number)
|
||||
|
||||
/** Only swipe-away, non-ongoing notifications get a dot. */
|
||||
private fun isCounted(sbn: StatusBarNotification): Boolean {
|
||||
val notification = sbn.notification
|
||||
@@ -58,9 +66,13 @@ class NotificationBadgeService : NotificationListenerService() {
|
||||
"${sbn.userId}/" + sbn.packageName
|
||||
|
||||
private fun publish() {
|
||||
// AOSP launcher badge semantics: for each counted notification add
|
||||
// its badge number (Notification.number), or 1 when unset, so e.g.
|
||||
// K-9's unread-count badge shows a dot.
|
||||
val counts = HashMap<String, Int>()
|
||||
keyToPackage.values.forEach { key ->
|
||||
counts[key] = (counts[key] ?: 0) + 1
|
||||
keyToEntry.values.forEach { entry ->
|
||||
val increment = entry.number.coerceAtLeast(1)
|
||||
counts[entry.packageKey] = (counts[entry.packageKey] ?: 0) + increment
|
||||
}
|
||||
notificationCounts.value = counts
|
||||
}
|
||||
|
||||
BIN
dist/EasyLauncher-Internet-v0.3.5-Signed.apk
vendored
BIN
dist/EasyLauncher-Internet-v0.3.5-Signed.apk
vendored
Binary file not shown.
BIN
dist/EasyLauncher-Internet-v0.3.5-Signed.apk.idsig
vendored
BIN
dist/EasyLauncher-Internet-v0.3.5-Signed.apk.idsig
vendored
Binary file not shown.
Reference in New Issue
Block a user