Respect notification badge settings

This commit is contained in:
MM20 2023-05-09 00:03:40 +02:00
parent b5a4d459d3
commit b678662121
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
8 changed files with 151 additions and 64 deletions

View File

@ -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) {}
}
)

View File

@ -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)
}

View File

@ -3,5 +3,5 @@ package de.mm20.launcher2.notifications
import org.koin.dsl.module
val notificationsModule = module {
single<NotificationRepository> { NotificationRepositoryImpl() }
single<NotificationRepository> { NotificationRepository() }
}

View File

@ -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
}

View File

@ -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<List<StatusBarNotification>>
/**
* Internal use only. Used by NotificationService.
*/
fun setNotifications(notifications: List<StatusBarNotification>)
/**
* 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<List<StatusBarNotification>> = MutableStateFlow(
private val _notifications: MutableStateFlow<List<Notification>> = MutableStateFlow(
emptyList()
)
override fun setNotifications(notifications: List<StatusBarNotification>) {
this.notifications.value = notifications
val notifications: Flow<List<Notification>> = _notifications
internal fun setNotifications(notifications: List<Notification>) {
_notifications.value = notifications
}
override fun postNotification(notification: StatusBarNotification) {
notifications.value = notifications.value.filter { !isEqual(it, notification) } + notification
internal fun getNotifications(): List<Notification> = _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)
}

View File

@ -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<StatusBarNotification> {
@ -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() {

View File

@ -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() }

View File

@ -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()