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())
|
val notifications by viewModel.notifications.collectAsState(emptyList())
|
||||||
|
|
||||||
for (not in notifications) {
|
for (not in notifications) {
|
||||||
val title =
|
val title = not.title?.takeIf { it.isNotBlank() }
|
||||||
not.notification.extras.getString(NotificationCompat.EXTRA_TITLE, null)
|
?: not.text?.takeIf { it.isNotBlank() }
|
||||||
?.takeIf { it.isNotBlank() }
|
|
||||||
?: not.notification.extras.getString(
|
|
||||||
NotificationCompat.EXTRA_TEXT,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
?.takeIf { it.isNotBlank() }
|
|
||||||
?: continue
|
?: continue
|
||||||
|
|
||||||
val icon =
|
val icon =
|
||||||
remember { not.notification.smallIcon?.loadDrawable(context) }?.let {
|
remember { not.smallIcon?.loadDrawable(context) }?.let {
|
||||||
rememberAsyncImagePainter(
|
rememberAsyncImagePainter(
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
@ -143,7 +137,7 @@ fun AppItem(
|
|||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
try {
|
||||||
not.notification.contentIntent?.send()
|
not.contentIntent?.send()
|
||||||
} catch (e: PendingIntent.CanceledException) {}
|
} catch (e: PendingIntent.CanceledException) {}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
|||||||
import de.mm20.launcher2.badges.BadgeService
|
import de.mm20.launcher2.badges.BadgeService
|
||||||
import de.mm20.launcher2.files.FileRepository
|
import de.mm20.launcher2.files.FileRepository
|
||||||
import de.mm20.launcher2.icons.IconService
|
import de.mm20.launcher2.icons.IconService
|
||||||
|
import de.mm20.launcher2.notifications.Notification
|
||||||
import de.mm20.launcher2.notifications.NotificationRepository
|
import de.mm20.launcher2.notifications.NotificationRepository
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.data.AppShortcut
|
import de.mm20.launcher2.search.data.AppShortcut
|
||||||
@ -122,7 +123,7 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearNotification(notification: StatusBarNotification) {
|
fun clearNotification(notification: Notification) {
|
||||||
notificationRepository.cancelNotification(notification)
|
notificationRepository.cancelNotification(notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,5 +3,5 @@ package de.mm20.launcher2.notifications
|
|||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val notificationsModule = 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
|
package de.mm20.launcher2.notifications
|
||||||
|
|
||||||
import android.service.notification.StatusBarNotification
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
interface NotificationRepository {
|
|
||||||
val notifications: Flow<List<StatusBarNotification>>
|
|
||||||
|
|
||||||
/**
|
class NotificationRepository {
|
||||||
* 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 {
|
|
||||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
override val notifications: MutableStateFlow<List<StatusBarNotification>> = MutableStateFlow(
|
|
||||||
|
private val _notifications: MutableStateFlow<List<Notification>> = MutableStateFlow(
|
||||||
emptyList()
|
emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun setNotifications(notifications: List<StatusBarNotification>) {
|
val notifications: Flow<List<Notification>> = _notifications
|
||||||
this.notifications.value = notifications
|
|
||||||
|
internal fun setNotifications(notifications: List<Notification>) {
|
||||||
|
_notifications.value = notifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun postNotification(notification: StatusBarNotification) {
|
internal fun getNotifications(): List<Notification> = _notifications.value
|
||||||
notifications.value = notifications.value.filter { !isEqual(it, notification) } + notification
|
|
||||||
|
internal fun onNotificationPosted(notification: Notification) {
|
||||||
|
_notifications.value = _notifications.value.filter { !isEqual(it, notification) } + notification
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeNotification(notification: StatusBarNotification) {
|
internal fun onNotificationRemoved(key: String) {
|
||||||
notifications.value = notifications.value.filter { !isEqual(it, notification) }
|
_notifications.value = _notifications.value.filter { it.key != key }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEqual(
|
private fun isEqual(
|
||||||
notification1: StatusBarNotification,
|
notification1: Notification,
|
||||||
notification2: StatusBarNotification
|
notification2: Notification
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return notification1.key == notification2.key
|
return notification1.key == notification2.key
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelNotification(notification: StatusBarNotification) {
|
fun cancelNotification(notification: Notification) {
|
||||||
NotificationService.getInstance()?.cancelNotification(notification.key)
|
NotificationService.getInstance()?.cancelNotification(notification.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,17 @@ import android.service.notification.NotificationListenerService
|
|||||||
import android.service.notification.StatusBarNotification
|
import android.service.notification.StatusBarNotification
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
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 org.koin.android.ext.android.inject
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
class NotificationService : NotificationListenerService() {
|
class NotificationService : NotificationListenerService() {
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
|
||||||
private val notificationRepository: NotificationRepository by inject()
|
private val notificationRepository: NotificationRepository by inject()
|
||||||
|
|
||||||
private val permissionsManager: PermissionsManager by inject()
|
private val permissionsManager: PermissionsManager by inject()
|
||||||
@ -24,8 +30,34 @@ class NotificationService : NotificationListenerService() {
|
|||||||
Log.d("MM20", "Notification listener connected")
|
Log.d("MM20", "Notification listener connected")
|
||||||
permissionsManager.reportNotificationListenerState(true)
|
permissionsManager.reportNotificationListenerState(true)
|
||||||
instance = WeakReference(this)
|
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> {
|
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) {
|
override fun onNotificationRemoved(sbn: StatusBarNotification) {
|
||||||
super.onNotificationRemoved(sbn)
|
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() {
|
override fun onListenerDisconnected() {
|
||||||
|
|||||||
@ -21,18 +21,17 @@ class NotificationBadgeProvider : BadgeProvider, KoinComponent {
|
|||||||
notificationRepository.notifications.map {
|
notificationRepository.notifications.map {
|
||||||
it.filter { it.packageName == packageName }
|
it.filter { it.packageName == packageName }
|
||||||
}.collectLatest {
|
}.collectLatest {
|
||||||
if (it.isEmpty()) {
|
if (it.isEmpty() || it.none { it.canShowBadge }) {
|
||||||
send(null)
|
send(null)
|
||||||
} else {
|
} else {
|
||||||
val badge = Badge(
|
val badge = Badge(
|
||||||
number = it.distinctBy { it.notification.shortcutId }.sumOf {
|
number = it.sumOf {
|
||||||
if(it.notification.shortcutId == null) 0
|
if (it.canShowBadge && !it.isGroupSummary) it.number
|
||||||
else it.notification.number
|
else 0
|
||||||
},
|
},
|
||||||
progress = it.mapNotNull {
|
progress = it.mapNotNull {
|
||||||
if (!it.notification.extras.containsKey(Notification.EXTRA_PROGRESS)) return@mapNotNull null
|
val progress = it.progress ?: return@mapNotNull null
|
||||||
val progress = it.notification.extras.getInt(Notification.EXTRA_PROGRESS)
|
val progressMax = it.progressMax ?: return@mapNotNull null
|
||||||
val progressMax = it.notification.extras.getInt(Notification.EXTRA_PROGRESS_MAX).takeIf { it > 0 } ?: return@mapNotNull null
|
|
||||||
return@mapNotNull progress.toFloat() / progressMax.toFloat()
|
return@mapNotNull progress.toFloat() / progressMax.toFloat()
|
||||||
}
|
}
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import coil.imageLoader
|
|||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.size.Scale
|
import coil.size.Scale
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import de.mm20.launcher2.notifications.Notification
|
||||||
import de.mm20.launcher2.notifications.NotificationRepository
|
import de.mm20.launcher2.notifications.NotificationRepository
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -114,11 +115,11 @@ internal class MusicServiceImpl(
|
|||||||
settings.allowListList.toSet(),
|
settings.allowListList.toSet(),
|
||||||
settings.denyListList.toSet()
|
settings.denyListList.toSet()
|
||||||
)
|
)
|
||||||
val sbn: StatusBarNotification? = notifications.filter {
|
val sbn: Notification? = notifications.filter {
|
||||||
it.notification.extras.getParcelable(NotificationCompat.EXTRA_MEDIA_SESSION) as? MediaSession.Token != null && musicApps.contains(it.packageName)
|
it.mediaSessionToken != null && musicApps.contains(it.packageName)
|
||||||
}.maxByOrNull { it.postTime }
|
}.maxByOrNull { it.postTime }
|
||||||
|
|
||||||
return@withContext (sbn?.notification?.extras?.get(NotificationCompat.EXTRA_MEDIA_SESSION) as? MediaSession.Token)
|
return@withContext sbn?.mediaSessionToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user