mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 18:35:40 +03:00
lifting settings change to cancel all notifications out of the renderer
- the renderer's responsibility it handling events
This commit is contained in:
parent
3023cb4d39
commit
c85afa96d3
5 changed files with 62 additions and 395 deletions
|
@ -16,26 +16,15 @@
|
||||||
package im.vector.app.features.notifications
|
package im.vector.app.features.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.Person
|
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat
|
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
|
||||||
import im.vector.app.ActiveSessionDataSource
|
import im.vector.app.ActiveSessionDataSource
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
|
||||||
import im.vector.app.core.utils.FirstThrottler
|
import im.vector.app.core.utils.FirstThrottler
|
||||||
import im.vector.app.features.displayname.getBestName
|
import im.vector.app.features.displayname.getBestName
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
|
||||||
import im.vector.app.features.invite.AutoAcceptInvites
|
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import me.gujun.android.span.span
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
@ -54,12 +43,8 @@ import javax.inject.Singleton
|
||||||
class NotificationDrawerManager @Inject constructor(private val context: Context,
|
class NotificationDrawerManager @Inject constructor(private val context: Context,
|
||||||
private val notificationUtils: NotificationUtils,
|
private val notificationUtils: NotificationUtils,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val stringProvider: StringProvider,
|
|
||||||
private val activeSessionDataSource: ActiveSessionDataSource,
|
private val activeSessionDataSource: ActiveSessionDataSource,
|
||||||
private val iconLoader: IconLoader,
|
private val notificationRenderer: NotificationRenderer) {
|
||||||
private val bitmapLoader: BitmapLoader,
|
|
||||||
private val outdatedDetector: OutdatedEventDetector?,
|
|
||||||
private val autoAcceptInvites: AutoAcceptInvites) {
|
|
||||||
|
|
||||||
private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY)
|
private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY)
|
||||||
private var backgroundHandler: Handler
|
private var backgroundHandler: Handler
|
||||||
|
@ -69,13 +54,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
backgroundHandler = Handler(handlerThread.looper)
|
backgroundHandler = Handler(handlerThread.looper)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first time the notification drawer is refreshed, we force re-render of all notifications
|
|
||||||
private var firstTime = true
|
|
||||||
|
|
||||||
private val eventList = loadEventInfo()
|
private val eventList = loadEventInfo()
|
||||||
|
|
||||||
private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
|
private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
|
||||||
|
|
||||||
private var currentRoomId: String? = null
|
private var currentRoomId: String? = null
|
||||||
|
|
||||||
// TODO Multi-session: this will have to be improved
|
// TODO Multi-session: this will have to be improved
|
||||||
|
@ -258,359 +238,17 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE)
|
val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
|
|
||||||
synchronized(eventList) {
|
synchronized(eventList) {
|
||||||
val useSplitNotifications = false
|
val newSettings = vectorPreferences.useCompleteNotificationFormat()
|
||||||
if (useSplitNotifications) {
|
if (newSettings != useCompleteNotificationFormat) {
|
||||||
// TODO
|
// Settings has changed, remove all current notifications
|
||||||
} else {
|
notificationUtils.cancelAllNotifications()
|
||||||
Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER ")
|
useCompleteNotificationFormat = newSettings
|
||||||
// TMP code
|
|
||||||
var hasNewEvent = false
|
|
||||||
var summaryIsNoisy = false
|
|
||||||
val summaryInboxStyle = NotificationCompat.InboxStyle()
|
|
||||||
|
|
||||||
// group events by room to create a single MessagingStyle notif
|
|
||||||
val roomIdToEventMap: MutableMap<String, MutableList<NotifiableMessageEvent>> = LinkedHashMap()
|
|
||||||
val simpleEvents: MutableList<SimpleNotifiableEvent> = ArrayList()
|
|
||||||
val invitationEvents: MutableList<InviteNotifiableEvent> = ArrayList()
|
|
||||||
|
|
||||||
val eventIterator = eventList.listIterator()
|
|
||||||
while (eventIterator.hasNext()) {
|
|
||||||
when (val event = eventIterator.next()) {
|
|
||||||
is NotifiableMessageEvent -> {
|
|
||||||
val roomId = event.roomId
|
|
||||||
val roomEvents = roomIdToEventMap.getOrPut(roomId) { ArrayList() }
|
|
||||||
|
|
||||||
if (shouldIgnoreMessageEventInRoom(roomId) || outdatedDetector?.isMessageOutdated(event) == true) {
|
|
||||||
// forget this event
|
|
||||||
eventIterator.remove()
|
|
||||||
} else {
|
|
||||||
roomEvents.add(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is InviteNotifiableEvent -> {
|
|
||||||
if (autoAcceptInvites.hideInvites) {
|
|
||||||
// Forget this event
|
|
||||||
eventIterator.remove()
|
|
||||||
} else {
|
|
||||||
invitationEvents.add(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is SimpleNotifiableEvent -> simpleEvents.add(event)
|
|
||||||
else -> Timber.w("Type not handled")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER ${roomIdToEventMap.size} room groups")
|
notificationRenderer.render(currentRoomId, session.myUserId, myUserDisplayName, myUserAvatarUrl, useCompleteNotificationFormat, eventList)
|
||||||
|
|
||||||
var globalLastMessageTimestamp = 0L
|
|
||||||
|
|
||||||
val newSettings = vectorPreferences.useCompleteNotificationFormat()
|
|
||||||
if (newSettings != useCompleteNotificationFormat) {
|
|
||||||
// Settings has changed, remove all current notifications
|
|
||||||
notificationUtils.cancelAllNotifications()
|
|
||||||
useCompleteNotificationFormat = newSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
var simpleNotificationRoomCounter = 0
|
|
||||||
var simpleNotificationMessageCounter = 0
|
|
||||||
|
|
||||||
// events have been grouped by roomId
|
|
||||||
for ((roomId, events) in roomIdToEventMap) {
|
|
||||||
// Build the notification for the room
|
|
||||||
if (events.isEmpty() || events.all { it.isRedacted }) {
|
|
||||||
// Just clear this notification
|
|
||||||
Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER $roomId has no more events")
|
|
||||||
notificationUtils.cancelNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
simpleNotificationRoomCounter++
|
|
||||||
val roomName = events[0].roomName ?: events[0].senderName ?: ""
|
|
||||||
|
|
||||||
val roomEventGroupInfo = RoomEventGroupInfo(
|
|
||||||
roomId = roomId,
|
|
||||||
isDirect = events[0].roomIsDirect,
|
|
||||||
roomDisplayName = roomName)
|
|
||||||
|
|
||||||
val style = NotificationCompat.MessagingStyle(Person.Builder()
|
|
||||||
.setName(myUserDisplayName)
|
|
||||||
.setIcon(iconLoader.getUserIcon(myUserAvatarUrl))
|
|
||||||
.setKey(events[0].matrixID)
|
|
||||||
.build())
|
|
||||||
|
|
||||||
style.isGroupConversation = !roomEventGroupInfo.isDirect
|
|
||||||
|
|
||||||
if (!roomEventGroupInfo.isDirect) {
|
|
||||||
style.conversationTitle = roomEventGroupInfo.roomDisplayName
|
|
||||||
}
|
|
||||||
|
|
||||||
val largeBitmap = getRoomBitmap(events)
|
|
||||||
|
|
||||||
for (event in events) {
|
|
||||||
// if all events in this room have already been displayed there is no need to update it
|
|
||||||
if (!event.hasBeenDisplayed && !event.isRedacted) {
|
|
||||||
roomEventGroupInfo.shouldBing = roomEventGroupInfo.shouldBing || event.noisy
|
|
||||||
roomEventGroupInfo.customSound = event.soundName
|
|
||||||
}
|
|
||||||
roomEventGroupInfo.hasNewEvent = roomEventGroupInfo.hasNewEvent || !event.hasBeenDisplayed
|
|
||||||
|
|
||||||
val senderPerson = if (event.outGoingMessage) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
Person.Builder()
|
|
||||||
.setName(event.senderName)
|
|
||||||
.setIcon(iconLoader.getUserIcon(event.senderAvatarPath))
|
|
||||||
.setKey(event.senderId)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
val openRoomIntent = RoomDetailActivity.shortcutIntent(context, roomId)
|
|
||||||
|
|
||||||
val shortcut = ShortcutInfoCompat.Builder(context, roomId)
|
|
||||||
.setLongLived(true)
|
|
||||||
.setIntent(openRoomIntent)
|
|
||||||
.setShortLabel(roomName)
|
|
||||||
.setIcon(largeBitmap?.let { IconCompat.createWithAdaptiveBitmap(it) } ?: iconLoader.getUserIcon(event.senderAvatarPath))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.outGoingMessage && event.outGoingMessageFailed) {
|
|
||||||
style.addMessage(stringProvider.getString(R.string.notification_inline_reply_failed), event.timestamp, senderPerson)
|
|
||||||
roomEventGroupInfo.hasSmartReplyError = true
|
|
||||||
} else {
|
|
||||||
if (!event.isRedacted) {
|
|
||||||
simpleNotificationMessageCounter++
|
|
||||||
style.addMessage(event.body, event.timestamp, senderPerson)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event.hasBeenDisplayed = true // we can consider it as displayed
|
|
||||||
|
|
||||||
// It is possible that this event was previously shown as an 'anonymous' simple notif.
|
|
||||||
// And now it will be merged in a single MessageStyle notif, so we can clean to be sure
|
|
||||||
notificationUtils.cancelNotificationMessage(event.eventId, ROOM_EVENT_NOTIFICATION_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (events.size == 1) {
|
|
||||||
val event = events[0]
|
|
||||||
if (roomEventGroupInfo.isDirect) {
|
|
||||||
val line = span {
|
|
||||||
span {
|
|
||||||
textStyle = "bold"
|
|
||||||
+String.format("%s: ", event.senderName)
|
|
||||||
}
|
|
||||||
+(event.description)
|
|
||||||
}
|
|
||||||
summaryInboxStyle.addLine(line)
|
|
||||||
} else {
|
|
||||||
val line = span {
|
|
||||||
span {
|
|
||||||
textStyle = "bold"
|
|
||||||
+String.format("%s: %s ", roomName, event.senderName)
|
|
||||||
}
|
|
||||||
+(event.description)
|
|
||||||
}
|
|
||||||
summaryInboxStyle.addLine(line)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val summaryLine = stringProvider.getQuantityString(
|
|
||||||
R.plurals.notification_compat_summary_line_for_room, events.size, roomName, events.size)
|
|
||||||
summaryInboxStyle.addLine(summaryLine)
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
// String not found or bad format
|
|
||||||
Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER failed to resolve string")
|
|
||||||
summaryInboxStyle.addLine(roomName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstTime || roomEventGroupInfo.hasNewEvent) {
|
|
||||||
// Should update displayed notification
|
|
||||||
Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER $roomId need refresh")
|
|
||||||
val lastMessageTimestamp = events.last().timestamp
|
|
||||||
|
|
||||||
if (globalLastMessageTimestamp < lastMessageTimestamp) {
|
|
||||||
globalLastMessageTimestamp = lastMessageTimestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
val tickerText = if (roomEventGroupInfo.isDirect) {
|
|
||||||
stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description)
|
|
||||||
} else {
|
|
||||||
stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useCompleteNotificationFormat) {
|
|
||||||
val notification = notificationUtils.buildMessagesListNotification(
|
|
||||||
style,
|
|
||||||
roomEventGroupInfo,
|
|
||||||
largeBitmap,
|
|
||||||
lastMessageTimestamp,
|
|
||||||
myUserDisplayName,
|
|
||||||
tickerText)
|
|
||||||
|
|
||||||
// is there an id for this room?
|
|
||||||
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasNewEvent = true
|
|
||||||
summaryIsNoisy = summaryIsNoisy || roomEventGroupInfo.shouldBing
|
|
||||||
} else {
|
|
||||||
Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER $roomId is up to date")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle invitation events
|
|
||||||
for (event in invitationEvents) {
|
|
||||||
// We build a invitation notification
|
|
||||||
if (firstTime || !event.hasBeenDisplayed) {
|
|
||||||
if (useCompleteNotificationFormat) {
|
|
||||||
val notification = notificationUtils.buildRoomInvitationNotification(event, session.myUserId)
|
|
||||||
notificationUtils.showNotificationMessage(event.roomId, ROOM_INVITATION_NOTIFICATION_ID, notification)
|
|
||||||
}
|
|
||||||
event.hasBeenDisplayed = true // we can consider it as displayed
|
|
||||||
hasNewEvent = true
|
|
||||||
summaryIsNoisy = summaryIsNoisy || event.noisy
|
|
||||||
summaryInboxStyle.addLine(event.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle simple events
|
|
||||||
for (event in simpleEvents) {
|
|
||||||
// We build a simple notification
|
|
||||||
if (firstTime || !event.hasBeenDisplayed) {
|
|
||||||
if (useCompleteNotificationFormat) {
|
|
||||||
val notification = notificationUtils.buildSimpleEventNotification(event, session.myUserId)
|
|
||||||
notificationUtils.showNotificationMessage(event.eventId, ROOM_EVENT_NOTIFICATION_ID, notification)
|
|
||||||
}
|
|
||||||
event.hasBeenDisplayed = true // we can consider it as displayed
|
|
||||||
hasNewEvent = true
|
|
||||||
summaryIsNoisy = summaryIsNoisy || event.noisy
|
|
||||||
summaryInboxStyle.addLine(event.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======== Build summary notification =========
|
|
||||||
// On Android 7.0 (API level 24) and higher, the system automatically builds a summary for
|
|
||||||
// your group using snippets of text from each notification. The user can expand this
|
|
||||||
// notification to see each separate notification.
|
|
||||||
// To support older versions, which cannot show a nested group of notifications,
|
|
||||||
// you must create an extra notification that acts as the summary.
|
|
||||||
// This appears as the only notification and the system hides all the others.
|
|
||||||
// So this summary should include a snippet from all the other notifications,
|
|
||||||
// which the user can tap to open your app.
|
|
||||||
// The behavior of the group summary may vary on some device types such as wearables.
|
|
||||||
// To ensure the best experience on all devices and versions, always include a group summary when you create a group
|
|
||||||
// https://developer.android.com/training/notify-user/group
|
|
||||||
|
|
||||||
if (eventList.isEmpty() || eventList.all { it.isRedacted }) {
|
|
||||||
notificationUtils.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID)
|
|
||||||
} else if (hasNewEvent) {
|
|
||||||
// FIXME roomIdToEventMap.size is not correct, this is the number of rooms
|
|
||||||
val nbEvents = roomIdToEventMap.size + simpleEvents.size
|
|
||||||
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
|
|
||||||
summaryInboxStyle.setBigContentTitle(sumTitle)
|
|
||||||
// TODO get latest event?
|
|
||||||
.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents))
|
|
||||||
|
|
||||||
if (useCompleteNotificationFormat) {
|
|
||||||
val notification = notificationUtils.buildSummaryListNotification(
|
|
||||||
summaryInboxStyle,
|
|
||||||
sumTitle,
|
|
||||||
noisy = hasNewEvent && summaryIsNoisy,
|
|
||||||
lastMessageTimestamp = globalLastMessageTimestamp)
|
|
||||||
|
|
||||||
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
|
|
||||||
} else {
|
|
||||||
// Add the simple events as message (?)
|
|
||||||
simpleNotificationMessageCounter += simpleEvents.size
|
|
||||||
val numberOfInvitations = invitationEvents.size
|
|
||||||
|
|
||||||
val privacyTitle = if (numberOfInvitations > 0) {
|
|
||||||
val invitationsStr = stringProvider.getQuantityString(R.plurals.notification_invitations, numberOfInvitations, numberOfInvitations)
|
|
||||||
if (simpleNotificationMessageCounter > 0) {
|
|
||||||
// Invitation and message
|
|
||||||
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
|
|
||||||
simpleNotificationMessageCounter, simpleNotificationMessageCounter)
|
|
||||||
if (simpleNotificationRoomCounter > 1) {
|
|
||||||
// In several rooms
|
|
||||||
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
|
|
||||||
simpleNotificationRoomCounter, simpleNotificationRoomCounter)
|
|
||||||
stringProvider.getString(
|
|
||||||
R.string.notification_unread_notified_messages_in_room_and_invitation,
|
|
||||||
messageStr,
|
|
||||||
roomStr,
|
|
||||||
invitationsStr
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// In one room
|
|
||||||
stringProvider.getString(
|
|
||||||
R.string.notification_unread_notified_messages_and_invitation,
|
|
||||||
messageStr,
|
|
||||||
invitationsStr
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Only invitation
|
|
||||||
invitationsStr
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No invitation, only messages
|
|
||||||
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
|
|
||||||
simpleNotificationMessageCounter, simpleNotificationMessageCounter)
|
|
||||||
if (simpleNotificationRoomCounter > 1) {
|
|
||||||
// In several rooms
|
|
||||||
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
|
|
||||||
simpleNotificationRoomCounter, simpleNotificationRoomCounter)
|
|
||||||
stringProvider.getString(R.string.notification_unread_notified_messages_in_room, messageStr, roomStr)
|
|
||||||
} else {
|
|
||||||
// In one room
|
|
||||||
messageStr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val notification = notificationUtils.buildSummaryListNotification(
|
|
||||||
style = null,
|
|
||||||
compatSummary = privacyTitle,
|
|
||||||
noisy = hasNewEvent && summaryIsNoisy,
|
|
||||||
lastMessageTimestamp = globalLastMessageTimestamp)
|
|
||||||
|
|
||||||
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNewEvent && summaryIsNoisy) {
|
|
||||||
try {
|
|
||||||
// turn the screen on for 3 seconds
|
|
||||||
/*
|
|
||||||
TODO
|
|
||||||
if (Matrix.getInstance(VectorApp.getInstance())!!.pushManager.isScreenTurnedOn) {
|
|
||||||
val pm = VectorApp.getInstance().getSystemService<PowerManager>()!!
|
|
||||||
val wl = pm.newWakeLock(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or PowerManager.ACQUIRE_CAUSES_WAKEUP,
|
|
||||||
NotificationDrawerManager::class.java.name)
|
|
||||||
wl.acquire(3000)
|
|
||||||
wl.release()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Timber.e(e, "## Failed to turn screen on")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// notice that we can get bit out of sync with actual display but not a big issue
|
|
||||||
firstTime = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRoomBitmap(events: List<NotifiableMessageEvent>): Bitmap? {
|
|
||||||
if (events.isEmpty()) return null
|
|
||||||
|
|
||||||
// Use the last event (most recent?)
|
|
||||||
val roomAvatarPath = events.last().roomAvatarPath ?: events.last().senderAvatarPath
|
|
||||||
|
|
||||||
return bitmapLoader.getRoomBitmap(roomAvatarPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean {
|
fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean {
|
||||||
return currentRoomId != null && roomId == currentRoomId
|
return currentRoomId != null && roomId == currentRoomId
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package im.vector.app.features.notifications
|
package im.vector.app.features.notifications
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NotificationFactory @Inject constructor(
|
class NotificationFactory @Inject constructor(
|
||||||
|
@ -83,7 +85,7 @@ private fun List<OneShotNotification>.mapToMeta() = filterIsInstance<OneShotNoti
|
||||||
|
|
||||||
sealed interface RoomNotification {
|
sealed interface RoomNotification {
|
||||||
data class Removed(val roomId: String) : RoomNotification
|
data class Removed(val roomId: String) : RoomNotification
|
||||||
data class Message(val notification: Notification, val meta: Meta) : RoomNotification {
|
data class Message(val notification: Notification, val shortcutInfo: ShortcutInfoCompat?, val meta: Meta) : RoomNotification {
|
||||||
data class Meta(
|
data class Meta(
|
||||||
val summaryLine: CharSequence,
|
val summaryLine: CharSequence,
|
||||||
val messageCount: Int,
|
val messageCount: Int,
|
||||||
|
|
|
@ -15,8 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.app.features.notifications
|
package im.vector.app.features.notifications
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -24,32 +29,29 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class NotificationRenderer @Inject constructor(private val notifiableEventProcessor: NotifiableEventProcessor,
|
class NotificationRenderer @Inject constructor(private val notifiableEventProcessor: NotifiableEventProcessor,
|
||||||
private val notificationDisplayer: NotificationDisplayer,
|
private val notificationDisplayer: NotificationDisplayer,
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val notificationFactory: NotificationFactory,
|
||||||
private val notificationFactory: NotificationFactory) {
|
private val appContext: Context) {
|
||||||
|
|
||||||
private var lastKnownEventList = -1
|
private var lastKnownEventList = -1
|
||||||
private var useCompleteNotificationFormat = vectorPreferences.useCompleteNotificationFormat()
|
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun render(currentRoomId: String?, myUserId: String, myUserDisplayName: String, myUserAvatarUrl: String?, eventList: MutableList<NotifiableEvent>) {
|
fun render(currentRoomId: String?,
|
||||||
|
myUserId: String,
|
||||||
|
myUserDisplayName: String,
|
||||||
|
myUserAvatarUrl: String?,
|
||||||
|
useCompleteNotificationFormat: Boolean,
|
||||||
|
eventList: MutableList<NotifiableEvent>) {
|
||||||
Timber.v("refreshNotificationDrawerBg()")
|
Timber.v("refreshNotificationDrawerBg()")
|
||||||
val newSettings = vectorPreferences.useCompleteNotificationFormat()
|
|
||||||
if (newSettings != useCompleteNotificationFormat) {
|
|
||||||
// Settings has changed, remove all current notifications
|
|
||||||
notificationDisplayer.cancelAllNotifications()
|
|
||||||
useCompleteNotificationFormat = newSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
val notificationEvents = notifiableEventProcessor.modifyAndProcess(eventList, currentRoomId)
|
val notificationEvents = notifiableEventProcessor.modifyAndProcess(eventList, currentRoomId)
|
||||||
if (lastKnownEventList == notificationEvents.hashCode()) {
|
if (lastKnownEventList == notificationEvents.hashCode()) {
|
||||||
Timber.d("Skipping notification update due to event list not changing")
|
Timber.d("Skipping notification update due to event list not changing")
|
||||||
} else {
|
} else {
|
||||||
processEvents(notificationEvents, myUserId, myUserDisplayName, myUserAvatarUrl)
|
processEvents(notificationEvents, myUserId, myUserDisplayName, myUserAvatarUrl, useCompleteNotificationFormat)
|
||||||
lastKnownEventList = notificationEvents.hashCode()
|
lastKnownEventList = notificationEvents.hashCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processEvents(notificationEvents: ProcessedNotificationEvents, myUserId: String, myUserDisplayName: String, myUserAvatarUrl: String?) {
|
private fun processEvents(notificationEvents: ProcessedNotificationEvents, myUserId: String, myUserDisplayName: String, myUserAvatarUrl: String?, useCompleteNotificationFormat: Boolean) {
|
||||||
val (roomEvents, simpleEvents, invitationEvents) = notificationEvents
|
val (roomEvents, simpleEvents, invitationEvents) = notificationEvents
|
||||||
with(notificationFactory) {
|
with(notificationFactory) {
|
||||||
val roomNotifications = roomEvents.toNotifications(myUserDisplayName, myUserAvatarUrl)
|
val roomNotifications = roomEvents.toNotifications(myUserDisplayName, myUserAvatarUrl)
|
||||||
|
@ -70,6 +72,9 @@ class NotificationRenderer @Inject constructor(private val notifiableEventProces
|
||||||
is RoomNotification.Removed -> notificationDisplayer.cancelNotificationMessage(wrapper.roomId, NotificationDrawerManager.ROOM_MESSAGES_NOTIFICATION_ID)
|
is RoomNotification.Removed -> notificationDisplayer.cancelNotificationMessage(wrapper.roomId, NotificationDrawerManager.ROOM_MESSAGES_NOTIFICATION_ID)
|
||||||
is RoomNotification.Message -> if (useCompleteNotificationFormat) {
|
is RoomNotification.Message -> if (useCompleteNotificationFormat) {
|
||||||
Timber.d("Updating room messages notification ${wrapper.meta.roomId}")
|
Timber.d("Updating room messages notification ${wrapper.meta.roomId}")
|
||||||
|
wrapper.shortcutInfo?.let {
|
||||||
|
ShortcutManagerCompat.pushDynamicShortcut(appContext, it)
|
||||||
|
}
|
||||||
notificationDisplayer.showNotificationMessage(wrapper.meta.roomId, NotificationDrawerManager.ROOM_MESSAGES_NOTIFICATION_ID, wrapper.notification)
|
notificationDisplayer.showNotificationMessage(wrapper.meta.roomId, NotificationDrawerManager.ROOM_MESSAGES_NOTIFICATION_ID, wrapper.notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,17 @@
|
||||||
|
|
||||||
package im.vector.app.features.notifications
|
package im.vector.app.features.notifications
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.os.Build
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.Person
|
import androidx.core.app.Person
|
||||||
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
import me.gujun.android.span.Span
|
import me.gujun.android.span.Span
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -30,7 +36,8 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
private val iconLoader: IconLoader,
|
private val iconLoader: IconLoader,
|
||||||
private val bitmapLoader: BitmapLoader,
|
private val bitmapLoader: BitmapLoader,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val notificationUtils: NotificationUtils
|
private val notificationUtils: NotificationUtils,
|
||||||
|
private val appContext: Context
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun createRoomMessage(events: List<NotifiableMessageEvent>, roomId: String, userDisplayName: String, userAvatarUrl: String?): RoomNotification.Message {
|
fun createRoomMessage(events: List<NotifiableMessageEvent>, roomId: String, userDisplayName: String, userAvatarUrl: String?): RoomNotification.Message {
|
||||||
|
@ -54,6 +61,19 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description)
|
stringProvider.getString(R.string.notification_ticker_text_dm, events.last().senderName, events.last().description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val largeBitmap = getRoomBitmap(events)
|
||||||
|
val shortcutInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
val openRoomIntent = RoomDetailActivity.shortcutIntent(appContext, roomId)
|
||||||
|
ShortcutInfoCompat.Builder(appContext, roomId)
|
||||||
|
.setLongLived(true)
|
||||||
|
.setIntent(openRoomIntent)
|
||||||
|
.setShortLabel(roomName)
|
||||||
|
.setIcon(largeBitmap?.let { IconCompat.createWithAdaptiveBitmap(it) } ?: iconLoader.getUserIcon(events.last().senderAvatarPath))
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
val lastMessageTimestamp = events.last().timestamp
|
val lastMessageTimestamp = events.last().timestamp
|
||||||
val smartReplyErrors = events.filter { it.isSmartReplyError() }
|
val smartReplyErrors = events.filter { it.isSmartReplyError() }
|
||||||
val messageCount = (events.size - smartReplyErrors.size)
|
val messageCount = (events.size - smartReplyErrors.size)
|
||||||
|
@ -72,22 +92,27 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
it.shouldBing = meta.shouldBing
|
it.shouldBing = meta.shouldBing
|
||||||
it.customSound = events.last().soundName
|
it.customSound = events.last().soundName
|
||||||
},
|
},
|
||||||
largeIcon = getRoomBitmap(events),
|
largeIcon = largeBitmap,
|
||||||
lastMessageTimestamp,
|
lastMessageTimestamp,
|
||||||
userDisplayName,
|
userDisplayName,
|
||||||
tickerText
|
tickerText
|
||||||
),
|
),
|
||||||
|
shortcutInfo,
|
||||||
meta
|
meta
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotificationCompat.MessagingStyle.addMessagesFromEvents(events: List<NotifiableMessageEvent>) {
|
private fun NotificationCompat.MessagingStyle.addMessagesFromEvents(events: List<NotifiableMessageEvent>) {
|
||||||
events.forEach { event ->
|
events.forEach { event ->
|
||||||
val senderPerson = Person.Builder()
|
val senderPerson = if (event.outGoingMessage) {
|
||||||
.setName(event.senderName)
|
null
|
||||||
.setIcon(iconLoader.getUserIcon(event.senderAvatarPath))
|
} else {
|
||||||
.setKey(event.senderId)
|
Person.Builder()
|
||||||
.build()
|
.setName(event.senderName)
|
||||||
|
.setIcon(iconLoader.getUserIcon(event.senderAvatarPath))
|
||||||
|
.setKey(event.senderId)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
when {
|
when {
|
||||||
event.isSmartReplyError() -> addMessage(stringProvider.getString(R.string.notification_inline_reply_failed), event.timestamp, senderPerson)
|
event.isSmartReplyError() -> addMessage(stringProvider.getString(R.string.notification_inline_reply_failed), event.timestamp, senderPerson)
|
||||||
else -> addMessage(event.body, event.timestamp, senderPerson)
|
else -> addMessage(event.body, event.timestamp, senderPerson)
|
||||||
|
|
|
@ -45,15 +45,11 @@ class NotificationRendererTest {
|
||||||
|
|
||||||
private val notifiableEventProcessor = FakeNotifiableEventProcessor()
|
private val notifiableEventProcessor = FakeNotifiableEventProcessor()
|
||||||
private val notificationDisplayer = FakeNotificationDisplayer()
|
private val notificationDisplayer = FakeNotificationDisplayer()
|
||||||
private val preferences = FakeVectorPreferences().also {
|
|
||||||
it.givenUseCompleteNotificationFormat(USE_COMPLETE_NOTIFICATION_FORMAT)
|
|
||||||
}
|
|
||||||
private val notificationFactory = FakeNotificationFactory()
|
private val notificationFactory = FakeNotificationFactory()
|
||||||
|
|
||||||
private val notificationRenderer = NotificationRenderer(
|
private val notificationRenderer = NotificationRenderer(
|
||||||
notifiableEventProcessor = notifiableEventProcessor.instance,
|
notifiableEventProcessor = notifiableEventProcessor.instance,
|
||||||
notificationDisplayer = notificationDisplayer.instance,
|
notificationDisplayer = notificationDisplayer.instance,
|
||||||
vectorPreferences = preferences.instance,
|
|
||||||
notificationFactory = notificationFactory.instance
|
notificationFactory = notificationFactory.instance
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -154,6 +150,7 @@ class NotificationRendererTest {
|
||||||
myUserId = MY_USER_ID,
|
myUserId = MY_USER_ID,
|
||||||
myUserDisplayName = MY_USER_DISPLAY_NAME,
|
myUserDisplayName = MY_USER_DISPLAY_NAME,
|
||||||
myUserAvatarUrl = MY_USER_AVATAR_URL,
|
myUserAvatarUrl = MY_USER_AVATAR_URL,
|
||||||
|
useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT,
|
||||||
eventList = AN_EVENT_LIST
|
eventList = AN_EVENT_LIST
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue