Merge pull request #5788 from k9mail/notification_refactoring

Separate notification UI/UX logic from notification creation
This commit is contained in:
cketti 2021-11-24 21:17:36 +01:00 committed by GitHub
commit 04c114bbca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1898 additions and 1368 deletions

View file

@ -0,0 +1,52 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.K9.LockScreenNotificationVisibility
private const val MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5
internal class BaseNotificationDataCreator {
fun createBaseNotificationData(notificationData: NotificationData): BaseNotificationData {
val account = notificationData.account
return BaseNotificationData(
account = account,
groupKey = NotificationGroupKeys.getGroupKey(account),
accountName = getAccountName(account),
color = account.chipColor,
newMessagesCount = notificationData.newMessagesCount,
lockScreenNotificationData = createLockScreenNotificationData(notificationData),
appearance = createNotificationAppearance(account)
)
}
private fun getAccountName(account: Account): String {
val accountDescription = account.description?.takeIf { it.isNotEmpty() }
return accountDescription ?: account.email
}
private fun createLockScreenNotificationData(data: NotificationData): LockScreenNotificationData {
return when (K9.lockScreenNotificationVisibility) {
LockScreenNotificationVisibility.NOTHING -> LockScreenNotificationData.None
LockScreenNotificationVisibility.APP_NAME -> LockScreenNotificationData.AppName
LockScreenNotificationVisibility.EVERYTHING -> LockScreenNotificationData.Public
LockScreenNotificationVisibility.MESSAGE_COUNT -> LockScreenNotificationData.MessageCount
LockScreenNotificationVisibility.SENDERS -> LockScreenNotificationData.SenderNames(getSenderNames(data))
}
}
private fun getSenderNames(data: NotificationData): String {
return data.getContentForSummaryNotification().asSequence()
.map { it.sender }
.distinct()
.take(MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION)
.joinToString()
}
private fun createNotificationAppearance(account: Account): NotificationAppearance {
return with(account.notificationSetting) {
NotificationAppearance(ringtone = ringtone, vibrationPattern = vibration, ledColor = ledColor)
}
}
}

View file

@ -56,31 +56,45 @@ val coreNotificationModule = module {
}
single {
NewMailNotificationController(
notificationHelper = get(),
contentCreator = get(),
notificationManager = get(),
newMailNotificationManager = get(),
summaryNotificationCreator = get(),
singleMessageNotificationCreator = get()
)
}
single { NotificationContentCreator(context = get(), resourceProvider = get()) }
single {
NewMailNotificationManager(
contentCreator = get(),
baseNotificationDataCreator = get(),
singleMessageNotificationDataCreator = get(),
summaryNotificationDataCreator = get(),
clock = get()
)
}
factory { NotificationContentCreator(context = get(), resourceProvider = get()) }
factory { BaseNotificationDataCreator() }
factory { SingleMessageNotificationDataCreator() }
factory { SummaryNotificationDataCreator(singleMessageNotificationDataCreator = get()) }
factory {
SingleMessageNotificationCreator(
notificationHelper = get(),
actionCreator = get(),
resourceProvider = get(),
lockScreenNotificationCreator = get()
lockScreenNotificationCreator = get(),
notificationManager = get()
)
}
single {
factory {
SummaryNotificationCreator(
notificationHelper = get(),
actionCreator = get(),
lockScreenNotificationCreator = get(),
singleMessageNotificationCreator = get(),
resourceProvider = get()
resourceProvider = get(),
notificationManager = get()
)
}
single { LockScreenNotificationCreator(notificationHelper = get(), resourceProvider = get()) }
factory { LockScreenNotificationCreator(notificationHelper = get(), resourceProvider = get()) }
single {
PushNotificationManager(
context = get(),

View file

@ -2,82 +2,59 @@ package com.fsck.k9.notification
import android.app.Notification
import androidx.core.app.NotificationCompat
import com.fsck.k9.K9
import com.fsck.k9.K9.LockScreenNotificationVisibility
internal class LockScreenNotificationCreator(
private val notificationHelper: NotificationHelper,
private val resourceProvider: NotificationResourceProvider
) {
fun configureLockScreenNotification(builder: NotificationCompat.Builder, notificationData: NotificationData) {
when (K9.lockScreenNotificationVisibility) {
LockScreenNotificationVisibility.NOTHING -> {
fun configureLockScreenNotification(
builder: NotificationCompat.Builder,
baseNotificationData: BaseNotificationData
) {
when (baseNotificationData.lockScreenNotificationData) {
LockScreenNotificationData.None -> {
builder.setVisibility(NotificationCompat.VISIBILITY_SECRET)
}
LockScreenNotificationVisibility.APP_NAME -> {
LockScreenNotificationData.AppName -> {
builder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
}
LockScreenNotificationVisibility.EVERYTHING -> {
LockScreenNotificationData.Public -> {
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
}
LockScreenNotificationVisibility.SENDERS -> {
val publicNotification = createPublicNotificationWithSenderList(notificationData)
is LockScreenNotificationData.SenderNames -> {
val publicNotification = createPublicNotificationWithSenderList(baseNotificationData)
builder.setPublicVersion(publicNotification)
}
LockScreenNotificationVisibility.MESSAGE_COUNT -> {
val publicNotification = createPublicNotificationWithNewMessagesCount(notificationData)
LockScreenNotificationData.MessageCount -> {
val publicNotification = createPublicNotificationWithNewMessagesCount(baseNotificationData)
builder.setPublicVersion(publicNotification)
}
}
}
private fun createPublicNotificationWithSenderList(notificationData: NotificationData): Notification {
val builder = createPublicNotification(notificationData)
val newMessages = notificationData.newMessagesCount
if (newMessages == 1) {
val holder = notificationData.holderForLatestNotification
builder.setContentText(holder.content.sender)
} else {
val contents = notificationData.getContentForSummaryNotification()
val senderList = createCommaSeparatedListOfSenders(contents)
builder.setContentText(senderList)
}
return builder.build()
private fun createPublicNotificationWithSenderList(baseNotificationData: BaseNotificationData): Notification {
val notificationData = baseNotificationData.lockScreenNotificationData as LockScreenNotificationData.SenderNames
return createPublicNotification(baseNotificationData)
.setContentText(notificationData.senderNames)
.build()
}
private fun createPublicNotificationWithNewMessagesCount(notificationData: NotificationData): Notification {
val builder = createPublicNotification(notificationData)
val account = notificationData.account
val accountName = notificationHelper.getAccountName(account)
builder.setContentText(accountName)
return builder.build()
private fun createPublicNotificationWithNewMessagesCount(baseNotificationData: BaseNotificationData): Notification {
return createPublicNotification(baseNotificationData)
.setContentText(baseNotificationData.accountName)
.build()
}
private fun createPublicNotification(notificationData: NotificationData): NotificationCompat.Builder {
val account = notificationData.account
val newMessagesCount = notificationData.newMessagesCount
private fun createPublicNotification(baseNotificationData: BaseNotificationData): NotificationCompat.Builder {
val account = baseNotificationData.account
val newMessagesCount = baseNotificationData.newMessagesCount
val title = resourceProvider.newMessagesTitle(newMessagesCount)
return notificationHelper.createNotificationBuilder(account, NotificationChannelManager.ChannelType.MESSAGES)
.setSmallIcon(resourceProvider.iconNewMail)
.setColor(account.chipColor)
.setColor(baseNotificationData.color)
.setNumber(newMessagesCount)
.setContentTitle(title)
.setCategory(NotificationCompat.CATEGORY_EMAIL)
}
fun createCommaSeparatedListOfSenders(contents: List<NotificationContent>): String {
return contents.asSequence()
.map { it.sender }
.distinct()
.take(MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION)
.joinToString()
}
companion object {
const val MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5
}
}

View file

@ -1,6 +1,5 @@
package com.fsck.k9.notification
import android.util.SparseArray
import androidx.core.app.NotificationManagerCompat
import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference
@ -9,135 +8,61 @@ import com.fsck.k9.mailstore.LocalMessage
/**
* Handle notifications for new messages.
*/
internal open class NewMailNotificationController(
private val notificationHelper: NotificationHelper,
private val contentCreator: NotificationContentCreator,
internal class NewMailNotificationController(
private val notificationManager: NotificationManagerCompat,
private val newMailNotificationManager: NewMailNotificationManager,
private val summaryNotificationCreator: SummaryNotificationCreator,
private val singleMessageNotificationCreator: SingleMessageNotificationCreator
) {
private val notifications = SparseArray<NotificationData>()
private val lock = Any()
fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean) {
val content = contentCreator.createFromMessage(account, message)
val notificationData = newMailNotificationManager.addNewMailNotification(account, message, silent)
synchronized(lock) {
val notificationData = getOrCreateNotificationData(account)
val result = notificationData.addNotificationContent(content)
if (result.shouldCancelNotification) {
val notificationId = result.notificationId
cancelNotification(notificationId)
}
if (notificationData.isSingleMessageNotification) {
createSingleMessageNotificationWithLockScreenNotification(account, notificationData)
} else {
createSingleMessageNotification(account, result.notificationHolder)
}
createSummaryNotification(account, notificationData, silent)
}
processNewMailNotificationData(notificationData)
}
fun removeNewMailNotification(account: Account, messageReference: MessageReference) {
synchronized(lock) {
val notificationData = getNotificationData(account) ?: return
val notificationData = newMailNotificationManager.removeNewMailNotification(account, messageReference)
val result = notificationData.removeNotificationForMessage(messageReference)
if (result.isUnknownNotification) return
cancelNotification(result.notificationId)
if (notificationData.isSingleMessageNotification) {
createSingleMessageNotificationWithLockScreenNotification(account, notificationData)
} else if (result.shouldCreateNotification) {
createSingleMessageNotification(account, result.notificationHolder)
}
updateSummaryNotification(account, notificationData)
if (notificationData != null) {
processNewMailNotificationData(notificationData)
}
}
fun clearNewMailNotifications(account: Account) {
val notificationData = synchronized(lock) { removeNotificationData(account) } ?: return
val cancelNotificationIds = newMailNotificationManager.clearNewMailNotifications(account)
for (notificationId in notificationData.getActiveNotificationIds()) {
cancelNotification(notificationId)
cancelNotifications(cancelNotificationIds)
}
private fun processNewMailNotificationData(notificationData: NewMailNotificationData) {
cancelNotifications(notificationData.cancelNotificationIds)
for (singleNotificationData in notificationData.singleNotificationData) {
createSingleNotification(notificationData.baseNotificationData, singleNotificationData)
}
val notificationId = NotificationIds.getNewMailSummaryNotificationId(account)
cancelNotification(notificationId)
}
private fun getOrCreateNotificationData(account: Account): NotificationData {
val notificationData = getNotificationData(account)
if (notificationData != null) return notificationData
val accountNumber = account.accountNumber
val newNotificationHolder = createNotificationData(account)
notifications.put(accountNumber, newNotificationHolder)
return newNotificationHolder
}
private fun getNotificationData(account: Account): NotificationData? {
val accountNumber = account.accountNumber
return notifications[accountNumber]
}
private fun removeNotificationData(account: Account): NotificationData? {
val accountNumber = account.accountNumber
val notificationData = notifications[accountNumber]
notifications.remove(accountNumber)
return notificationData
}
protected open fun createNotificationData(account: Account): NotificationData {
return NotificationData(account)
}
private fun cancelNotification(notificationId: Int) {
notificationManager.cancel(notificationId)
}
private fun updateSummaryNotification(account: Account, notificationData: NotificationData) {
if (notificationData.newMessagesCount == 0) {
clearNewMailNotifications(account)
} else {
createSummaryNotification(account, notificationData, silent = true)
notificationData.summaryNotificationData?.let { summaryNotificationData ->
createSummaryNotification(notificationData.baseNotificationData, summaryNotificationData)
}
}
private fun createSummaryNotification(account: Account, notificationData: NotificationData, silent: Boolean) {
val notification = summaryNotificationCreator.buildSummaryNotification(account, notificationData, silent)
val notificationId = NotificationIds.getNewMailSummaryNotificationId(account)
notificationManager.notify(notificationId, notification)
private fun cancelNotifications(notificationIds: List<Int>) {
for (notificationId in notificationIds) {
notificationManager.cancel(notificationId)
}
}
private fun createSingleMessageNotification(account: Account, holder: NotificationHolder) {
val notification = singleMessageNotificationCreator.buildSingleMessageNotification(account, holder)
val notificationId = holder.notificationId
notificationManager.notify(notificationId, notification)
}
// When there's only one notification the "public version" of the notification that might be displayed on a secure
// lockscreen isn't taken from the summary notification, but from the single "grouped" notification.
private fun createSingleMessageNotificationWithLockScreenNotification(
account: Account,
notificationData: NotificationData
private fun createSingleNotification(
baseNotificationData: BaseNotificationData,
singleNotificationData: SingleNotificationData
) {
val holder = notificationData.holderForLatestNotification
val notification = singleMessageNotificationCreator.buildSingleMessageNotificationWithLockScreenNotification(
account,
holder,
notificationData
)
val notificationId = holder.notificationId
notificationManager.notify(notificationId, notification)
singleMessageNotificationCreator.createSingleNotification(baseNotificationData, singleNotificationData)
}
private val notificationManager: NotificationManagerCompat
get() = notificationHelper.getNotificationManager()
private fun createSummaryNotification(
baseNotificationData: BaseNotificationData,
summaryNotificationData: SummaryNotificationData
) {
summaryNotificationCreator.createSummaryNotification(baseNotificationData, summaryNotificationData)
}
}

View file

@ -0,0 +1,87 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference
internal data class NewMailNotificationData(
val cancelNotificationIds: List<Int>,
val baseNotificationData: BaseNotificationData,
val singleNotificationData: List<SingleNotificationData>,
val summaryNotificationData: SummaryNotificationData?
)
internal data class BaseNotificationData(
val account: Account,
val accountName: String,
val groupKey: String,
val color: Int,
val newMessagesCount: Int,
val lockScreenNotificationData: LockScreenNotificationData,
val appearance: NotificationAppearance
)
internal sealed interface LockScreenNotificationData {
object None : LockScreenNotificationData
object AppName : LockScreenNotificationData
object Public : LockScreenNotificationData
object MessageCount : LockScreenNotificationData
data class SenderNames(val senderNames: String) : LockScreenNotificationData
}
internal data class NotificationAppearance(
val ringtone: String?,
val vibrationPattern: LongArray?,
val ledColor: Int?
)
internal data class SingleNotificationData(
val notificationId: Int,
val isSilent: Boolean,
val timestamp: Long,
val content: NotificationContent,
val actions: List<NotificationAction>,
val wearActions: List<WearNotificationAction>,
val addLockScreenNotification: Boolean
)
internal sealed interface SummaryNotificationData
internal data class SummarySingleNotificationData(
val singleNotificationData: SingleNotificationData
) : SummaryNotificationData
internal data class SummaryInboxNotificationData(
val notificationId: Int,
val isSilent: Boolean,
val timestamp: Long,
val content: List<CharSequence>,
val additionalMessagesCount: Int,
val messageReferences: List<MessageReference>,
val actions: List<SummaryNotificationAction>,
val wearActions: List<SummaryWearNotificationAction>
) : SummaryNotificationData
internal enum class NotificationAction {
Reply,
MarkAsRead,
Delete
}
internal enum class WearNotificationAction {
Reply,
MarkAsRead,
Delete,
Archive,
Spam
}
internal enum class SummaryNotificationAction {
MarkAsRead,
Delete
}
internal enum class SummaryWearNotificationAction {
MarkAsRead,
Delete,
Archive
}

View file

@ -0,0 +1,143 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.Clock
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.mailstore.LocalMessage
/**
* Manages notifications for new messages
*/
internal class NewMailNotificationManager(
private val contentCreator: NotificationContentCreator,
private val baseNotificationDataCreator: BaseNotificationDataCreator,
private val singleMessageNotificationDataCreator: SingleMessageNotificationDataCreator,
private val summaryNotificationDataCreator: SummaryNotificationDataCreator,
private val clock: Clock
) {
private val notifications = mutableMapOf<Int, NotificationData>()
private val lock = Any()
fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean): NewMailNotificationData {
val content = contentCreator.createFromMessage(account, message)
synchronized(lock) {
val notificationData = getOrCreateNotificationData(account)
val result = notificationData.addNotificationContent(content)
val singleNotificationData = createSingleNotificationData(
account = account,
notificationId = result.notificationHolder.notificationId,
content = result.notificationHolder.content,
addLockScreenNotification = notificationData.isSingleMessageNotification
)
return NewMailNotificationData(
cancelNotificationIds = if (result.shouldCancelNotification) {
listOf(result.notificationId)
} else {
emptyList()
},
baseNotificationData = createBaseNotificationData(notificationData),
singleNotificationData = listOf(singleNotificationData),
summaryNotificationData = createSummaryNotificationData(notificationData, silent)
)
}
}
fun removeNewMailNotification(account: Account, messageReference: MessageReference): NewMailNotificationData? {
synchronized(lock) {
val notificationData = getNotificationData(account) ?: return null
val result = notificationData.removeNotificationForMessage(messageReference)
if (result.isUnknownNotification) return null
if (notificationData.newMessagesCount == 0) {
return NewMailNotificationData(
cancelNotificationIds = listOf(
NotificationIds.getNewMailSummaryNotificationId(account),
result.notificationId
),
baseNotificationData = createBaseNotificationData(notificationData),
singleNotificationData = emptyList(),
summaryNotificationData = null
)
}
val singleNotificationData = if (result.shouldCreateNotification) {
val singleNotificationData = createSingleNotificationData(
account = account,
notificationId = result.notificationHolder.notificationId,
content = result.notificationHolder.content,
addLockScreenNotification = notificationData.isSingleMessageNotification
)
listOf(singleNotificationData)
} else {
emptyList()
}
return NewMailNotificationData(
cancelNotificationIds = listOf(result.notificationId),
baseNotificationData = createBaseNotificationData(notificationData),
singleNotificationData = singleNotificationData,
summaryNotificationData = createSummaryNotificationData(notificationData, silent = true)
)
}
}
fun clearNewMailNotifications(account: Account): List<Int> {
synchronized(lock) {
val notificationData = removeNotificationData(account) ?: return emptyList()
return notificationData.getActiveNotificationIds() +
NotificationIds.getNewMailSummaryNotificationId(account)
}
}
private fun createBaseNotificationData(notificationData: NotificationData): BaseNotificationData {
return baseNotificationDataCreator.createBaseNotificationData(notificationData)
}
private fun createSingleNotificationData(
account: Account,
notificationId: Int,
content: NotificationContent,
addLockScreenNotification: Boolean
): SingleNotificationData {
return singleMessageNotificationDataCreator.createSingleNotificationData(
account,
notificationId,
content,
timestamp = now(),
addLockScreenNotification
)
}
private fun createSummaryNotificationData(data: NotificationData, silent: Boolean): SummaryNotificationData {
return summaryNotificationDataCreator.createSummaryNotificationData(data, timestamp = now(), silent)
}
private fun getOrCreateNotificationData(account: Account): NotificationData {
val notificationData = getNotificationData(account)
if (notificationData != null) return notificationData
val accountNumber = account.accountNumber
val newNotificationHolder = NotificationData(account)
notifications[accountNumber] = newNotificationHolder
return newNotificationHolder
}
private fun getNotificationData(account: Account): NotificationData? {
val accountNumber = account.accountNumber
return notifications[accountNumber]
}
private fun removeNotificationData(account: Account): NotificationData? {
val accountNumber = account.accountNumber
val notificationData = notifications[accountNumber]
notifications.remove(accountNumber)
return notificationData
}
private fun now(): Long = clock.time
}

View file

@ -1,7 +1,6 @@
package com.fsck.k9.notification
import android.app.PendingIntent
import android.content.Context
import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference
@ -21,7 +20,6 @@ interface NotificationActionCreator {
fun createDismissAllMessagesPendingIntent(account: Account, notificationId: Int): PendingIntent
fun createDismissMessagePendingIntent(
context: Context,
messageReference: MessageReference,
notificationId: Int
): PendingIntent

View file

@ -2,7 +2,7 @@ package com.fsck.k9.notification
import com.fsck.k9.controller.MessageReference
internal class NotificationContent(
internal data class NotificationContent(
val messageReference: MessageReference,
val sender: String,
val subject: String,

View file

@ -1,6 +1,5 @@
package com.fsck.k9.notification
import android.util.SparseBooleanArray
import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference
import java.util.LinkedList
@ -11,7 +10,7 @@ import java.util.LinkedList
internal class NotificationData(val account: Account) {
private val activeNotifications = LinkedList<NotificationHolder>()
private val additionalNotifications = LinkedList<NotificationContent>()
private val notificationIdsInUse = SparseBooleanArray()
private val notificationIdsInUse = mutableMapOf<Int, Boolean>()
val newMessagesCount: Int
get() = activeNotifications.size + additionalNotifications.size
@ -65,15 +64,15 @@ internal class NotificationData(val account: Account) {
}
private fun isNotificationInUse(notificationId: Int): Boolean {
return notificationIdsInUse[notificationId]
return notificationIdsInUse[notificationId] ?: false
}
private fun markNotificationIdAsInUse(notificationId: Int) {
notificationIdsInUse.put(notificationId, true)
notificationIdsInUse[notificationId] = true
}
private fun markNotificationIdAsFree(notificationId: Int) {
notificationIdsInUse.delete(notificationId)
notificationIdsInUse.remove(notificationId)
}
private fun createNotificationHolder(notificationId: Int, content: NotificationContent): NotificationHolder {
@ -100,8 +99,8 @@ internal class NotificationData(val account: Account) {
.toList()
}
fun getActiveNotificationIds(): IntArray {
return activeNotifications.map { it.notificationId }.toIntArray()
fun getActiveNotificationIds(): List<Int> {
return activeNotifications.map { it.notificationId }
}
fun removeNotificationForMessage(messageReference: MessageReference): RemoveNotificationResult {

View file

@ -78,8 +78,8 @@ class NotificationHelper(
}
companion object {
private const val NOTIFICATION_LED_ON_TIME = 500
private const val NOTIFICATION_LED_OFF_TIME = 2000
internal const val NOTIFICATION_LED_ON_TIME = 500
internal const val NOTIFICATION_LED_OFF_TIME = 2000
private const val NOTIFICATION_LED_FAST_ON_TIME = 100
private const val NOTIFICATION_LED_FAST_OFF_TIME = 100
@ -88,3 +88,28 @@ class NotificationHelper(
internal const val NOTIFICATION_LED_FAILURE_COLOR = -0x10000
}
}
internal fun NotificationCompat.Builder.setAppearance(
silent: Boolean,
appearance: NotificationAppearance
): NotificationCompat.Builder = apply {
if (silent) {
setSilent(true)
} else {
if (!appearance.ringtone.isNullOrEmpty()) {
setSound(Uri.parse(appearance.ringtone))
}
if (appearance.vibrationPattern != null) {
setVibrate(appearance.vibrationPattern)
}
if (appearance.ledColor != null) {
setLights(
appearance.ledColor,
NotificationHelper.NOTIFICATION_LED_ON_TIME,
NotificationHelper.NOTIFICATION_LED_OFF_TIME
)
}
}
}

View file

@ -1,218 +1,185 @@
package com.fsck.k9.notification
import android.app.Notification
import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import com.fsck.k9.Account
import com.fsck.k9.K9
import androidx.core.app.NotificationCompat.WearableExtender
import androidx.core.app.NotificationManagerCompat
import com.fsck.k9.notification.NotificationChannelManager.ChannelType
import androidx.core.app.NotificationCompat.Builder as NotificationBuilder
internal open class SingleMessageNotificationCreator(
internal class SingleMessageNotificationCreator(
private val notificationHelper: NotificationHelper,
private val actionCreator: NotificationActionCreator,
private val resourceProvider: NotificationResourceProvider,
private val lockScreenNotificationCreator: LockScreenNotificationCreator,
private val notificationManager: NotificationManagerCompat
) {
fun createSingleNotification(
baseNotificationData: BaseNotificationData,
singleNotificationData: SingleNotificationData,
isGroupSummary: Boolean = false
) {
val account = baseNotificationData.account
val notificationId = singleNotificationData.notificationId
val content = singleNotificationData.content
fun buildSingleMessageNotification(account: Account, holder: NotificationHolder): Notification {
val notificationId = holder.notificationId
return createSingleMessageNotificationBuilder(account, holder, notificationId)
.setNotificationSilent()
.build()
}
fun buildSingleMessageNotificationWithLockScreenNotification(
account: Account,
holder: NotificationHolder,
notificationData: NotificationData
): Notification {
val notificationId = holder.notificationId
return createSingleMessageNotificationBuilder(account, holder, notificationId)
.setNotificationSilent()
.apply {
lockScreenNotificationCreator.configureLockScreenNotification(this, notificationData)
}
.build()
}
fun createSingleMessageNotificationBuilder(
account: Account,
holder: NotificationHolder,
notificationId: Int
): NotificationCompat.Builder {
val accountName = notificationHelper.getAccountName(account)
val content = holder.content
val groupKey = NotificationGroupKeys.getGroupKey(account)
val builder = notificationHelper.createNotificationBuilder(account, ChannelType.MESSAGES)
.setSmallIcon(resourceProvider.iconNewMail)
.setColor(account.chipColor)
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
val notification = notificationHelper.createNotificationBuilder(account, ChannelType.MESSAGES)
.setCategory(NotificationCompat.CATEGORY_EMAIL)
.setAutoCancel(true)
.setGroup(baseNotificationData.groupKey)
.setGroupSummary(isGroupSummary)
.setSmallIcon(resourceProvider.iconNewMail)
.setColor(baseNotificationData.color)
.setWhen(singleNotificationData.timestamp)
.setTicker(content.summary)
.setGroup(groupKey)
.setContentTitle(content.sender)
.setContentText(content.subject)
.setSubText(accountName)
.setSubText(baseNotificationData.accountName)
.setBigText(content.preview)
.setContentIntent(createViewIntent(content, notificationId))
.setDeleteIntent(createDismissIntent(content, notificationId))
.setDeviceActions(singleNotificationData)
.setWearActions(singleNotificationData)
.setAppearance(singleNotificationData.isSilent, baseNotificationData.appearance)
.setLockScreenNotification(baseNotificationData, singleNotificationData.addLockScreenNotification)
.build()
val style = createBigTextStyle(builder)
style.bigText(content.preview)
builder.setStyle(style)
val contentIntent = actionCreator.createViewMessagePendingIntent(content.messageReference, notificationId)
builder.setContentIntent(contentIntent)
val deletePendingIntent = actionCreator.createDismissMessagePendingIntent(
notificationHelper.getContext(), content.messageReference, holder.notificationId
)
builder.setDeleteIntent(deletePendingIntent)
addActions(builder, account, holder)
return builder
notificationManager.notify(notificationId, notification)
}
private fun addActions(builder: NotificationCompat.Builder, account: Account, holder: NotificationHolder) {
addDeviceActions(builder, holder)
addWearActions(builder, account, holder)
private fun NotificationBuilder.setBigText(text: CharSequence) = apply {
setStyle(NotificationCompat.BigTextStyle().bigText(text))
}
private fun addDeviceActions(builder: NotificationCompat.Builder, holder: NotificationHolder) {
addDeviceReplyAction(builder, holder)
addDeviceMarkAsReadAction(builder, holder)
addDeviceDeleteAction(builder, holder)
private fun createViewIntent(content: NotificationContent, notificationId: Int): PendingIntent {
return actionCreator.createViewMessagePendingIntent(content.messageReference, notificationId)
}
private fun addDeviceReplyAction(builder: NotificationCompat.Builder, holder: NotificationHolder) {
private fun createDismissIntent(content: NotificationContent, notificationId: Int): PendingIntent {
return actionCreator.createDismissMessagePendingIntent(content.messageReference, notificationId)
}
private fun NotificationBuilder.setDeviceActions(notificationData: SingleNotificationData) = apply {
val actions = notificationData.actions
for (action in actions) {
when (action) {
NotificationAction.Reply -> addReplyAction(notificationData)
NotificationAction.MarkAsRead -> addMarkAsReadAction(notificationData)
NotificationAction.Delete -> addDeleteAction(notificationData)
}
}
}
private fun NotificationBuilder.addReplyAction(notificationData: SingleNotificationData) {
val icon = resourceProvider.iconReply
val title = resourceProvider.actionReply()
val content = holder.content
val content = notificationData.content
val messageReference = content.messageReference
val replyToMessagePendingIntent =
actionCreator.createReplyPendingIntent(messageReference, holder.notificationId)
actionCreator.createReplyPendingIntent(messageReference, notificationData.notificationId)
builder.addAction(icon, title, replyToMessagePendingIntent)
addAction(icon, title, replyToMessagePendingIntent)
}
private fun addDeviceMarkAsReadAction(builder: NotificationCompat.Builder, holder: NotificationHolder) {
private fun NotificationBuilder.addMarkAsReadAction(notificationData: SingleNotificationData) {
val icon = resourceProvider.iconMarkAsRead
val title = resourceProvider.actionMarkAsRead()
val content = holder.content
val notificationId = holder.notificationId
val content = notificationData.content
val notificationId = notificationData.notificationId
val messageReference = content.messageReference
val action = actionCreator.createMarkMessageAsReadPendingIntent(messageReference, notificationId)
builder.addAction(icon, title, action)
addAction(icon, title, action)
}
private fun addDeviceDeleteAction(builder: NotificationCompat.Builder, holder: NotificationHolder) {
if (!isDeleteActionEnabled()) {
return
}
private fun NotificationBuilder.addDeleteAction(notificationData: SingleNotificationData) {
val icon = resourceProvider.iconDelete
val title = resourceProvider.actionDelete()
val content = holder.content
val notificationId = holder.notificationId
val content = notificationData.content
val notificationId = notificationData.notificationId
val messageReference = content.messageReference
val action = actionCreator.createDeleteMessagePendingIntent(messageReference, notificationId)
builder.addAction(icon, title, action)
addAction(icon, title, action)
}
private fun addWearActions(builder: NotificationCompat.Builder, account: Account, holder: NotificationHolder) {
val wearableExtender = NotificationCompat.WearableExtender()
addReplyAction(wearableExtender, holder)
addMarkAsReadAction(wearableExtender, holder)
if (isDeleteActionAvailableForWear()) {
addDeleteAction(wearableExtender, holder)
private fun NotificationBuilder.setWearActions(notificationData: SingleNotificationData) = apply {
val wearableExtender = WearableExtender().apply {
for (action in notificationData.wearActions) {
when (action) {
WearNotificationAction.Reply -> addReplyAction(notificationData)
WearNotificationAction.MarkAsRead -> addMarkAsReadAction(notificationData)
WearNotificationAction.Delete -> addDeleteAction(notificationData)
WearNotificationAction.Archive -> addArchiveAction(notificationData)
WearNotificationAction.Spam -> addMarkAsSpamAction(notificationData)
}
}
}
if (isArchiveActionAvailableForWear(account)) {
addArchiveAction(wearableExtender, holder)
}
if (isSpamActionAvailableForWear(account)) {
addMarkAsSpamAction(wearableExtender, holder)
}
builder.extend(wearableExtender)
extend(wearableExtender)
}
private fun addReplyAction(wearableExtender: NotificationCompat.WearableExtender, holder: NotificationHolder) {
private fun WearableExtender.addReplyAction(notificationData: SingleNotificationData) {
val icon = resourceProvider.wearIconReplyAll
val title = resourceProvider.actionReply()
val messageReference = holder.content.messageReference
val notificationId = holder.notificationId
val messageReference = notificationData.content.messageReference
val notificationId = notificationData.notificationId
val action = actionCreator.createReplyPendingIntent(messageReference, notificationId)
val replyAction = NotificationCompat.Action.Builder(icon, title, action).build()
wearableExtender.addAction(replyAction)
addAction(replyAction)
}
private fun addMarkAsReadAction(wearableExtender: NotificationCompat.WearableExtender, holder: NotificationHolder) {
private fun WearableExtender.addMarkAsReadAction(notificationData: SingleNotificationData) {
val icon = resourceProvider.wearIconMarkAsRead
val title = resourceProvider.actionMarkAsRead()
val messageReference = holder.content.messageReference
val notificationId = holder.notificationId
val messageReference = notificationData.content.messageReference
val notificationId = notificationData.notificationId
val action = actionCreator.createMarkMessageAsReadPendingIntent(messageReference, notificationId)
val markAsReadAction = NotificationCompat.Action.Builder(icon, title, action).build()
wearableExtender.addAction(markAsReadAction)
addAction(markAsReadAction)
}
private fun addDeleteAction(wearableExtender: NotificationCompat.WearableExtender, holder: NotificationHolder) {
private fun WearableExtender.addDeleteAction(notificationData: SingleNotificationData) {
val icon = resourceProvider.wearIconDelete
val title = resourceProvider.actionDelete()
val messageReference = holder.content.messageReference
val notificationId = holder.notificationId
val messageReference = notificationData.content.messageReference
val notificationId = notificationData.notificationId
val action = actionCreator.createDeleteMessagePendingIntent(messageReference, notificationId)
val deleteAction = NotificationCompat.Action.Builder(icon, title, action).build()
wearableExtender.addAction(deleteAction)
addAction(deleteAction)
}
private fun addArchiveAction(wearableExtender: NotificationCompat.WearableExtender, holder: NotificationHolder) {
private fun WearableExtender.addArchiveAction(notificationData: SingleNotificationData) {
val icon = resourceProvider.wearIconArchive
val title = resourceProvider.actionArchive()
val messageReference = holder.content.messageReference
val notificationId = holder.notificationId
val messageReference = notificationData.content.messageReference
val notificationId = notificationData.notificationId
val action = actionCreator.createArchiveMessagePendingIntent(messageReference, notificationId)
val archiveAction = NotificationCompat.Action.Builder(icon, title, action).build()
wearableExtender.addAction(archiveAction)
addAction(archiveAction)
}
private fun addMarkAsSpamAction(wearableExtender: NotificationCompat.WearableExtender, holder: NotificationHolder) {
private fun WearableExtender.addMarkAsSpamAction(notificationData: SingleNotificationData) {
val icon = resourceProvider.wearIconMarkAsSpam
val title = resourceProvider.actionMarkAsSpam()
val messageReference = holder.content.messageReference
val notificationId = holder.notificationId
val messageReference = notificationData.content.messageReference
val notificationId = notificationData.notificationId
val action = actionCreator.createMarkMessageAsSpamPendingIntent(messageReference, notificationId)
val spamAction = NotificationCompat.Action.Builder(icon, title, action).build()
wearableExtender.addAction(spamAction)
addAction(spamAction)
}
private fun isDeleteActionAvailableForWear(): Boolean {
return isDeleteActionEnabled() && !K9.isConfirmDeleteFromNotification
}
private fun isDeleteActionEnabled(): Boolean {
return K9.notificationQuickDeleteBehaviour != K9.NotificationQuickDelete.NEVER
}
private fun isArchiveActionAvailableForWear(account: Account): Boolean {
return account.archiveFolderId != null
}
private fun isSpamActionAvailableForWear(account: Account): Boolean {
return account.spamFolderId != null && !K9.isConfirmSpam
}
protected open fun createBigTextStyle(builder: NotificationCompat.Builder?): NotificationCompat.BigTextStyle {
return NotificationCompat.BigTextStyle(builder)
private fun NotificationBuilder.setLockScreenNotification(
notificationData: BaseNotificationData,
addLockScreenNotification: Boolean
) = apply {
if (addLockScreenNotification) {
lockScreenNotificationCreator.configureLockScreenNotification(this, notificationData)
}
}
}

View file

@ -0,0 +1,89 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.K9
internal class SingleMessageNotificationDataCreator {
fun createSingleNotificationData(
account: Account,
notificationId: Int,
content: NotificationContent,
timestamp: Long,
addLockScreenNotification: Boolean
): SingleNotificationData {
return SingleNotificationData(
notificationId = notificationId,
isSilent = true,
timestamp = timestamp,
content = content,
actions = createSingleNotificationActions(),
wearActions = createSingleNotificationWearActions(account),
addLockScreenNotification = addLockScreenNotification
)
}
fun createSummarySingleNotificationData(
data: NotificationData,
timestamp: Long,
silent: Boolean
): SummarySingleNotificationData {
return SummarySingleNotificationData(
SingleNotificationData(
notificationId = NotificationIds.getNewMailSummaryNotificationId(data.account),
isSilent = silent,
timestamp = timestamp,
content = data.holderForLatestNotification.content,
actions = createSingleNotificationActions(),
wearActions = createSingleNotificationWearActions(data.account),
addLockScreenNotification = false,
),
)
}
@OptIn(ExperimentalStdlibApi::class)
private fun createSingleNotificationActions(): List<NotificationAction> {
return buildList {
add(NotificationAction.Reply)
add(NotificationAction.MarkAsRead)
if (isDeleteActionEnabled()) {
add(NotificationAction.Delete)
}
}
}
@OptIn(ExperimentalStdlibApi::class)
private fun createSingleNotificationWearActions(account: Account): List<WearNotificationAction> {
return buildList {
add(WearNotificationAction.Reply)
add(WearNotificationAction.MarkAsRead)
if (isDeleteActionAvailableForWear()) {
add(WearNotificationAction.Delete)
}
if (account.hasArchiveFolder()) {
add(WearNotificationAction.Archive)
}
if (isSpamActionAvailableForWear(account)) {
add(WearNotificationAction.Spam)
}
}
}
private fun isDeleteActionEnabled(): Boolean {
return K9.notificationQuickDeleteBehaviour != K9.NotificationQuickDelete.NEVER
}
// We don't support confirming actions on Wear devices. So don't show the action when confirmation is enabled.
private fun isDeleteActionAvailableForWear(): Boolean {
return isDeleteActionEnabled() && !K9.isConfirmDeleteFromNotification
}
// We don't support confirming actions on Wear devices. So don't show the action when confirmation is enabled.
private fun isSpamActionAvailableForWear(account: Account): Boolean {
return account.hasSpamFolder() && !K9.isConfirmSpam
}
}

View file

@ -1,213 +1,210 @@
package com.fsck.k9.notification
import android.app.Notification
import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.WearableExtender
import androidx.core.app.NotificationManagerCompat
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.K9.NotificationQuickDelete
import com.fsck.k9.notification.NotificationChannelManager.ChannelType
import com.fsck.k9.notification.NotificationGroupKeys.getGroupKey
import com.fsck.k9.notification.NotificationIds.getNewMailSummaryNotificationId
import androidx.core.app.NotificationCompat.Builder as NotificationBuilder
internal open class SummaryNotificationCreator(
internal class SummaryNotificationCreator(
private val notificationHelper: NotificationHelper,
private val actionCreator: NotificationActionCreator,
private val lockScreenNotificationCreator: LockScreenNotificationCreator,
private val singleMessageNotificationCreator: SingleMessageNotificationCreator,
private val resourceProvider: NotificationResourceProvider
private val resourceProvider: NotificationResourceProvider,
private val notificationManager: NotificationManagerCompat
) {
fun buildSummaryNotification(account: Account, notificationData: NotificationData, silent: Boolean): Notification {
val builder = when {
notificationData.isSingleMessageNotification -> {
val holder = notificationData.holderForLatestNotification
createSingleMessageNotification(account, holder)
fun createSummaryNotification(
baseNotificationData: BaseNotificationData,
summaryNotificationData: SummaryNotificationData
) {
when (summaryNotificationData) {
is SummarySingleNotificationData -> {
createSingleMessageNotification(baseNotificationData, summaryNotificationData.singleNotificationData)
}
else -> {
createInboxStyleSummaryNotification(account, notificationData)
is SummaryInboxNotificationData -> {
createInboxStyleSummaryNotification(baseNotificationData, summaryNotificationData)
}
}
val notificationId = getNewMailSummaryNotificationId(account)
val deletePendingIntent = actionCreator.createDismissAllMessagesPendingIntent(account, notificationId)
builder.setDeleteIntent(deletePendingIntent)
lockScreenNotificationCreator.configureLockScreenNotification(builder, notificationData)
val notificationSetting = account.notificationSetting
notificationHelper.configureNotification(
builder = builder,
ringtone = if (notificationSetting.isRingEnabled) notificationSetting.ringtone else null,
vibrationPattern = if (notificationSetting.isVibrateEnabled) notificationSetting.vibration else null,
ledColor = if (notificationSetting.isLedEnabled) notificationSetting.ledColor else null,
ledSpeed = NotificationHelper.NOTIFICATION_LED_BLINK_SLOW,
ringAndVibrate = !silent
)
return builder.build()
}
private fun createSingleMessageNotification(
account: Account,
holder: NotificationHolder
): NotificationCompat.Builder {
val notificationId = getNewMailSummaryNotificationId(account)
val builder = singleMessageNotificationCreator.createSingleMessageNotificationBuilder(account, holder, notificationId)
builder.setGroupSummary(true)
return builder
baseNotificationData: BaseNotificationData,
singleNotificationData: SingleNotificationData
) {
singleMessageNotificationCreator.createSingleNotification(
baseNotificationData,
singleNotificationData,
isGroupSummary = true
)
}
private fun createInboxStyleSummaryNotification(
account: Account,
notificationData: NotificationData
): NotificationCompat.Builder {
val latestNotification = notificationData.holderForLatestNotification
val newMessagesCount = notificationData.newMessagesCount
val accountName = notificationHelper.getAccountName(account)
baseNotificationData: BaseNotificationData,
notificationData: SummaryInboxNotificationData
) {
val account = baseNotificationData.account
val accountName = baseNotificationData.accountName
val newMessagesCount = baseNotificationData.newMessagesCount
val title = resourceProvider.newMessagesTitle(newMessagesCount)
val summary = if (notificationData.hasSummaryOverflowMessages()) {
resourceProvider.additionalMessages(notificationData.getSummaryOverflowMessagesCount(), accountName)
val summary = buildInboxSummaryText(accountName, notificationData)
val notification = notificationHelper.createNotificationBuilder(account, ChannelType.MESSAGES)
.setCategory(NotificationCompat.CATEGORY_EMAIL)
.setAutoCancel(true)
.setGroup(baseNotificationData.groupKey)
.setGroupSummary(true)
.setSmallIcon(resourceProvider.iconNewMail)
.setColor(baseNotificationData.color)
.setWhen(notificationData.timestamp)
.setNumber(notificationData.additionalMessagesCount)
.setTicker(notificationData.content.firstOrNull())
.setContentTitle(title)
.setSubText(accountName)
.setInboxStyle(title, summary, notificationData.content)
.setContentIntent(createViewIntent(account, notificationData))
.setDeleteIntent(createDismissIntent(account, notificationData.notificationId))
.setDeviceActions(account, notificationData)
.setWearActions(account, notificationData)
.setAppearance(notificationData.isSilent, baseNotificationData.appearance)
.setLockScreenNotification(baseNotificationData)
.build()
notificationManager.notify(notificationData.notificationId, notification)
}
private fun buildInboxSummaryText(accountName: String, notificationData: SummaryInboxNotificationData): String {
return if (notificationData.additionalMessagesCount > 0) {
resourceProvider.additionalMessages(notificationData.additionalMessagesCount, accountName)
} else {
accountName
}
val groupKey = getGroupKey(account)
}
val builder = notificationHelper.createNotificationBuilder(account, ChannelType.MESSAGES)
.setSmallIcon(resourceProvider.iconNewMail)
.setColor(account.chipColor)
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setCategory(NotificationCompat.CATEGORY_EMAIL)
.setNumber(newMessagesCount)
.setTicker(latestNotification.content.summary)
.setGroup(groupKey)
.setGroupSummary(true)
.setContentTitle(title)
.setSubText(accountName)
val style = createInboxStyle(builder)
private fun NotificationBuilder.setInboxStyle(
title: String,
summary: String,
contentLines: List<CharSequence>
) = apply {
val style = NotificationCompat.InboxStyle()
.setBigContentTitle(title)
.setSummaryText(summary)
for (content in notificationData.getContentForSummaryNotification()) {
style.addLine(content.summary)
for (line in contentLines) {
style.addLine(line)
}
builder.setStyle(style)
addMarkAllAsReadAction(builder, notificationData)
addDeleteAllAction(builder, notificationData)
addWearActions(builder, notificationData)
val notificationId = getNewMailSummaryNotificationId(account)
val messageReferences = notificationData.getAllMessageReferences()
val contentIntent = actionCreator.createViewMessagesPendingIntent(account, messageReferences, notificationId)
builder.setContentIntent(contentIntent)
return builder
setStyle(style)
}
private fun addMarkAllAsReadAction(builder: NotificationCompat.Builder, notificationData: NotificationData) {
private fun createViewIntent(account: Account, notificationData: SummaryInboxNotificationData): PendingIntent {
return actionCreator.createViewMessagesPendingIntent(
account = account,
messageReferences = notificationData.messageReferences,
notificationId = notificationData.notificationId
)
}
private fun createDismissIntent(account: Account, notificationId: Int): PendingIntent {
return actionCreator.createDismissAllMessagesPendingIntent(account, notificationId)
}
private fun NotificationBuilder.setDeviceActions(
account: Account,
notificationData: SummaryInboxNotificationData
) = apply {
for (action in notificationData.actions) {
when (action) {
SummaryNotificationAction.MarkAsRead -> addMarkAllAsReadAction(account, notificationData)
SummaryNotificationAction.Delete -> addDeleteAllAction(account, notificationData)
}
}
}
private fun NotificationBuilder.addMarkAllAsReadAction(
account: Account,
notificationData: SummaryInboxNotificationData
) {
val icon = resourceProvider.iconMarkAsRead
val title = resourceProvider.actionMarkAsRead()
val account = notificationData.account
val messageReferences = notificationData.getAllMessageReferences()
val notificationId = getNewMailSummaryNotificationId(account)
val messageReferences = notificationData.messageReferences
val notificationId = notificationData.notificationId
val markAllAsReadPendingIntent =
actionCreator.createMarkAllAsReadPendingIntent(account, messageReferences, notificationId)
builder.addAction(icon, title, markAllAsReadPendingIntent)
addAction(icon, title, markAllAsReadPendingIntent)
}
private fun addDeleteAllAction(builder: NotificationCompat.Builder, notificationData: NotificationData) {
if (K9.notificationQuickDeleteBehaviour !== NotificationQuickDelete.ALWAYS) {
return
}
private fun NotificationBuilder.addDeleteAllAction(
account: Account,
notificationData: SummaryInboxNotificationData
) {
val icon = resourceProvider.iconDelete
val title = resourceProvider.actionDelete()
val account = notificationData.account
val notificationId = getNewMailSummaryNotificationId(account)
val messageReferences = notificationData.getAllMessageReferences()
val messageReferences = notificationData.messageReferences
val action = actionCreator.createDeleteAllPendingIntent(account, messageReferences, notificationId)
builder.addAction(icon, title, action)
addAction(icon, title, action)
}
private fun addWearActions(builder: NotificationCompat.Builder, notificationData: NotificationData) {
val wearableExtender = NotificationCompat.WearableExtender()
addMarkAllAsReadWearAction(wearableExtender, notificationData)
if (isDeleteActionAvailableForWear()) {
addDeleteAllWearAction(wearableExtender, notificationData)
private fun NotificationBuilder.setWearActions(
account: Account,
notificationData: SummaryInboxNotificationData
) = apply {
val wearableExtender = WearableExtender().apply {
for (action in notificationData.wearActions) {
when (action) {
SummaryWearNotificationAction.MarkAsRead -> addMarkAllAsReadAction(account, notificationData)
SummaryWearNotificationAction.Delete -> addDeleteAllAction(account, notificationData)
SummaryWearNotificationAction.Archive -> addArchiveAllAction(account, notificationData)
}
}
}
if (isArchiveActionAvailableForWear(notificationData.account)) {
addArchiveAllWearAction(wearableExtender, notificationData)
}
builder.extend(wearableExtender)
extend(wearableExtender)
}
private fun addMarkAllAsReadWearAction(
wearableExtender: NotificationCompat.WearableExtender,
notificationData: NotificationData
private fun WearableExtender.addMarkAllAsReadAction(
account: Account,
notificationData: SummaryInboxNotificationData
) {
val icon = resourceProvider.wearIconMarkAsRead
val title = resourceProvider.actionMarkAllAsRead()
val account = notificationData.account
val messageReferences = notificationData.getAllMessageReferences()
val messageReferences = notificationData.messageReferences
val notificationId = getNewMailSummaryNotificationId(account)
val action = actionCreator.createMarkAllAsReadPendingIntent(account, messageReferences, notificationId)
val markAsReadAction = NotificationCompat.Action.Builder(icon, title, action).build()
wearableExtender.addAction(markAsReadAction)
addAction(markAsReadAction)
}
private fun addDeleteAllWearAction(
wearableExtender: NotificationCompat.WearableExtender,
notificationData: NotificationData
) {
private fun WearableExtender.addDeleteAllAction(account: Account, notificationData: SummaryInboxNotificationData) {
val icon = resourceProvider.wearIconDelete
val title = resourceProvider.actionDeleteAll()
val account = notificationData.account
val messageReferences = notificationData.getAllMessageReferences()
val messageReferences = notificationData.messageReferences
val notificationId = getNewMailSummaryNotificationId(account)
val action = actionCreator.createDeleteAllPendingIntent(account, messageReferences, notificationId)
val deleteAction = NotificationCompat.Action.Builder(icon, title, action).build()
wearableExtender.addAction(deleteAction)
addAction(deleteAction)
}
private fun addArchiveAllWearAction(
wearableExtender: NotificationCompat.WearableExtender,
notificationData: NotificationData
) {
private fun WearableExtender.addArchiveAllAction(account: Account, notificationData: SummaryInboxNotificationData) {
val icon = resourceProvider.wearIconArchive
val title = resourceProvider.actionArchiveAll()
val account = notificationData.account
val messageReferences = notificationData.getAllMessageReferences()
val messageReferences = notificationData.messageReferences
val notificationId = getNewMailSummaryNotificationId(account)
val action = actionCreator.createArchiveAllPendingIntent(account, messageReferences, notificationId)
val archiveAction = NotificationCompat.Action.Builder(icon, title, action).build()
wearableExtender.addAction(archiveAction)
addAction(archiveAction)
}
private fun isDeleteActionAvailableForWear(): Boolean {
return isDeleteActionEnabled() && !K9.isConfirmDeleteFromNotification
}
private fun isDeleteActionEnabled(): Boolean {
return K9.notificationQuickDeleteBehaviour != NotificationQuickDelete.NEVER
}
private fun isArchiveActionAvailableForWear(account: Account): Boolean {
return account.archiveFolderId != null
}
protected open fun createInboxStyle(builder: NotificationCompat.Builder?): NotificationCompat.InboxStyle {
return NotificationCompat.InboxStyle(builder)
private fun NotificationBuilder.setLockScreenNotification(notificationData: BaseNotificationData) = apply {
lockScreenNotificationCreator.configureLockScreenNotification(this, notificationData)
}
}

View file

@ -0,0 +1,85 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.K9
internal class SummaryNotificationDataCreator(
private val singleMessageNotificationDataCreator: SingleMessageNotificationDataCreator
) {
fun createSummaryNotificationData(
data: NotificationData,
timestamp: Long,
silent: Boolean
): SummaryNotificationData {
val shouldBeSilent = silent || K9.isQuietTime
return if (data.isSingleMessageNotification) {
createSummarySingleNotificationData(data, timestamp, shouldBeSilent)
} else {
createSummaryInboxNotificationData(data, timestamp, shouldBeSilent)
}
}
private fun createSummarySingleNotificationData(
data: NotificationData,
timestamp: Long,
silent: Boolean
): SummaryNotificationData {
return singleMessageNotificationDataCreator.createSummarySingleNotificationData(data, timestamp, silent)
}
private fun createSummaryInboxNotificationData(
data: NotificationData,
timestamp: Long,
silent: Boolean
): SummaryNotificationData {
return SummaryInboxNotificationData(
notificationId = NotificationIds.getNewMailSummaryNotificationId(data.account),
isSilent = silent,
timestamp = timestamp,
content = getSummaryContent(data),
additionalMessagesCount = data.getSummaryOverflowMessagesCount(),
messageReferences = data.getAllMessageReferences(),
actions = createSummaryNotificationActions(),
wearActions = createSummaryWearNotificationActions(data.account)
)
}
private fun getSummaryContent(data: NotificationData): List<CharSequence> {
return data.getContentForSummaryNotification().map { it.summary }
}
@OptIn(ExperimentalStdlibApi::class)
private fun createSummaryNotificationActions(): List<SummaryNotificationAction> {
return buildList {
add(SummaryNotificationAction.MarkAsRead)
if (isDeleteActionEnabled()) {
add(SummaryNotificationAction.Delete)
}
}
}
@OptIn(ExperimentalStdlibApi::class)
private fun createSummaryWearNotificationActions(account: Account): List<SummaryWearNotificationAction> {
return buildList {
add(SummaryWearNotificationAction.MarkAsRead)
if (isDeleteActionAvailableForWear()) {
add(SummaryWearNotificationAction.Delete)
}
if (account.hasArchiveFolder()) {
add(SummaryWearNotificationAction.Archive)
}
}
}
private fun isDeleteActionEnabled(): Boolean {
return K9.notificationQuickDeleteBehaviour == K9.NotificationQuickDelete.ALWAYS
}
// We don't support confirming actions on Wear devices. So don't show the action when confirmation is enabled.
private fun isDeleteActionAvailableForWear(): Boolean {
return isDeleteActionEnabled() && !K9.isConfirmDeleteFromNotification
}
}

View file

@ -0,0 +1,197 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.Identity
import com.fsck.k9.K9
import com.fsck.k9.K9.LockScreenNotificationVisibility
import com.fsck.k9.NotificationSetting
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.kotlin.mock
class BaseNotificationDataCreatorTest {
private val account = createAccount()
private val notificationDataCreator = BaseNotificationDataCreator()
@Test
fun `account instance`() {
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.account).isSameInstanceAs(account)
}
@Test
fun `account name from description property`() {
account.description = "description"
account.email = "irrelevant@k9mail.example"
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.accountName).isEqualTo("description")
}
@Test
fun `account description is blank`() {
account.description = ""
account.email = "test@k9mail.example"
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.accountName).isEqualTo("test@k9mail.example")
}
@Test
fun `account description is null`() {
account.description = null
account.email = "test@k9mail.example"
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.accountName).isEqualTo("test@k9mail.example")
}
@Test
fun `group key`() {
account.accountNumber = 42
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.groupKey).isEqualTo("newMailNotifications-42")
}
@Test
fun `notification color`() {
account.chipColor = 0xFF0000
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.color).isEqualTo(0xFF0000)
}
@Test
fun `new messages count`() {
val notificationData = createNotificationData(senders = listOf("irrelevant", "irrelevant"))
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.newMessagesCount).isEqualTo(2)
}
@Test
fun `do not display notification on lock screen`() {
setLockScreenMode(LockScreenNotificationVisibility.NOTHING)
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.lockScreenNotificationData).isEqualTo(LockScreenNotificationData.None)
}
@Test
fun `display application name on lock screen`() {
setLockScreenMode(LockScreenNotificationVisibility.APP_NAME)
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.lockScreenNotificationData).isEqualTo(LockScreenNotificationData.AppName)
}
@Test
fun `display new message count on lock screen`() {
setLockScreenMode(LockScreenNotificationVisibility.MESSAGE_COUNT)
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.lockScreenNotificationData).isEqualTo(LockScreenNotificationData.MessageCount)
}
@Test
fun `display message sender names on lock screen`() {
setLockScreenMode(LockScreenNotificationVisibility.SENDERS)
val notificationData = createNotificationData(senders = listOf("Sender One", "Sender Two", "Sender Three"))
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.lockScreenNotificationData).isInstanceOf(LockScreenNotificationData.SenderNames::class.java)
val senderNamesData = result.lockScreenNotificationData as LockScreenNotificationData.SenderNames
assertThat(senderNamesData.senderNames).isEqualTo("Sender Three, Sender Two, Sender One")
}
@Test
fun `display notification on lock screen`() {
setLockScreenMode(LockScreenNotificationVisibility.EVERYTHING)
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.lockScreenNotificationData).isEqualTo(LockScreenNotificationData.Public)
}
@Test
fun ringtone() {
account.notificationSetting.ringtone = "content://ringtone/1"
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.appearance.ringtone).isEqualTo("content://ringtone/1")
}
@Test
fun `vibration pattern`() {
account.notificationSetting.vibratePattern = 3
account.notificationSetting.vibrateTimes = 2
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.appearance.vibrationPattern).isEqualTo(NotificationSetting.getVibration(3, 2))
}
@Test
fun `led color`() {
account.notificationSetting.ledColor = 0x00FF00
val notificationData = createNotificationData()
val result = notificationDataCreator.createBaseNotificationData(notificationData)
assertThat(result.appearance.ledColor).isEqualTo(0x00FF00)
}
private fun setLockScreenMode(mode: LockScreenNotificationVisibility) {
K9.lockScreenNotificationVisibility = mode
}
private fun createNotificationData(senders: List<String> = emptyList()): NotificationData {
val notificationData = NotificationData(account)
for (sender in senders) {
notificationData.addNotificationContent(
NotificationContent(
messageReference = mock(),
sender = sender,
preview = "irrelevant",
summary = "irrelevant",
subject = "irrelevant"
)
)
}
return notificationData
}
private fun createAccount(): Account {
return Account("00000000-0000-4000-0000-000000000000").apply {
description = "account name"
identities = listOf(Identity())
notificationSetting.vibrateTimes = 1
}
}
}

View file

@ -3,150 +3,84 @@ package com.fsck.k9.notification
import androidx.core.app.NotificationCompat
import androidx.test.core.app.ApplicationProvider
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.K9.LockScreenNotificationVisibility
import com.fsck.k9.RobolectricTest
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.testing.MockHelper.mockBuilder
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stubbing
private const val ACCOUNT_NAME = "Hugo"
private const val NEW_MESSAGE_COUNT = 3
class LockScreenNotificationCreatorTest : RobolectricTest() {
private val account = Account("00000000-0000-0000-0000-000000000000")
private val resourceProvider = TestNotificationResourceProvider()
private val builder = createFakeNotificationBuilder()
private val publicBuilder = createFakeNotificationBuilder()
private var notificationData = createFakeNotificationData()
private var notificationCreator = LockScreenNotificationCreator(
notificationHelper = createFakeNotificationHelper(publicBuilder),
resourceProvider = resourceProvider
)
@Test
fun configureLockScreenNotification_NOTHING() {
K9.lockScreenNotificationVisibility = LockScreenNotificationVisibility.NOTHING
fun `no lock screen notification`() {
val baseNotificationData = createBaseNotificationData(LockScreenNotificationData.None)
notificationCreator.configureLockScreenNotification(builder, notificationData)
notificationCreator.configureLockScreenNotification(builder, baseNotificationData)
verify(builder).setVisibility(NotificationCompat.VISIBILITY_SECRET)
}
@Test
fun configureLockScreenNotification_APP_NAME() {
K9.lockScreenNotificationVisibility = LockScreenNotificationVisibility.APP_NAME
fun `app name`() {
val baseNotificationData = createBaseNotificationData(LockScreenNotificationData.AppName)
notificationCreator.configureLockScreenNotification(builder, notificationData)
notificationCreator.configureLockScreenNotification(builder, baseNotificationData)
verify(builder).setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
}
@Test
fun configureLockScreenNotification_EVERYTHING() {
K9.lockScreenNotificationVisibility = LockScreenNotificationVisibility.EVERYTHING
fun `regular notification on lock screen`() {
val baseNotificationData = createBaseNotificationData(LockScreenNotificationData.Public)
notificationCreator.configureLockScreenNotification(builder, notificationData)
notificationCreator.configureLockScreenNotification(builder, baseNotificationData)
verify(builder).setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
}
@Test
fun configureLockScreenNotification_SENDERS_withSingleMessage() {
K9.lockScreenNotificationVisibility = LockScreenNotificationVisibility.SENDERS
val holder = NotificationHolder(
notificationId = 42,
content = createNotificationContent(sender = "alice@example.com")
fun `list of sender names`() {
val baseNotificationData = createBaseNotificationData(
lockScreenNotificationData = LockScreenNotificationData.SenderNames("Alice, Bob"),
newMessagesCount = 2
)
stubbing(notificationData) {
on { newMessagesCount } doReturn 1
on { holderForLatestNotification } doReturn holder
}
notificationCreator.configureLockScreenNotification(builder, notificationData)
notificationCreator.configureLockScreenNotification(builder, baseNotificationData)
verify(publicBuilder).setSmallIcon(resourceProvider.iconNewMail)
verify(publicBuilder).setNumber(1)
verify(publicBuilder).setContentTitle("1 new message")
verify(publicBuilder).setContentText("alice@example.com")
verify(publicBuilder).setNumber(2)
verify(publicBuilder).setContentTitle("2 new messages")
verify(publicBuilder).setContentText("Alice, Bob")
verify(builder).setPublicVersion(publicBuilder.build())
}
@Test
fun configureLockScreenNotification_SENDERS_withMultipleMessages() {
K9.lockScreenNotificationVisibility = LockScreenNotificationVisibility.SENDERS
val content1 = createNotificationContent("alice@example.com")
val content2 = createNotificationContent("Bob <bob@example.com>")
val content3 = createNotificationContent("\"Peter Lustig\" <peter@example.com>")
stubbing(notificationData) {
on { newMessagesCount } doReturn NEW_MESSAGE_COUNT
on { getContentForSummaryNotification() } doReturn listOf(content1, content2, content3)
}
fun `new message count`() {
val baseNotificationData = createBaseNotificationData(
lockScreenNotificationData = LockScreenNotificationData.MessageCount,
accountName = "Account name",
newMessagesCount = 23
)
notificationCreator.configureLockScreenNotification(builder, notificationData)
notificationCreator.configureLockScreenNotification(builder, baseNotificationData)
verify(publicBuilder).setSmallIcon(resourceProvider.iconNewMail)
verify(publicBuilder).setNumber(NEW_MESSAGE_COUNT)
verify(publicBuilder).setContentTitle("$NEW_MESSAGE_COUNT new messages")
verify(publicBuilder).setContentText(
"alice@example.com, Bob <bob@example.com>, \"Peter Lustig\" <peter@example.com>"
)
verify(publicBuilder).setNumber(23)
verify(publicBuilder).setContentTitle("23 new messages")
verify(publicBuilder).setContentText("Account name")
verify(builder).setPublicVersion(publicBuilder.build())
}
@Test
fun configureLockScreenNotification_SENDERS_makeSureWeGetEnoughSenderNames() {
assertThat(
NotificationData.MAX_NUMBER_OF_MESSAGES_FOR_SUMMARY_NOTIFICATION >=
LockScreenNotificationCreator.MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION
).isTrue()
}
@Test
fun createCommaSeparatedListOfSenders_withMoreSendersThanShouldBeDisplayed() {
val content1 = createNotificationContent("alice@example.com")
val content2 = createNotificationContent("bob@example.com")
val content3 = createNotificationContent("cloe@example.com")
val content4 = createNotificationContent("dagobert@example.com")
val content5 = createNotificationContent("ed@example.com")
val content6 = createNotificationContent("fiona@example.com")
val result = notificationCreator.createCommaSeparatedListOfSenders(
listOf(content1, content2, content3, content4, content5, content6)
)
assertThat(result).isEqualTo(
"alice@example.com, bob@example.com, cloe@example.com, dagobert@example.com, ed@example.com"
)
}
@Test
fun configureLockScreenNotification_MESSAGE_COUNT() {
K9.lockScreenNotificationVisibility = LockScreenNotificationVisibility.MESSAGE_COUNT
stubbing(notificationData) {
on { newMessagesCount } doReturn NEW_MESSAGE_COUNT
}
notificationCreator.configureLockScreenNotification(builder, notificationData)
verify(publicBuilder).setSmallIcon(resourceProvider.iconNewMail)
verify(publicBuilder).setNumber(NEW_MESSAGE_COUNT)
verify(publicBuilder).setContentTitle("$NEW_MESSAGE_COUNT new messages")
verify(publicBuilder).setContentText(ACCOUNT_NAME)
verify(builder).setPublicVersion(publicBuilder.build())
}
private fun createFakeAccount(): Account {
return mock {
on { description } doReturn ACCOUNT_NAME
}
}
private fun createFakeNotificationBuilder(): NotificationCompat.Builder {
return mockBuilder {
on { build() } doReturn mock()
@ -156,26 +90,27 @@ class LockScreenNotificationCreatorTest : RobolectricTest() {
private fun createFakeNotificationHelper(builder: NotificationCompat.Builder): NotificationHelper {
return mock {
on { getContext() } doReturn ApplicationProvider.getApplicationContext()
on { getAccountName(any()) } doReturn ACCOUNT_NAME
on { createNotificationBuilder(any(), any()) } doReturn builder
}
}
private fun createFakeNotificationData(): NotificationData {
val fakeAccount = createFakeAccount()
return mock {
on { account } doReturn fakeAccount
}
}
private fun createNotificationContent(sender: String): NotificationContent {
val messageReference = MessageReference(accountUuid = "irrelevant", folderId = 1, uid = "irrelevant")
return NotificationContent(
messageReference = messageReference,
sender = sender,
subject = "irrelevant",
preview = "irrelevant",
summary = "irrelevant"
private fun createBaseNotificationData(
lockScreenNotificationData: LockScreenNotificationData,
accountName: String = "irrelevant",
newMessagesCount: Int = 0
): BaseNotificationData {
return BaseNotificationData(
account = account,
accountName = accountName,
groupKey = "irrelevant",
color = 0,
newMessagesCount = newMessagesCount,
lockScreenNotificationData = lockScreenNotificationData,
appearance = NotificationAppearance(
ringtone = null,
vibrationPattern = longArrayOf(),
ledColor = 0
)
)
}
}

View file

@ -1,342 +0,0 @@
package com.fsck.k9.notification
import android.app.Notification
import androidx.core.app.NotificationManagerCompat
import com.fsck.k9.Account
import com.fsck.k9.K9RobolectricTest
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.mailstore.LocalMessage
import org.junit.Test
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.stubbing
import org.mockito.kotlin.whenever
private const val ACCOUNT_NUMBER = 23
class NewMailNotificationControllerTest : K9RobolectricTest() {
private val account = createAccount()
private val notificationManager = createNotificationManager()
private val contentCreator = createNotificationContentCreator()
private val summaryNotificationCreator = createSummaryNotificationCreator()
private val singleMessageNotificationCreator = createSingleMessageNotificationCreator()
private val controller = TestNewMailNotificationController(
notificationHelper = createNotificationHelper(notificationManager),
contentCreator = contentCreator,
summaryNotificationCreator = summaryNotificationCreator,
singleMessageNotificationCreator = singleMessageNotificationCreator
)
@Test
fun testAddNewMailNotification() {
val notificationIndex = 0
val message = createLocalMessage()
val content = createNotificationContent()
val holder = createNotificationHolder(content, notificationIndex)
addToNotificationContentCreator(message, content)
whenAddingContentReturn(content, AddNotificationResult.newNotification(holder))
val singleMessageNotification = createNotification()
val summaryNotification = createNotification()
addToSingleMessageNotificationCreator(holder, singleMessageNotification)
addToSummaryNotificationCreator(summaryNotification)
controller.addNewMailNotification(account, message, silent = false)
val singleMessageNotificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
verify(notificationManager).notify(singleMessageNotificationId, singleMessageNotification)
val summaryNotificationId = NotificationIds.getNewMailSummaryNotificationId(account)
verify(notificationManager).notify(summaryNotificationId, summaryNotification)
}
@Test
fun testAddNewMailNotificationWithCancelingExistingNotification() {
val notificationIndex = 0
val message = createLocalMessage()
val content = createNotificationContent()
val holder = createNotificationHolder(content, notificationIndex)
addToNotificationContentCreator(message, content)
whenAddingContentReturn(content, AddNotificationResult.replaceNotification(holder))
val singleMessageNotification = createNotification()
val summaryNotification = createNotification()
addToSingleMessageNotificationCreator(holder, singleMessageNotification)
addToSummaryNotificationCreator(summaryNotification)
controller.addNewMailNotification(account, message, silent = false)
val singleMessageNotificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
verify(notificationManager).notify(singleMessageNotificationId, singleMessageNotification)
verify(notificationManager).cancel(singleMessageNotificationId)
val summaryNotificationId = NotificationIds.getNewMailSummaryNotificationId(account)
verify(notificationManager).notify(summaryNotificationId, summaryNotification)
}
@Test
fun testAddNewMailNotificationTwice() {
val notificationIndexOne = 0
val notificationIndexTwo = 1
val messageOne = createLocalMessage()
val messageTwo = createLocalMessage()
val contentOne = createNotificationContent()
val contentTwo = createNotificationContent()
val holderOne = createNotificationHolder(contentOne, notificationIndexOne)
val holderTwo = createNotificationHolder(contentTwo, notificationIndexTwo)
addToNotificationContentCreator(messageOne, contentOne)
addToNotificationContentCreator(messageTwo, contentTwo)
whenAddingContentReturn(contentOne, AddNotificationResult.newNotification(holderOne))
whenAddingContentReturn(contentTwo, AddNotificationResult.newNotification(holderTwo))
val singleMessageNotificationOne = createNotification()
val singleMessageNotificationTwo = createNotification()
val summaryNotification = createNotification()
addToSingleMessageNotificationCreator(holderOne, singleMessageNotificationOne)
addToSingleMessageNotificationCreator(holderTwo, singleMessageNotificationTwo)
addToSummaryNotificationCreator(summaryNotification)
controller.addNewMailNotification(account, messageOne, silent = false)
controller.addNewMailNotification(account, messageTwo, silent = false)
val singleMessageNotificationIdOne = NotificationIds.getSingleMessageNotificationId(account, notificationIndexOne)
verify(notificationManager).notify(singleMessageNotificationIdOne, singleMessageNotificationOne)
val singleMessageNotificationIdTwo = NotificationIds.getSingleMessageNotificationId(account, notificationIndexTwo)
verify(notificationManager).notify(singleMessageNotificationIdTwo, singleMessageNotificationTwo)
val summaryNotificationId = NotificationIds.getNewMailSummaryNotificationId(account)
verify(notificationManager, times(2)).notify(summaryNotificationId, summaryNotification)
}
@Test
fun testRemoveNewMailNotificationWithoutNotificationData() {
val messageReference = createMessageReference(1)
controller.removeNewMailNotification(account, messageReference)
verify(notificationManager, never()).cancel(anyInt())
}
@Test
fun testRemoveNewMailNotificationWithUnknownMessageReference() {
val messageReference = createMessageReference(1)
val notificationIndex = 0
val message = createLocalMessage()
val content = createNotificationContent()
val holder = createNotificationHolder(content, notificationIndex)
addToNotificationContentCreator(message, content)
whenAddingContentReturn(content, AddNotificationResult.newNotification(holder))
val summaryNotification = createNotification()
addToSummaryNotificationCreator(summaryNotification)
controller.addNewMailNotification(account, message, silent = false)
whenRemovingContentReturn(messageReference, RemoveNotificationResult.unknownNotification())
controller.removeNewMailNotification(account, messageReference)
verify(notificationManager, never()).cancel(anyInt())
}
@Test
fun testRemoveNewMailNotification() {
val messageReference = createMessageReference(1)
val notificationIndex = 0
val notificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
val message = createLocalMessage()
val content = createNotificationContent()
val holder = createNotificationHolder(content, notificationIndex)
addToNotificationContentCreator(message, content)
whenAddingContentReturn(content, AddNotificationResult.newNotification(holder))
val summaryNotification = createNotification()
addToSummaryNotificationCreator(summaryNotification)
controller.addNewMailNotification(account, message, silent = false)
whenRemovingContentReturn(messageReference, RemoveNotificationResult.cancelNotification(notificationId))
controller.removeNewMailNotification(account, messageReference)
verify(notificationManager).cancel(notificationId)
val summaryNotificationId = NotificationIds.getNewMailSummaryNotificationId(account)
verify(notificationManager, times(2)).notify(summaryNotificationId, summaryNotification)
}
@Test
fun testRemoveNewMailNotificationClearingAllNotifications() {
val messageReference = createMessageReference(1)
val notificationIndex = 0
val notificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
val message = createLocalMessage()
val content = createNotificationContent()
val holder = createNotificationHolder(content, notificationIndex)
addToNotificationContentCreator(message, content)
whenAddingContentReturn(content, AddNotificationResult.newNotification(holder))
val summaryNotification = createNotification()
addToSummaryNotificationCreator(summaryNotification)
controller.addNewMailNotification(account, message, silent = false)
whenRemovingContentReturn(messageReference, RemoveNotificationResult.cancelNotification(notificationId))
whenever(controller.notificationData.newMessagesCount).thenReturn(0)
setActiveNotificationIds()
controller.removeNewMailNotification(account, messageReference)
verify(notificationManager).cancel(notificationId)
val summaryNotificationId = NotificationIds.getNewMailSummaryNotificationId(account)
verify(notificationManager).cancel(summaryNotificationId)
}
@Test
fun testRemoveNewMailNotificationWithCreateNotification() {
val messageReference = createMessageReference(1)
val notificationIndex = 0
val notificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
val message = createLocalMessage()
val contentOne = createNotificationContent()
val contentTwo = createNotificationContent()
val holderOne = createNotificationHolder(contentOne, notificationIndex)
val holderTwo = createNotificationHolder(contentTwo, notificationIndex)
addToNotificationContentCreator(message, contentOne)
whenAddingContentReturn(contentOne, AddNotificationResult.newNotification(holderOne))
val summaryNotification = createNotification()
addToSummaryNotificationCreator(summaryNotification)
val singleMessageNotificationOne = createNotification()
val singleMessageNotificationTwo = createNotification()
addToSingleMessageNotificationCreator(holderOne, singleMessageNotificationOne)
addToSingleMessageNotificationCreator(holderTwo, singleMessageNotificationTwo)
controller.addNewMailNotification(account, message, silent = false)
whenRemovingContentReturn(messageReference, RemoveNotificationResult.createNotification(holderTwo))
controller.removeNewMailNotification(account, messageReference)
verify(notificationManager).cancel(notificationId)
verify(notificationManager).notify(notificationId, singleMessageNotificationTwo)
val summaryNotificationId = NotificationIds.getNewMailSummaryNotificationId(account)
verify(notificationManager, times(2)).notify(summaryNotificationId, summaryNotification)
}
@Test
fun testClearNewMailNotificationsWithoutNotificationData() {
controller.clearNewMailNotifications(account)
verify(notificationManager, never()).cancel(anyInt())
}
@Test
fun testClearNewMailNotifications() {
val notificationIndex = 0
val notificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
val message = createLocalMessage()
val content = createNotificationContent()
val holder = createNotificationHolder(content, notificationIndex)
addToNotificationContentCreator(message, content)
setActiveNotificationIds(notificationId)
whenAddingContentReturn(content, AddNotificationResult.newNotification(holder))
controller.addNewMailNotification(account, message, silent = false)
controller.clearNewMailNotifications(account)
verify(notificationManager).cancel(notificationId)
verify(notificationManager).cancel(NotificationIds.getNewMailSummaryNotificationId(account))
}
private fun createAccount(): Account {
return mock {
on { accountNumber } doReturn ACCOUNT_NUMBER
}
}
private fun createLocalMessage(): LocalMessage = mock()
private fun createNotificationContent(): NotificationContent {
val messageReference = MessageReference(accountUuid = "irrelevant", folderId = 1, uid = "irrelevant")
return NotificationContent(
messageReference = messageReference,
sender = "irrelevant",
subject = "irrelevant",
preview = "irrelevant",
summary = "irrelevant"
)
}
private fun createNotificationHolder(content: NotificationContent, index: Int): NotificationHolder {
val notificationId = NotificationIds.getSingleMessageNotificationId(account, index)
return NotificationHolder(notificationId, content)
}
private fun createNotificationManager(): NotificationManagerCompat = mock()
private fun createNotificationHelper(notificationManager: NotificationManagerCompat): NotificationHelper {
return mock {
on { getNotificationManager() } doReturn notificationManager
}
}
private fun createNotificationContentCreator(): NotificationContentCreator = mock()
private fun addToNotificationContentCreator(message: LocalMessage, content: NotificationContent) {
stubbing(contentCreator) {
on { createFromMessage(account, message) } doReturn content
}
}
private fun createSummaryNotificationCreator(): SummaryNotificationCreator = mock()
private fun addToSummaryNotificationCreator(notificationToReturn: Notification) {
stubbing(summaryNotificationCreator) {
on {
buildSummaryNotification(eq(account), eq(controller.notificationData), anyBoolean())
} doReturn notificationToReturn
}
}
private fun createNotification(): Notification = mock()
private fun createSingleMessageNotificationCreator(): SingleMessageNotificationCreator = mock()
private fun createMessageReference(number: Int): MessageReference {
return MessageReference("account", 1, number.toString())
}
private fun addToSingleMessageNotificationCreator(
notificationHolder: NotificationHolder,
notificationToReturn: Notification
) {
stubbing(singleMessageNotificationCreator) {
on { buildSingleMessageNotification(account, notificationHolder) } doReturn notificationToReturn
}
}
private fun whenAddingContentReturn(content: NotificationContent, result: AddNotificationResult) {
val notificationData = controller.notificationData
val newCount = notificationData.newMessagesCount + 1
stubbing(notificationData) {
on { addNotificationContent(content) } doReturn result
on { newMessagesCount } doReturn newCount
}
}
private fun whenRemovingContentReturn(messageReference: MessageReference, result: RemoveNotificationResult) {
stubbing(controller.notificationData) {
on { removeNotificationForMessage(messageReference) } doReturn result
}
}
private fun setActiveNotificationIds(vararg notificationIds: Int) {
stubbing(controller.notificationData) {
on { getActiveNotificationIds() } doReturn notificationIds
}
}
internal class TestNewMailNotificationController(
notificationHelper: NotificationHelper,
contentCreator: NotificationContentCreator,
summaryNotificationCreator: SummaryNotificationCreator,
singleMessageNotificationCreator: SingleMessageNotificationCreator
) : NewMailNotificationController(
notificationHelper, contentCreator, summaryNotificationCreator, singleMessageNotificationCreator
) {
val notificationData = mock<NotificationData>()
override fun createNotificationData(account: Account): NotificationData {
return notificationData
}
}
}

View file

@ -0,0 +1,291 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.TestClock
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.mailstore.LocalMessage
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertNotNull
import org.junit.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stubbing
private const val ACCOUNT_UUID = "00000000-0000-4000-0000-000000000000"
private const val ACCOUNT_NAME = "Personal"
private const val ACCOUNT_COLOR = 0xFF112233L.toInt()
private const val FOLDER_ID = 42L
private const val TIMESTAMP = 23L
class NewMailNotificationManagerTest {
private val account = createAccount()
private val notificationContentCreator = mock<NotificationContentCreator>()
private val clock = TestClock(TIMESTAMP)
private val manager = NewMailNotificationManager(
notificationContentCreator,
BaseNotificationDataCreator(),
SingleMessageNotificationDataCreator(),
SummaryNotificationDataCreator(SingleMessageNotificationDataCreator()),
clock
)
@Test
fun `add first notification`() {
val message = addMessageToNotificationContentCreator(
sender = "sender",
subject = "subject",
preview = "preview",
summary = "summary",
messageUid = "msg-1"
)
val result = manager.addNewMailNotification(account, message, silent = false)
assertThat(result.singleNotificationData.first().content).isEqualTo(
NotificationContent(
messageReference = createMessageReference("msg-1"),
sender = "sender",
subject = "subject",
preview = "preview",
summary = "summary"
)
)
assertThat(result.summaryNotificationData).isInstanceOf(SummarySingleNotificationData::class.java)
val summaryNotificationData = result.summaryNotificationData as SummarySingleNotificationData
assertThat(summaryNotificationData.singleNotificationData.isSilent).isFalse()
}
@Test
fun `add second notification`() {
val messageOne = addMessageToNotificationContentCreator(
sender = "Alice",
subject = "Hi Bob",
preview = "How are you?",
summary = "Alice Hi Bob",
messageUid = "msg-1"
)
val messageTwo = addMessageToNotificationContentCreator(
sender = "Zoe",
subject = "Meeting",
preview = "We need to talk",
summary = "Zoe Meeting",
messageUid = "msg-2"
)
manager.addNewMailNotification(account, messageOne, silent = false)
val timestamp = TIMESTAMP + 1000
clock.time = timestamp
val result = manager.addNewMailNotification(account, messageTwo, silent = false)
assertThat(result.singleNotificationData.first().content).isEqualTo(
NotificationContent(
messageReference = createMessageReference("msg-2"),
sender = "Zoe",
subject = "Meeting",
preview = "We need to talk",
summary = "Zoe Meeting"
)
)
assertThat(result.baseNotificationData.newMessagesCount).isEqualTo(2)
assertThat(result.summaryNotificationData).isInstanceOf(SummaryInboxNotificationData::class.java)
val summaryNotificationData = result.summaryNotificationData as SummaryInboxNotificationData
assertThat(summaryNotificationData.content).isEqualTo(listOf("Zoe Meeting", "Alice Hi Bob"))
assertThat(summaryNotificationData.messageReferences).isEqualTo(
listOf(
createMessageReference("msg-2"),
createMessageReference("msg-1")
)
)
assertThat(summaryNotificationData.additionalMessagesCount).isEqualTo(0)
assertThat(summaryNotificationData.isSilent).isFalse()
}
@Test
fun `add one more notification when already displaying the maximum number of notifications`() {
addMaximumNumberOfNotifications()
val message = addMessageToNotificationContentCreator(
sender = "Alice",
subject = "Another one",
preview = "Are you tired of me yet?",
summary = "Alice Another one",
messageUid = "msg-x"
)
val result = manager.addNewMailNotification(account, message, silent = false)
val notificationId = NotificationIds.getSingleMessageNotificationId(account, index = 0)
assertThat(result.cancelNotificationIds).isEqualTo(listOf(notificationId))
assertThat(result.singleNotificationData.first().notificationId).isEqualTo(notificationId)
}
@Test
fun `remove notification when none was added before should return null`() {
val result = manager.removeNewMailNotification(account, createMessageReference("any"))
assertThat(result).isNull()
}
@Test
fun `remove notification with untracked notification ID should return null`() {
val message = addMessageToNotificationContentCreator(
sender = "Alice",
subject = "Another one",
preview = "Are you tired of me yet?",
summary = "Alice Another one",
messageUid = "msg-x"
)
manager.addNewMailNotification(account, message, silent = false)
val result = manager.removeNewMailNotification(account, createMessageReference("untracked"))
assertThat(result).isNull()
}
@Test
fun `remove last remaining notification`() {
val message = addMessageToNotificationContentCreator(
sender = "Alice",
subject = "Hello",
preview = "How are you?",
summary = "Alice Hello",
messageUid = "msg-1"
)
manager.addNewMailNotification(account, message, silent = false)
val result = manager.removeNewMailNotification(account, createMessageReference("msg-1"))
assertNotNull(result) { data ->
assertThat(data.cancelNotificationIds).containsExactly(
NotificationIds.getNewMailSummaryNotificationId(account),
NotificationIds.getSingleMessageNotificationId(account, 0)
)
assertThat(data.singleNotificationData).isEmpty()
assertThat(data.summaryNotificationData).isNull()
}
}
@Test
fun `remove one of three notifications`() {
val messageOne = addMessageToNotificationContentCreator(
sender = "Alice",
subject = "One",
preview = "preview",
summary = "Alice One",
messageUid = "msg-1"
)
manager.addNewMailNotification(account, messageOne, silent = false)
val messageTwo = addMessageToNotificationContentCreator(
sender = "Alice",
subject = "Two",
preview = "preview",
summary = "Alice Two",
messageUid = "msg-2"
)
val dataTwo = manager.addNewMailNotification(account, messageTwo, silent = true)
val notificationIdTwo = dataTwo.singleNotificationData.first().notificationId
val messageThree = addMessageToNotificationContentCreator(
sender = "Alice",
subject = "Three",
preview = "preview",
summary = "Alice Three",
messageUid = "msg-3"
)
manager.addNewMailNotification(account, messageThree, silent = true)
val result = manager.removeNewMailNotification(account, createMessageReference("msg-2"))
assertNotNull(result) { data ->
assertThat(data.cancelNotificationIds).isEqualTo(listOf(notificationIdTwo))
assertThat(data.singleNotificationData).isEmpty()
assertThat(data.baseNotificationData.newMessagesCount).isEqualTo(2)
assertThat(data.summaryNotificationData).isInstanceOf(SummaryInboxNotificationData::class.java)
val summaryNotificationData = data.summaryNotificationData as SummaryInboxNotificationData
assertThat(summaryNotificationData.content).isEqualTo(listOf("Alice Three", "Alice One"))
assertThat(summaryNotificationData.messageReferences).isEqualTo(
listOf(
createMessageReference("msg-3"),
createMessageReference("msg-1")
)
)
}
}
@Test
fun `remove notification when additional notifications are available`() {
val message = addMessageToNotificationContentCreator(
sender = "Alice",
subject = "Another one",
preview = "Are you tired of me yet?",
summary = "Alice Another one",
messageUid = "msg-restore"
)
manager.addNewMailNotification(account, message, silent = false)
addMaximumNumberOfNotifications()
val result = manager.removeNewMailNotification(account, createMessageReference("msg-1"))
assertNotNull(result) { data ->
assertThat(data.cancelNotificationIds).hasSize(1)
assertThat(data.baseNotificationData.newMessagesCount)
.isEqualTo(NotificationData.MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS)
val singleNotificationData = data.singleNotificationData.first()
assertThat(singleNotificationData.notificationId).isEqualTo(data.cancelNotificationIds.first())
assertThat(singleNotificationData.isSilent).isTrue()
assertThat(singleNotificationData.content).isEqualTo(
NotificationContent(
messageReference = createMessageReference("msg-restore"),
sender = "Alice",
subject = "Another one",
preview = "Are you tired of me yet?",
summary = "Alice Another one"
)
)
}
}
private fun createAccount(): Account {
return Account(ACCOUNT_UUID).apply {
description = ACCOUNT_NAME
chipColor = ACCOUNT_COLOR
notificationSetting.vibrateTimes = 1
}
}
private fun addMaximumNumberOfNotifications() {
repeat(NotificationData.MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS) { index ->
val message = addMessageToNotificationContentCreator(
sender = "sender",
subject = "subject",
preview = "preview",
summary = "summary",
messageUid = "msg-$index"
)
manager.addNewMailNotification(account, message, silent = true)
}
}
private fun addMessageToNotificationContentCreator(
sender: String,
subject: String,
preview: String,
summary: String,
messageUid: String
): LocalMessage {
val message = mock<LocalMessage>()
stubbing(notificationContentCreator) {
on { createFromMessage(account, message) } doReturn
NotificationContent(
messageReference = createMessageReference(messageUid),
sender, subject, preview, summary
)
}
return message
}
private fun createMessageReference(messageUid: String): MessageReference {
return MessageReference(ACCOUNT_UUID, FOLDER_ID, messageUid)
}
}

View file

@ -1,240 +0,0 @@
package com.fsck.k9.notification
import android.app.Notification
import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.test.core.app.ApplicationProvider
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.K9.NotificationQuickDelete
import com.fsck.k9.RobolectricTest
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.testing.MockHelper.mockBuilder
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.ArgumentMatcher
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
private const val ACCOUNT_NUMBER = 42
private const val ACCOUNT_NAME = "accountName"
class SingleMessageNotificationCreatorTest : RobolectricTest() {
private val resourceProvider: NotificationResourceProvider = TestNotificationResourceProvider()
private val account = createAccount()
private val notification = mock<Notification>()
private val builder = createNotificationBuilder(notification)
private val actionCreator = mock<NotificationActionCreator>()
private val notificationCreator = SingleMessageNotificationCreator(
notificationHelper = createNotificationHelper(builder),
actionCreator = actionCreator,
resourceProvider = resourceProvider,
lockScreenNotificationCreator = mock()
)
@Test
fun testBuildStackedNotification() {
disableOptionalActions()
val notificationIndex = 0
val notificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
val messageReference = createMessageReference(1)
val content = createNotificationContent(messageReference)
val holder = createNotificationHolder(notificationId, content)
val replyPendingIntent = createFakePendingIntent(1)
val markAsReadPendingIntent = createFakePendingIntent(2)
whenever(
actionCreator.createReplyPendingIntent(
messageReference,
notificationId
)
).thenReturn(replyPendingIntent)
whenever(actionCreator.createMarkMessageAsReadPendingIntent(messageReference, notificationId)).thenReturn(
markAsReadPendingIntent
)
val result = notificationCreator.buildSingleMessageNotification(account, holder)
assertThat(result).isEqualTo(notification)
verifyExtendWasOnlyCalledOnce()
verifyAddAction(resourceProvider.wearIconReplyAll, "Reply", replyPendingIntent)
verifyAddAction(resourceProvider.wearIconMarkAsRead, "Mark Read", markAsReadPendingIntent)
verifyNumberOfActions(2)
}
@Test
fun testBuildStackedNotificationWithDeleteActionEnabled() {
enableDeleteAction()
val notificationIndex = 0
val notificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
val messageReference = createMessageReference(1)
val content = createNotificationContent(messageReference)
val holder = createNotificationHolder(notificationId, content)
val deletePendingIntent = createFakePendingIntent(1)
whenever(actionCreator.createDeleteMessagePendingIntent(messageReference, notificationId)).thenReturn(
deletePendingIntent
)
val result = notificationCreator.buildSingleMessageNotification(account, holder)
assertThat(result).isEqualTo(notification)
verifyExtendWasOnlyCalledOnce()
verifyAddAction(resourceProvider.wearIconDelete, "Delete", deletePendingIntent)
}
@Test
fun testBuildStackedNotificationWithArchiveActionEnabled() {
enableArchiveAction()
val notificationIndex = 0
val notificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
val messageReference = createMessageReference(1)
val content = createNotificationContent(messageReference)
val holder = createNotificationHolder(notificationId, content)
val archivePendingIntent = createFakePendingIntent(1)
whenever(actionCreator.createArchiveMessagePendingIntent(messageReference, notificationId)).thenReturn(
archivePendingIntent
)
val result = notificationCreator.buildSingleMessageNotification(account, holder)
assertThat(result).isEqualTo(notification)
verifyExtendWasOnlyCalledOnce()
verifyAddAction(resourceProvider.wearIconArchive, "Archive", archivePendingIntent)
}
@Test
fun testBuildStackedNotificationWithMarkAsSpamActionEnabled() {
enableSpamAction()
val notificationIndex = 0
val notificationId = NotificationIds.getSingleMessageNotificationId(account, notificationIndex)
val messageReference = createMessageReference(1)
val content = createNotificationContent(messageReference)
val holder = createNotificationHolder(notificationId, content)
val markAsSpamPendingIntent = createFakePendingIntent(1)
whenever(actionCreator.createMarkMessageAsSpamPendingIntent(messageReference, notificationId)).thenReturn(
markAsSpamPendingIntent
)
val result = notificationCreator.buildSingleMessageNotification(account, holder)
assertThat(result).isEqualTo(notification)
verifyExtendWasOnlyCalledOnce()
verifyAddAction(resourceProvider.wearIconMarkAsSpam, "Spam", markAsSpamPendingIntent)
}
private fun disableOptionalActions() {
disableDeleteAction()
disableArchiveAction()
disableSpamAction()
}
private fun disableDeleteAction() {
K9.notificationQuickDeleteBehaviour = NotificationQuickDelete.NEVER
}
private fun disableArchiveAction() {
whenever(account.archiveFolderId).thenReturn(null)
}
private fun disableSpamAction() {
whenever(account.spamFolderId).thenReturn(null)
}
private fun enableDeleteAction() {
K9.notificationQuickDeleteBehaviour = NotificationQuickDelete.ALWAYS
K9.isConfirmDeleteFromNotification = false
}
private fun enableArchiveAction() {
whenever(account.archiveFolderId).thenReturn(22L)
}
private fun enableSpamAction() {
whenever(account.spamFolderId).thenReturn(11L)
}
private fun createNotificationBuilder(notification: Notification): NotificationCompat.Builder {
return mockBuilder {
on { build() } doReturn notification
}
}
private fun createNotificationHelper(builder: NotificationCompat.Builder): NotificationHelper {
return mock {
on { createNotificationBuilder(any(), any()) } doReturn builder
on { getAccountName(account) } doReturn ACCOUNT_NAME
on { getContext() } doReturn ApplicationProvider.getApplicationContext()
}
}
private fun createAccount(): Account {
return mock {
on { accountNumber } doReturn ACCOUNT_NUMBER
}
}
private fun createNotificationContent(messageReference: MessageReference): NotificationContent {
return NotificationContent(
messageReference = messageReference,
sender = "irrelevant",
subject = "irrelevant",
preview = "irrelevant",
summary = "irrelevant"
)
}
private fun createNotificationHolder(notificationId: Int, content: NotificationContent): NotificationHolder {
return NotificationHolder(notificationId, content)
}
private fun createMessageReference(number: Int): MessageReference {
return MessageReference("account", 1, number.toString())
}
private fun createFakePendingIntent(requestCode: Int): PendingIntent {
return PendingIntent.getActivity(ApplicationProvider.getApplicationContext(), requestCode, null, 0)
}
private fun verifyExtendWasOnlyCalledOnce() {
verify(builder, times(1)).extend(any())
}
private fun verifyAddAction(icon: Int, title: String, pendingIntent: PendingIntent) {
verify(builder).extend(action(icon, title, pendingIntent))
}
private fun verifyNumberOfActions(expectedNumberOfActions: Int) {
verify(builder).extend(numberOfActions(expectedNumberOfActions))
}
private fun action(icon: Int, title: String, pendingIntent: PendingIntent): NotificationCompat.WearableExtender {
return argThat(ActionMatcher(icon, title, pendingIntent))
}
private fun numberOfActions(expectedNumberOfActions: Int): NotificationCompat.WearableExtender {
return argThat(NumberOfActionsMatcher(expectedNumberOfActions))
}
internal class ActionMatcher(
private val icon: Int,
private val title: String,
private val pendingIntent: PendingIntent
) : ArgumentMatcher<NotificationCompat.WearableExtender> {
override fun matches(argument: NotificationCompat.WearableExtender): Boolean {
return argument.actions.any { action ->
action.icon == icon && action.title == title && action.actionIntent === pendingIntent
}
}
}
internal class NumberOfActionsMatcher(private val expectedNumberOfActions: Int) :
ArgumentMatcher<NotificationCompat.WearableExtender> {
override fun matches(argument: NotificationCompat.WearableExtender): Boolean {
return argument.actions.size == expectedNumberOfActions
}
}
}

View file

@ -0,0 +1,274 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.K9.NotificationQuickDelete
import com.fsck.k9.controller.MessageReference
import com.google.common.truth.Truth.assertThat
import org.junit.Test
class SingleMessageNotificationDataCreatorTest {
private val account = createAccount()
private val notificationDataCreator = SingleMessageNotificationDataCreator()
@Test
fun `base properties`() {
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 23,
content = content,
timestamp = 9000,
addLockScreenNotification = true
)
assertThat(result.notificationId).isEqualTo(23)
assertThat(result.isSilent).isTrue()
assertThat(result.timestamp).isEqualTo(9000)
assertThat(result.content).isEqualTo(content)
assertThat(result.addLockScreenNotification).isTrue()
}
@Test
fun `summary notification base properties`() {
val content = createNotificationContent()
val notificationData = createNotificationData(content)
val result = notificationDataCreator.createSummarySingleNotificationData(
timestamp = 9000,
silent = false,
data = notificationData
)
assertThat(result.singleNotificationData.notificationId).isEqualTo(
NotificationIds.getNewMailSummaryNotificationId(account)
)
assertThat(result.singleNotificationData.isSilent).isFalse()
assertThat(result.singleNotificationData.timestamp).isEqualTo(9000)
assertThat(result.singleNotificationData.content).isEqualTo(content)
assertThat(result.singleNotificationData.addLockScreenNotification).isFalse()
}
@Test
fun `default actions`() {
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.actions).contains(NotificationAction.Reply)
assertThat(result.actions).contains(NotificationAction.MarkAsRead)
assertThat(result.wearActions).contains(WearNotificationAction.Reply)
assertThat(result.wearActions).contains(WearNotificationAction.MarkAsRead)
}
@Test
fun `always show delete action without confirmation`() {
setDeleteAction(NotificationQuickDelete.ALWAYS)
setConfirmDeleteFromNotification(false)
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.actions).contains(NotificationAction.Delete)
assertThat(result.wearActions).contains(WearNotificationAction.Delete)
}
@Test
fun `always show delete action with confirmation`() {
setDeleteAction(NotificationQuickDelete.ALWAYS)
setConfirmDeleteFromNotification(true)
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.actions).contains(NotificationAction.Delete)
assertThat(result.wearActions).doesNotContain(WearNotificationAction.Delete)
}
@Test
fun `show delete action for single notification without confirmation`() {
setDeleteAction(NotificationQuickDelete.FOR_SINGLE_MSG)
setConfirmDeleteFromNotification(false)
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.actions).contains(NotificationAction.Delete)
assertThat(result.wearActions).contains(WearNotificationAction.Delete)
}
@Test
fun `show delete action for single notification with confirmation`() {
setDeleteAction(NotificationQuickDelete.FOR_SINGLE_MSG)
setConfirmDeleteFromNotification(true)
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.actions).contains(NotificationAction.Delete)
assertThat(result.wearActions).doesNotContain(WearNotificationAction.Delete)
}
@Test
fun `never show delete action`() {
setDeleteAction(NotificationQuickDelete.NEVER)
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.actions).doesNotContain(NotificationAction.Delete)
assertThat(result.wearActions).doesNotContain(WearNotificationAction.Delete)
}
@Test
fun `archive action with archive folder`() {
account.archiveFolderId = 1
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.wearActions).contains(WearNotificationAction.Archive)
}
@Test
fun `archive action without archive folder`() {
account.archiveFolderId = null
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.wearActions).doesNotContain(WearNotificationAction.Archive)
}
@Test
fun `spam action with spam folder and without spam confirmation`() {
account.spamFolderId = 1
setConfirmSpam(false)
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.wearActions).contains(WearNotificationAction.Spam)
}
@Test
fun `spam action with spam folder and with spam confirmation`() {
account.spamFolderId = 1
setConfirmSpam(true)
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.wearActions).doesNotContain(WearNotificationAction.Spam)
}
@Test
fun `spam action without spam folder and without spam confirmation`() {
account.spamFolderId = null
setConfirmSpam(false)
val content = createNotificationContent()
val result = notificationDataCreator.createSingleNotificationData(
account = account,
notificationId = 0,
content = content,
timestamp = 0,
addLockScreenNotification = false
)
assertThat(result.wearActions).doesNotContain(WearNotificationAction.Spam)
}
private fun setDeleteAction(mode: NotificationQuickDelete) {
K9.notificationQuickDeleteBehaviour = mode
}
private fun setConfirmDeleteFromNotification(confirm: Boolean) {
K9.isConfirmDeleteFromNotification = confirm
}
private fun setConfirmSpam(confirm: Boolean) {
K9.isConfirmSpam = confirm
}
private fun createAccount(): Account {
return Account("00000000-0000-0000-0000-000000000000").apply {
accountNumber = 42
}
}
private fun createNotificationContent() = NotificationContent(
messageReference = MessageReference("irrelevant", 1, "irrelevant"),
sender = "irrelevant",
subject = "irrelevant",
preview = "irrelevant",
summary = "irrelevant"
)
private fun createNotificationData(content: NotificationContent): NotificationData {
return NotificationData(account).apply {
addNotificationContent(content)
}
}
}

View file

@ -1,230 +0,0 @@
package com.fsck.k9.notification
import android.app.Notification
import androidx.core.app.NotificationCompat
import androidx.test.core.app.ApplicationProvider
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.K9.NotificationQuickDelete
import com.fsck.k9.RobolectricTest
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.testing.MockHelper.mockBuilder
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stubbing
private const val NEW_MESSAGE_COUNT = 2
private const val ACCOUNT_NAME = "accountName"
private const val ACCOUNT_NUMBER = 3
private const val ACCOUNT_COLOR = 0xABCDEF
private const val SUMMARY = "summary"
private const val PREVIEW = "preview"
private const val SUBJECT = "subject"
private const val SENDER = "sender"
private const val SUMMARY_2 = "summary2"
private const val PREVIEW_2 = "preview2"
private const val SUBJECT_2 = "subject2"
private const val SENDER_2 = "sender2"
private const val NOTIFICATION_ID = 23
class SummaryNotificationCreatorTest : RobolectricTest() {
private val notification = mock<Notification>()
private val bigTextStyle = mockBuilder<NotificationCompat.BigTextStyle>()
private val resourceProvider: NotificationResourceProvider = TestNotificationResourceProvider()
private val account = createFakeAccount()
private val notificationData = createFakeNotificationData(account)
private val builder = createFakeNotificationBuilder()
private val builder2 = createFakeNotificationBuilder()
private val lockScreenNotificationCreator = mock<LockScreenNotificationCreator>()
private val notificationCreator = createSummaryNotificationCreator(builder, lockScreenNotificationCreator)
@Test
fun buildSummaryNotification_withSingleMessageNotification() {
K9.notificationQuickDeleteBehaviour = NotificationQuickDelete.ALWAYS
stubbing(notificationData) {
on { isSingleMessageNotification } doReturn true
}
val result = notificationCreator.buildSummaryNotification(account, notificationData, false)
verify(builder).setSmallIcon(resourceProvider.iconNewMail)
verify(builder).color = ACCOUNT_COLOR
verify(builder).setAutoCancel(true)
verify(builder).setTicker(SUMMARY)
verify(builder).setContentText(SUBJECT)
verify(builder).setContentTitle(SENDER)
verify(builder).setStyle(bigTextStyle)
verify(bigTextStyle).bigText(PREVIEW)
verify(builder).addAction(resourceProvider.iconReply, "Reply", null)
verify(builder).addAction(resourceProvider.iconMarkAsRead, "Mark Read", null)
verify(builder).addAction(resourceProvider.iconDelete, "Delete", null)
verify(lockScreenNotificationCreator).configureLockScreenNotification(builder, notificationData)
assertThat(result).isEqualTo(notification)
}
@Test
fun buildSummaryNotification_withMultiMessageNotification() {
K9.notificationQuickDeleteBehaviour = NotificationQuickDelete.ALWAYS
stubbing(notificationData) {
on { isSingleMessageNotification } doReturn false
}
val result = notificationCreator.buildSummaryNotification(account, notificationData, false)
verify(builder).setSmallIcon(resourceProvider.iconNewMail)
verify(builder).color = ACCOUNT_COLOR
verify(builder).setAutoCancel(true)
verify(builder).setTicker(SUMMARY)
verify(builder).setContentTitle("$NEW_MESSAGE_COUNT new messages")
verify(builder).setSubText(ACCOUNT_NAME)
verify(builder).setGroup("newMailNotifications-$ACCOUNT_NUMBER")
verify(builder).setGroupSummary(true)
verify(builder).setStyle(notificationCreator.inboxStyle)
verify(notificationCreator.inboxStyle).setBigContentTitle("$NEW_MESSAGE_COUNT new messages")
verify(notificationCreator.inboxStyle).setSummaryText(ACCOUNT_NAME)
verify(notificationCreator.inboxStyle).addLine(SUMMARY)
verify(notificationCreator.inboxStyle).addLine(SUMMARY_2)
verify(builder).addAction(resourceProvider.iconMarkAsRead, "Mark Read", null)
verify(builder).addAction(resourceProvider.iconDelete, "Delete", null)
verify(lockScreenNotificationCreator).configureLockScreenNotification(builder, notificationData)
assertThat(result).isEqualTo(notification)
}
@Test
fun buildSummaryNotification_withAdditionalMessages() {
K9.notificationQuickDeleteBehaviour = NotificationQuickDelete.ALWAYS
stubbing(notificationData) {
on { isSingleMessageNotification } doReturn false
on { hasSummaryOverflowMessages() } doReturn true
on { getSummaryOverflowMessagesCount() } doReturn 23
}
notificationCreator.buildSummaryNotification(account, notificationData, false)
verify(notificationCreator.inboxStyle).setSummaryText("+ 23 more on $ACCOUNT_NAME")
}
@Test
fun buildSummaryNotification_withoutDeleteAllAction() {
K9.notificationQuickDeleteBehaviour = NotificationQuickDelete.NEVER
stubbing(notificationData) {
on { isSingleMessageNotification } doReturn false
}
notificationCreator.buildSummaryNotification(account, notificationData, false)
verify(builder, never()).addAction(resourceProvider.iconDelete, "Delete", null)
}
@Test
@Throws(Exception::class)
fun buildSummaryNotification_withoutDeleteAction() {
K9.notificationQuickDeleteBehaviour = NotificationQuickDelete.NEVER
stubbing(notificationData) {
on { isSingleMessageNotification } doReturn true
}
notificationCreator.buildSummaryNotification(account, notificationData, false)
verify(builder, never()).addAction(resourceProvider.iconDelete, "Delete", null)
}
private fun createFakeNotificationBuilder(): NotificationCompat.Builder {
return mockBuilder {
on { build() } doReturn notification
}
}
private fun createFakeAccount(): Account {
return mock {
on { chipColor } doReturn ACCOUNT_COLOR
on { accountNumber } doReturn ACCOUNT_NUMBER
on { notificationSetting } doReturn mock()
}
}
private fun createFakeNotificationData(account: Account): NotificationData {
val messageReference = MessageReference("irrelevant", 1, "irrelevant")
val content = NotificationContent(messageReference, SENDER, SUBJECT, PREVIEW, SUMMARY)
val content2 = NotificationContent(messageReference, SENDER_2, SUBJECT_2, PREVIEW_2, SUMMARY_2)
return mock {
on { newMessagesCount } doReturn NEW_MESSAGE_COUNT
on { this.account } doReturn account
on { getContentForSummaryNotification() } doReturn listOf(content, content2)
on { holderForLatestNotification } doReturn NotificationHolder(NOTIFICATION_ID, content)
}
}
private fun createSummaryNotificationCreator(
builder: NotificationCompat.Builder,
lockScreenNotificationCreator: LockScreenNotificationCreator
): TestSummaryNotificationCreator {
val notificationHelper = createFakeNotificationHelper(builder)
val singleMessageNotificationCreator = TestSingleMessageNotificationCreator(
notificationHelper = notificationHelper,
actionCreator = mock(),
resourceProvider = resourceProvider,
lockScreenNotificationCreator = mock()
)
return TestSummaryNotificationCreator(
notificationHelper = notificationHelper,
actionCreator = mock(),
lockScreenNotificationCreator = lockScreenNotificationCreator,
singleMessageNotificationCreator = singleMessageNotificationCreator,
resourceProvider = resourceProvider
)
}
private fun createFakeNotificationHelper(builder: NotificationCompat.Builder): NotificationHelper {
return mock {
on { getContext() } doReturn ApplicationProvider.getApplicationContext()
on { getAccountName(any()) } doReturn ACCOUNT_NAME
on { createNotificationBuilder(any(), any()) } doReturn builder doReturn builder2 doAnswer {
throw AssertionError("createNotificationBuilder() invoked more than twice")
}
}
}
internal class TestSummaryNotificationCreator(
notificationHelper: NotificationHelper,
actionCreator: NotificationActionCreator,
lockScreenNotificationCreator: LockScreenNotificationCreator,
singleMessageNotificationCreator: SingleMessageNotificationCreator,
resourceProvider: NotificationResourceProvider
) : SummaryNotificationCreator(
notificationHelper,
actionCreator,
lockScreenNotificationCreator,
singleMessageNotificationCreator,
resourceProvider
) {
val inboxStyle = mockBuilder<NotificationCompat.InboxStyle>()
override fun createInboxStyle(builder: NotificationCompat.Builder?): NotificationCompat.InboxStyle {
return inboxStyle
}
}
internal inner class TestSingleMessageNotificationCreator(
notificationHelper: NotificationHelper,
actionCreator: NotificationActionCreator,
resourceProvider: NotificationResourceProvider,
lockScreenNotificationCreator: LockScreenNotificationCreator
) : SingleMessageNotificationCreator(
notificationHelper,
actionCreator,
resourceProvider,
lockScreenNotificationCreator
) {
override fun createBigTextStyle(builder: NotificationCompat.Builder?): NotificationCompat.BigTextStyle {
return bigTextStyle
}
}
}

View file

@ -0,0 +1,289 @@
package com.fsck.k9.notification
import com.fsck.k9.Account
import com.fsck.k9.Clock
import com.fsck.k9.K9
import com.fsck.k9.TestClock
import com.fsck.k9.controller.MessageReference
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module
class SummaryNotificationDataCreatorTest {
private val account = createAccount()
private val notificationDataCreator = SummaryNotificationDataCreator(SingleMessageNotificationDataCreator())
@Before
fun setUp() {
startKoin {
modules(
module {
single<Clock> { TestClock() }
}
)
}
}
@After
fun tearDown() {
stopKoin()
setQuietTime(false)
}
@Test
fun `single new message`() {
val notificationData = createNotificationData()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = false
)
assertThat(result).isInstanceOf(SummarySingleNotificationData::class.java)
}
@Test
fun `single notification during quiet time`() {
setQuietTime(true)
val notificationData = createNotificationData()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = false
)
val summaryNotificationData = result as SummarySingleNotificationData
assertThat(summaryNotificationData.singleNotificationData.isSilent).isTrue()
}
@Test
fun `single notification with quiet time disabled`() {
setQuietTime(false)
val notificationData = createNotificationData()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = false
)
val summaryNotificationData = result as SummarySingleNotificationData
assertThat(summaryNotificationData.singleNotificationData.isSilent).isFalse()
}
@Test
fun `inbox-style notification during quiet time`() {
setQuietTime(true)
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = false
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.isSilent).isTrue()
}
@Test
fun `inbox-style notification with quiet time disabled`() {
setQuietTime(false)
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = false
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.isSilent).isFalse()
}
@Test
fun `inbox-style base properties`() {
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = true
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.notificationId).isEqualTo(
NotificationIds.getNewMailSummaryNotificationId(account)
)
assertThat(summaryNotificationData.isSilent).isTrue()
assertThat(summaryNotificationData.timestamp).isEqualTo(9000)
}
@Test
fun `default actions`() {
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = true
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.actions).contains(SummaryNotificationAction.MarkAsRead)
assertThat(summaryNotificationData.wearActions).contains(SummaryWearNotificationAction.MarkAsRead)
}
@Test
fun `always show delete action without confirmation`() {
setDeleteAction(K9.NotificationQuickDelete.ALWAYS)
setConfirmDeleteFromNotification(false)
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = true
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.actions).contains(SummaryNotificationAction.Delete)
assertThat(summaryNotificationData.wearActions).contains(SummaryWearNotificationAction.Delete)
}
@Test
fun `always show delete action with confirmation`() {
setDeleteAction(K9.NotificationQuickDelete.ALWAYS)
setConfirmDeleteFromNotification(true)
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = true
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.actions).contains(SummaryNotificationAction.Delete)
assertThat(summaryNotificationData.wearActions).doesNotContain(SummaryWearNotificationAction.Delete)
}
@Test
fun `show delete action for single notification without confirmation`() {
setDeleteAction(K9.NotificationQuickDelete.FOR_SINGLE_MSG)
setConfirmDeleteFromNotification(false)
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = true
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.actions).doesNotContain(SummaryNotificationAction.Delete)
assertThat(summaryNotificationData.wearActions).doesNotContain(SummaryWearNotificationAction.Delete)
}
@Test
fun `never show delete action`() {
setDeleteAction(K9.NotificationQuickDelete.NEVER)
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = true
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.actions).doesNotContain(SummaryNotificationAction.Delete)
assertThat(summaryNotificationData.wearActions).doesNotContain(SummaryWearNotificationAction.Delete)
}
@Test
fun `archive action with archive folder`() {
account.archiveFolderId = 1
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = true
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.wearActions).contains(SummaryWearNotificationAction.Archive)
}
@Test
fun `archive action without archive folder`() {
account.archiveFolderId = null
val notificationData = createNotificationDataWithMultipleMessages()
val result = notificationDataCreator.createSummaryNotificationData(
notificationData,
timestamp = 9000,
silent = true
)
val summaryNotificationData = result as SummaryInboxNotificationData
assertThat(summaryNotificationData.wearActions).doesNotContain(SummaryWearNotificationAction.Archive)
}
private fun setQuietTime(quietTime: Boolean) {
K9.isQuietTimeEnabled = quietTime
if (quietTime) {
K9.quietTimeStarts = "0:00"
K9.quietTimeEnds = "23:59"
}
}
private fun setDeleteAction(mode: K9.NotificationQuickDelete) {
K9.notificationQuickDeleteBehaviour = mode
}
private fun setConfirmDeleteFromNotification(confirm: Boolean) {
K9.isConfirmDeleteFromNotification = confirm
}
private fun createAccount(): Account {
return Account("00000000-0000-0000-0000-000000000000").apply {
accountNumber = 42
}
}
private fun createNotificationContent() = NotificationContent(
messageReference = MessageReference("irrelevant", 1, "irrelevant"),
sender = "irrelevant",
subject = "irrelevant",
preview = "irrelevant",
summary = "irrelevant"
)
private fun createNotificationData(
contentList: List<NotificationContent> = listOf(createNotificationContent())
): NotificationData {
return NotificationData(account).apply {
for (content in contentList) {
addNotificationContent(content)
}
}
}
@OptIn(ExperimentalStdlibApi::class)
private fun createNotificationDataWithMultipleMessages(times: Int = 2): NotificationData {
val contentList = buildList {
repeat(times) {
add(createNotificationContent())
}
}
return createNotificationData(contentList)
}
}

View file

@ -68,7 +68,6 @@ internal class K9NotificationActionCreator(
}
override fun createDismissMessagePendingIntent(
context: Context,
messageReference: MessageReference,
notificationId: Int
): PendingIntent {

View file

@ -68,7 +68,6 @@ internal class K9NotificationActionCreator(
}
override fun createDismissMessagePendingIntent(
context: Context,
messageReference: MessageReference,
notificationId: Int
): PendingIntent {