Respect notification badge settings
This commit is contained in:
parent
b5a4d459d3
commit
b678662121
@ -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) {}
|
||||
}
|
||||
)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -3,5 +3,5 @@ package de.mm20.launcher2.notifications
|
||||
import org.koin.dsl.module
|
||||
|
||||
val notificationsModule = module {
|
||||
single<NotificationRepository> { NotificationRepositoryImpl() }
|
||||
single<NotificationRepository> { NotificationRepository() }
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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() }
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user