Separate notification UI/UX logic from notification creation
This commit is contained in:
parent
c0c0e05a29
commit
b7526588c7
24 changed files with 1898 additions and 1368 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -68,7 +68,6 @@ internal class K9NotificationActionCreator(
|
|||
}
|
||||
|
||||
override fun createDismissMessagePendingIntent(
|
||||
context: Context,
|
||||
messageReference: MessageReference,
|
||||
notificationId: Int
|
||||
): PendingIntent {
|
||||
|
|
|
@ -68,7 +68,6 @@ internal class K9NotificationActionCreator(
|
|||
}
|
||||
|
||||
override fun createDismissMessagePendingIntent(
|
||||
context: Context,
|
||||
messageReference: MessageReference,
|
||||
notificationId: Int
|
||||
): PendingIntent {
|
||||
|
|
Loading…
Reference in a new issue