From b678662121bf4dac5e3a19ce23b720105f22adf7 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Tue, 9 May 2023 00:03:40 +0200 Subject: [PATCH] Respect notification badge settings --- .../ui/launcher/search/apps/AppItem.kt | 14 +--- .../search/common/SearchableItemVM.kt | 3 +- .../de/mm20/launcher2/notifications/Module.kt | 2 +- .../launcher2/notifications/Notification.kt | 74 +++++++++++++++++++ .../notifications/NotificationRepository.kt | 51 ++++--------- .../notifications/NotificationService.kt | 51 +++++++++++-- .../providers/NotificationBadgeProvider.kt | 13 ++-- .../de/mm20/launcher2/music/MusicService.kt | 7 +- 8 files changed, 151 insertions(+), 64 deletions(-) create mode 100644 data/notifications/src/main/java/de/mm20/launcher2/notifications/Notification.kt diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt index a47a4bf2..fd8fa1c2 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt @@ -117,18 +117,12 @@ fun AppItem( val notifications by viewModel.notifications.collectAsState(emptyList()) for (not in notifications) { - val title = - not.notification.extras.getString(NotificationCompat.EXTRA_TITLE, null) - ?.takeIf { it.isNotBlank() } - ?: not.notification.extras.getString( - NotificationCompat.EXTRA_TEXT, - null - ) - ?.takeIf { it.isNotBlank() } + val title = not.title?.takeIf { it.isNotBlank() } + ?: not.text?.takeIf { it.isNotBlank() } ?: continue val icon = - remember { not.notification.smallIcon?.loadDrawable(context) }?.let { + remember { not.smallIcon?.loadDrawable(context) }?.let { rememberAsyncImagePainter( it ) @@ -143,7 +137,7 @@ fun AppItem( }, onClick = { try { - not.notification.contentIntent?.send() + not.contentIntent?.send() } catch (e: PendingIntent.CanceledException) {} } ) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt index 4bee1320..94bc0202 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/SearchableItemVM.kt @@ -16,6 +16,7 @@ import de.mm20.launcher2.appshortcuts.AppShortcutRepository import de.mm20.launcher2.badges.BadgeService import de.mm20.launcher2.files.FileRepository import de.mm20.launcher2.icons.IconService +import de.mm20.launcher2.notifications.Notification import de.mm20.launcher2.notifications.NotificationRepository import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.data.AppShortcut @@ -122,7 +123,7 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent { return false } - fun clearNotification(notification: StatusBarNotification) { + fun clearNotification(notification: Notification) { notificationRepository.cancelNotification(notification) } diff --git a/data/notifications/src/main/java/de/mm20/launcher2/notifications/Module.kt b/data/notifications/src/main/java/de/mm20/launcher2/notifications/Module.kt index 52d03f2b..dfed50f7 100644 --- a/data/notifications/src/main/java/de/mm20/launcher2/notifications/Module.kt +++ b/data/notifications/src/main/java/de/mm20/launcher2/notifications/Module.kt @@ -3,5 +3,5 @@ package de.mm20.launcher2.notifications import org.koin.dsl.module val notificationsModule = module { - single { NotificationRepositoryImpl() } + single { NotificationRepository() } } \ No newline at end of file diff --git a/data/notifications/src/main/java/de/mm20/launcher2/notifications/Notification.kt b/data/notifications/src/main/java/de/mm20/launcher2/notifications/Notification.kt new file mode 100644 index 00000000..29634590 --- /dev/null +++ b/data/notifications/src/main/java/de/mm20/launcher2/notifications/Notification.kt @@ -0,0 +1,74 @@ +package de.mm20.launcher2.notifications + +import android.app.PendingIntent +import android.graphics.drawable.Icon +import android.media.session.MediaSession +import android.os.Bundle +import android.service.notification.NotificationListenerService.Ranking +import android.service.notification.StatusBarNotification +import androidx.core.app.NotificationCompat + +data class Notification( + val id: Int, + val key: String, + val packageName: String, + val postTime: Long, + val canShowBadge: Boolean, + val number: Int, + val smallIcon: Icon?, + val extras: Bundle, + val flags: Int = 0, + val contentIntent: PendingIntent?, +) { + constructor( + sbn: StatusBarNotification, + ranking: Ranking + ) : this( + id = sbn.id, + key = sbn.key, + packageName = sbn.packageName, + postTime = sbn.postTime, + canShowBadge = ranking.canShowBadge(), + number = sbn.notification.number, + smallIcon = sbn.notification.smallIcon, + extras = sbn.notification.extras, + flags = sbn.notification.flags, + contentIntent = sbn.notification.contentIntent, + ) + + constructor( + notification: Notification, + ranking: Ranking, + ) : this( + id = notification.id, + key = notification.key, + packageName = notification.packageName, + postTime = notification.postTime, + canShowBadge = ranking.canShowBadge(), + number = notification.number, + smallIcon = notification.smallIcon, + extras = notification.extras, + contentIntent = notification.contentIntent + ) + + val mediaSessionToken: MediaSession.Token? + get() = extras.getParcelable(NotificationCompat.EXTRA_MEDIA_SESSION) as? MediaSession.Token + + val progress: Int? + get() = if (extras.containsKey(android.app.Notification.EXTRA_PROGRESS)) + extras.getInt(NotificationCompat.EXTRA_PROGRESS) + else null + + + val progressMax: Int? + get() = extras.getInt(NotificationCompat.EXTRA_PROGRESS_MAX).takeIf { it > 0 } + + val title: String? + get() = extras.getString(NotificationCompat.EXTRA_TITLE) + + val text: String? + get() = extras.getString(NotificationCompat.EXTRA_TEXT) + + val isGroupSummary: Boolean + get() = flags and NotificationCompat.FLAG_GROUP_SUMMARY != 0 +} \ No newline at end of file diff --git a/data/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationRepository.kt b/data/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationRepository.kt index acb0fca5..e4806e7c 100644 --- a/data/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationRepository.kt +++ b/data/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationRepository.kt @@ -1,62 +1,43 @@ package de.mm20.launcher2.notifications -import android.service.notification.StatusBarNotification import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -interface NotificationRepository { - val notifications: Flow> - /** - * Internal use only. Used by NotificationService. - */ - fun setNotifications(notifications: List) - - /** - * Internal use only. Used by NotificationService. - */ - fun postNotification(notification: StatusBarNotification) - - /** - * Internal use only. Used by NotificationService. - */ - fun removeNotification(notification: StatusBarNotification) - - /** - * Cancel a notification - */ - fun cancelNotification(notification: StatusBarNotification) -} - -internal class NotificationRepositoryImpl : NotificationRepository { +class NotificationRepository { private val scope = CoroutineScope(Job() + Dispatchers.Default) - override val notifications: MutableStateFlow> = MutableStateFlow( + + private val _notifications: MutableStateFlow> = MutableStateFlow( emptyList() ) - override fun setNotifications(notifications: List) { - this.notifications.value = notifications + val notifications: Flow> = _notifications + + internal fun setNotifications(notifications: List) { + _notifications.value = notifications } - override fun postNotification(notification: StatusBarNotification) { - notifications.value = notifications.value.filter { !isEqual(it, notification) } + notification + internal fun getNotifications(): List = _notifications.value + + internal fun onNotificationPosted(notification: Notification) { + _notifications.value = _notifications.value.filter { !isEqual(it, notification) } + notification } - override fun removeNotification(notification: StatusBarNotification) { - notifications.value = notifications.value.filter { !isEqual(it, notification) } + internal fun onNotificationRemoved(key: String) { + _notifications.value = _notifications.value.filter { it.key != key } } private fun isEqual( - notification1: StatusBarNotification, - notification2: StatusBarNotification + notification1: Notification, + notification2: Notification ): Boolean { return notification1.key == notification2.key } - override fun cancelNotification(notification: StatusBarNotification) { + fun cancelNotification(notification: Notification) { NotificationService.getInstance()?.cancelNotification(notification.key) } diff --git a/data/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationService.kt b/data/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationService.kt index b25ad599..4248ec2f 100644 --- a/data/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationService.kt +++ b/data/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationService.kt @@ -6,11 +6,17 @@ import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification import android.util.Log import de.mm20.launcher2.permissions.PermissionsManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import java.lang.ref.WeakReference class NotificationService : NotificationListenerService() { + private val scope = CoroutineScope(Job() + Dispatchers.Default) + private val notificationRepository: NotificationRepository by inject() private val permissionsManager: PermissionsManager by inject() @@ -24,8 +30,34 @@ class NotificationService : NotificationListenerService() { Log.d("MM20", "Notification listener connected") permissionsManager.reportNotificationListenerState(true) instance = WeakReference(this) - val notifications = getNotifications().sortedBy { it.postTime } - notificationRepository.setNotifications(notifications) + + scope.launch { + val statusBarNotifications = getNotifications().sortedBy { it.postTime } + val ranking = Ranking() + val rankingMap = currentRanking + + val notifications = statusBarNotifications.map { + rankingMap.getRanking(it.key, ranking) + Notification(it, ranking) + } + + notificationRepository.setNotifications(notifications) + } + } + + override fun onNotificationRankingUpdate(rankingMap: RankingMap?) { + super.onNotificationRankingUpdate(rankingMap) + scope.launch { + val notifications = notificationRepository.getNotifications() + + val ranking = Ranking() + val updatedNotifications = notifications.map { + rankingMap?.getRanking(it.key, ranking) + Notification(it, ranking) + } + + notificationRepository.setNotifications(updatedNotifications) + } } private fun getNotifications(): Array { @@ -36,14 +68,19 @@ class NotificationService : NotificationListenerService() { } } - override fun onNotificationPosted(sbn: StatusBarNotification) { - notificationRepository.postNotification(sbn) - } - override fun onNotificationRemoved(sbn: StatusBarNotification) { super.onNotificationRemoved(sbn) + notificationRepository.onNotificationRemoved(sbn.key) + } - notificationRepository.removeNotification(sbn) + override fun onNotificationPosted(sbn: StatusBarNotification, rankingMap: RankingMap) { + super.onNotificationPosted(sbn, rankingMap) + + val ranking = Ranking() + rankingMap.getRanking(sbn.key, ranking) + val notification = Notification(sbn, ranking) + + notificationRepository.onNotificationPosted(notification) } override fun onListenerDisconnected() { diff --git a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt index e993c2e1..3d41622f 100644 --- a/services/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt +++ b/services/badges/src/main/java/de/mm20/launcher2/badges/providers/NotificationBadgeProvider.kt @@ -21,18 +21,17 @@ class NotificationBadgeProvider : BadgeProvider, KoinComponent { notificationRepository.notifications.map { it.filter { it.packageName == packageName } }.collectLatest { - if (it.isEmpty()) { + if (it.isEmpty() || it.none { it.canShowBadge }) { send(null) } else { val badge = Badge( - number = it.distinctBy { it.notification.shortcutId }.sumOf { - if(it.notification.shortcutId == null) 0 - else it.notification.number + number = it.sumOf { + if (it.canShowBadge && !it.isGroupSummary) it.number + else 0 }, progress = it.mapNotNull { - if (!it.notification.extras.containsKey(Notification.EXTRA_PROGRESS)) return@mapNotNull null - val progress = it.notification.extras.getInt(Notification.EXTRA_PROGRESS) - val progressMax = it.notification.extras.getInt(Notification.EXTRA_PROGRESS_MAX).takeIf { it > 0 } ?: return@mapNotNull null + val progress = it.progress ?: return@mapNotNull null + val progressMax = it.progressMax ?: return@mapNotNull null return@mapNotNull progress.toFloat() / progressMax.toFloat() } .takeIf { it.isNotEmpty() } diff --git a/services/music/src/main/java/de/mm20/launcher2/music/MusicService.kt b/services/music/src/main/java/de/mm20/launcher2/music/MusicService.kt index 65c8cf1c..721d82ef 100644 --- a/services/music/src/main/java/de/mm20/launcher2/music/MusicService.kt +++ b/services/music/src/main/java/de/mm20/launcher2/music/MusicService.kt @@ -25,6 +25,7 @@ import coil.imageLoader import coil.request.ImageRequest import coil.size.Scale import de.mm20.launcher2.crashreporter.CrashReporter +import de.mm20.launcher2.notifications.Notification import de.mm20.launcher2.notifications.NotificationRepository import de.mm20.launcher2.preferences.LauncherDataStore import kotlinx.coroutines.CoroutineScope @@ -114,11 +115,11 @@ internal class MusicServiceImpl( settings.allowListList.toSet(), settings.denyListList.toSet() ) - val sbn: StatusBarNotification? = notifications.filter { - it.notification.extras.getParcelable(NotificationCompat.EXTRA_MEDIA_SESSION) as? MediaSession.Token != null && musicApps.contains(it.packageName) + val sbn: Notification? = notifications.filter { + it.mediaSessionToken != null && musicApps.contains(it.packageName) }.maxByOrNull { it.postTime } - return@withContext (sbn?.notification?.extras?.get(NotificationCompat.EXTRA_MEDIA_SESSION) as? MediaSession.Token) + return@withContext sbn?.mediaSessionToken } } .distinctUntilChanged()