Moves notifications to unified DB

Adds separation between layers for push notifications feature

MAILAND-2742
This commit is contained in:
Zorica Stojchevska 2022-02-08 17:30:28 +01:00 committed by Zorica Stojchevska
parent 9e515a71e1
commit c20ef4ee34
26 changed files with 580 additions and 220 deletions

View File

@ -54,7 +54,6 @@ import ch.protonmail.android.data.local.CounterDao
import ch.protonmail.android.data.local.CounterDatabase
import ch.protonmail.android.data.local.MessageDao
import ch.protonmail.android.data.local.MessageDatabase
import ch.protonmail.android.data.local.NotificationDao
import ch.protonmail.android.data.local.PendingActionDao
import ch.protonmail.android.data.local.PendingActionDatabase
import ch.protonmail.android.domain.entity.user.Address
@ -64,7 +63,6 @@ import ch.protonmail.android.jobs.FetchByLocationJob
import ch.protonmail.android.labels.presentation.ui.EXTRA_MANAGE_FOLDERS
import ch.protonmail.android.labels.presentation.ui.LabelsManagerActivity
import ch.protonmail.android.mailbox.data.local.ConversationDao
import ch.protonmail.android.notifications.domain.NotificationDatabase
import ch.protonmail.android.notifications.presentation.utils.CHANNEL_ID_EMAIL
import ch.protonmail.android.settings.pin.PinSettingsActivity
import ch.protonmail.android.settings.presentation.AccountSettingsActivity
@ -141,7 +139,6 @@ abstract class BaseSettingsActivity : BaseConnectivityActivity() {
var messageDao: MessageDao? = null
var conversationDao: ConversationDao? = null
private var searchDatabase: MessageDao? = null
private var notificationDao: NotificationDao? = null
var counterDao: CounterDao? = null
var pendingActionDao: PendingActionDao? = null
var preferences: SharedPreferences? = null
@ -185,7 +182,6 @@ abstract class BaseSettingsActivity : BaseConnectivityActivity() {
contactDao = ContactDatabase.getInstance(applicationContext, userId).getDao()
messageDao = MessageDatabase.getInstance(applicationContext, userId).getDao()
conversationDao = MessageDatabase.getInstance(applicationContext, userId).getConversationDao()
notificationDao = NotificationDatabase.getInstance(applicationContext, userId).getDao()
counterDao = CounterDatabase.getInstance(applicationContext, userId).getDao()
pendingActionDao = PendingActionDatabase.getInstance(applicationContext, userId).getDao()
preferences = userManager.preferencesFor(userId)
@ -444,7 +440,6 @@ abstract class BaseSettingsActivity : BaseConnectivityActivity() {
messageDao,
searchDatabase,
conversationDao,
notificationDao,
attachmentMetadataDao,
pendingActionDao,
true

View File

@ -27,11 +27,9 @@ import ch.protonmail.android.data.local.CounterDatabase
import ch.protonmail.android.data.local.MessageDao
import ch.protonmail.android.data.local.MessageDatabase
import ch.protonmail.android.data.local.MessagePreferenceDao
import ch.protonmail.android.data.local.NotificationDao
import ch.protonmail.android.data.local.PendingActionDao
import ch.protonmail.android.data.local.PendingActionDatabase
import ch.protonmail.android.mailbox.data.local.ConversationDao
import ch.protonmail.android.notifications.domain.NotificationDatabase
import me.proton.core.domain.entity.UserId
import javax.inject.Inject
import javax.inject.Singleton
@ -72,10 +70,6 @@ class DatabaseProvider @Inject constructor(
fun provideConversationDao(userId: UserId): ConversationDao =
MessageDatabase.getInstance(context, userId).getConversationDao()
// Notification
fun provideNotificationDao(userId: UserId): NotificationDao =
NotificationDatabase.getInstance(context, userId).getDao()
// Pending action
fun providePendingActionDao(userId: UserId): PendingActionDao =
PendingActionDatabase.getInstance(context, userId).getDao()

View File

@ -28,6 +28,8 @@ import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import ch.protonmail.android.labels.data.local.LabelDao
import ch.protonmail.android.labels.data.local.model.LabelEntity
import ch.protonmail.android.notifications.data.local.NotificationDao
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import me.proton.core.account.data.db.AccountConverters
import me.proton.core.account.data.db.AccountDatabase
import me.proton.core.account.data.entity.AccountEntity
@ -96,6 +98,7 @@ import timber.log.Timber
FeatureFlagEntity::class,
// Mail
LabelEntity::class,
NotificationEntity::class
],
version = AppDatabase.version,
exportSchema = true
@ -127,6 +130,7 @@ internal abstract class AppDatabase :
FeatureFlagDatabase {
abstract fun labelDao(): LabelDao
abstract fun notificationDao(): NotificationDao
companion object {

View File

@ -1,60 +0,0 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.data.local
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import ch.protonmail.android.data.local.model.COLUMN_NOTIFICATION_MESSAGE_ID
import ch.protonmail.android.data.local.model.Notification
import ch.protonmail.android.data.local.model.TABLE_NOTIFICATION
@Dao
interface NotificationDao {
@Query("SELECT * FROM Notification WHERE message_id=:messageId")
fun findByMessageId(messageId: String): Notification?
@Query("DELETE FROM Notification WHERE message_id=:messageId")
fun deleteByMessageId(messageId: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertNotification(notification: Notification)
@Query("DELETE FROM Notification")
fun clearNotificationCache()
@Query("SELECT * FROM Notification")
fun findAllNotifications(): List<Notification>
@Delete
fun deleteNotifications(notifications: List<Notification>)
@Query("SELECT COUNT($COLUMN_NOTIFICATION_MESSAGE_ID) FROM $TABLE_NOTIFICATION")
fun count(): Int
@Transaction
fun insertNewNotificationAndReturnAll(notification: Notification): List<Notification> {
insertNotification(notification)
return findAllNotifications()
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.data.local.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
const val TABLE_NOTIFICATION = "Notification"
const val COLUMN_NOTIFICATION_MESSAGE_ID = "message_id"
const val COLUMN_NOTIFICATION_TITLE = "notification_title"
const val COLUMN_NOTIFICATION_BODY = "notification_body"
@Entity(
tableName = TABLE_NOTIFICATION,
indices = [Index(COLUMN_NOTIFICATION_MESSAGE_ID, unique = true)]
)
data class Notification constructor(
@ColumnInfo(name = COLUMN_NOTIFICATION_MESSAGE_ID)
var messageId: String,
@ColumnInfo(name = COLUMN_NOTIFICATION_TITLE)
val notificationTitle: String,
@ColumnInfo(name = COLUMN_NOTIFICATION_BODY)
val notificationBody: String
) {
@PrimaryKey
var dbId: Long? = null
}

View File

@ -29,6 +29,8 @@ import ch.protonmail.android.labels.data.LabelRepositoryImpl
import ch.protonmail.android.labels.domain.LabelRepository
import ch.protonmail.android.mailbox.data.ConversationsRepositoryImpl
import ch.protonmail.android.mailbox.domain.ConversationsRepository
import ch.protonmail.android.notifications.data.NotificationRepositoryImpl
import ch.protonmail.android.notifications.domain.NotificationRepository
import ch.protonmail.android.settings.data.SharedPreferencesDeviceSettingsRepository
import ch.protonmail.android.settings.domain.DeviceSettingsRepository
import dagger.Binds
@ -58,4 +60,7 @@ internal interface ApplicationBindsModule {
@Binds
fun provideCounterRepository(repo: CounterRepositoryImpl): CounterRepository
@Binds
fun provideNotificationRepository(repo: NotificationRepositoryImpl): NotificationRepository
}

View File

@ -35,6 +35,7 @@ import ch.protonmail.android.data.local.PendingActionDatabase
import ch.protonmail.android.labels.data.local.LabelDao
import ch.protonmail.android.mailbox.data.local.ConversationDao
import ch.protonmail.android.mailbox.data.local.UnreadCounterDao
import ch.protonmail.android.notifications.data.local.NotificationDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -58,6 +59,11 @@ internal object DatabaseModule {
appDatabase: AppDatabase
): LabelDao = appDatabase.labelDao()
@Provides
fun provideNotificationDao(
appDatabase: AppDatabase
): NotificationDao = appDatabase.notificationDao()
@Provides
fun provideAttachmentMetadataDao(
context: Context,

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.data
import ch.protonmail.android.notifications.data.local.NotificationDao
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.notifications.data.mapper.NotificationApiEntityMapper
import ch.protonmail.android.notifications.data.mapper.NotificationEntityDomainMapper
import ch.protonmail.android.notifications.data.remote.model.PushNotification
import ch.protonmail.android.notifications.domain.NotificationRepository
import ch.protonmail.android.notifications.domain.model.Notification
import me.proton.core.domain.entity.UserId
import javax.inject.Inject
internal class NotificationRepositoryImpl @Inject constructor(
private val notificationDao: NotificationDao,
private val notificationApiEntityMapper: NotificationApiEntityMapper,
private val notificationEntityDomainMapper: NotificationEntityDomainMapper
) : NotificationRepository {
override suspend fun saveNotification(notification: PushNotification, userId: UserId): List<NotificationEntity> {
notificationDao.insertOrUpdate(notificationApiEntityMapper.toEntity(notification, userId))
return notificationDao.findAllNotifications()
}
override suspend fun deleteNotification(userId: UserId, notificationId: String) {
notificationDao.deleteByMessageId(notificationId)
}
override suspend fun clearNotificationsByUserId(userId: UserId) {
notificationDao.clearNotificationsByUserId(userId = userId.id)
}
override fun clearNotifications() {
notificationDao.clearNotifications()
}
override fun findNotificationById(messageId: String): Notification? =
notificationDao.findByMessageId(messageId)?.let {
notificationEntityDomainMapper.toNotification(it)
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.data.local
import androidx.room.Dao
import androidx.room.Query
import ch.protonmail.android.notifications.data.local.model.COLUMN_NOTIFICATION_MESSAGE_ID
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.notifications.data.local.model.TABLE_NOTIFICATION
import me.proton.core.data.room.db.BaseDao
@Dao
internal abstract class NotificationDao : BaseDao<NotificationEntity>() {
@Query("SELECT * FROM NotificationEntity WHERE message_id=:messageId")
abstract fun findByMessageId(messageId: String): NotificationEntity?
@Query("DELETE FROM NotificationEntity WHERE message_id=:messageId")
abstract suspend fun deleteByMessageId(messageId: String)
@Query("DELETE FROM NotificationEntity WHERE userId=:userId")
abstract suspend fun clearNotificationsByUserId(userId: String)
@Query("DELETE FROM NotificationEntity")
abstract fun clearNotifications()
@Query("SELECT COUNT($COLUMN_NOTIFICATION_MESSAGE_ID) FROM $TABLE_NOTIFICATION")
abstract fun count(): Int
@Query("SELECT * FROM NotificationEntity")
abstract fun findAllNotifications(): List<NotificationEntity>
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.data.local.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import ch.protonmail.android.notifications.domain.model.NotificationType
import me.proton.core.domain.entity.UserId
import me.proton.core.user.data.entity.UserEntity
const val TABLE_NOTIFICATION = "NotificationEntity"
internal const val COLUMN_NOTIFICATION_MESSAGE_ID = "message_id"
internal const val COLUMN_NOTIFICATION_USER_ID = "userId"
internal const val COLUMN_NOTIFICATION_TITLE = "notification_title"
internal const val COLUMN_NOTIFICATION_BODY = "notification_body"
internal const val COLUMN_NOTIFICATION_URL = "url"
internal const val COLUMN_NOTIFICATION_TYPE = "type"
@Entity(
tableName = TABLE_NOTIFICATION,
primaryKeys = [COLUMN_NOTIFICATION_MESSAGE_ID],
indices = [
Index(COLUMN_NOTIFICATION_MESSAGE_ID),
Index(COLUMN_NOTIFICATION_USER_ID)
],
foreignKeys = [
ForeignKey(
entity = UserEntity::class,
parentColumns = [COLUMN_NOTIFICATION_USER_ID],
childColumns = [COLUMN_NOTIFICATION_USER_ID],
onDelete = ForeignKey.CASCADE
)
]
)
data class NotificationEntity(
@ColumnInfo(name = COLUMN_NOTIFICATION_USER_ID)
val userId: UserId,
@ColumnInfo(name = COLUMN_NOTIFICATION_MESSAGE_ID)
val messageId: String,
@ColumnInfo(name = COLUMN_NOTIFICATION_TITLE)
val notificationTitle: String,
@ColumnInfo(name = COLUMN_NOTIFICATION_BODY)
val notificationBody: String,
@ColumnInfo(name = COLUMN_NOTIFICATION_URL)
val url: String?,
@ColumnInfo(name = COLUMN_NOTIFICATION_TYPE)
val type: NotificationType
)

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.data.mapper
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.notifications.data.remote.model.PushNotification
import ch.protonmail.android.notifications.domain.model.NotificationType
import me.proton.core.domain.arch.Mapper
import me.proton.core.domain.entity.UserId
import me.proton.core.util.kotlin.EMPTY_STRING
import javax.inject.Inject
class NotificationApiEntityMapper @Inject constructor() : Mapper<PushNotification, NotificationEntity> {
fun toEntity(model: PushNotification, userId: UserId): NotificationEntity {
val data = checkNotNull(model.data)
return NotificationEntity(
userId = userId,
messageId = data.messageId,
notificationTitle = data.sender?.let {
it.senderName.ifEmpty { it.senderAddress }
} ?: EMPTY_STRING,
notificationBody = data.body,
url = data.url,
type = requireNotNull(NotificationType.fromStringOrNull(model.type)) { "Notification type is null" }
)
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.data.mapper
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.notifications.domain.model.Notification
import ch.protonmail.android.notifications.domain.model.NotificationId
import me.proton.core.domain.arch.Mapper
import me.proton.core.domain.entity.UserId
import me.proton.core.util.kotlin.EMPTY_STRING
import javax.inject.Inject
class NotificationEntityDomainMapper @Inject constructor() : Mapper<NotificationEntity, Notification> {
fun toNotification(model: NotificationEntity) = Notification(
id = NotificationId(model.messageId),
notificationTitle = model.notificationTitle,
notificationBody = model.notificationBody,
url = model.url ?: EMPTY_STRING,
type = model.type
)
fun toEntity(model: Notification, userId: UserId) = NotificationEntity(
messageId = model.id.value,
userId = userId,
notificationTitle = model.notificationTitle,
notificationBody = model.notificationBody,
url = model.url,
type = model.type
)
}

View File

@ -26,5 +26,6 @@ import kotlinx.serialization.Serializable
data class PushNotification(
@SerialName("type") val type: String,
@SerialName("version") val version: Int,
@SerialName("data") val data: PushNotificationData?
@SerialName("data") val data: PushNotificationData?,
@SerialName("action") val action: String = "message_created"
)

View File

@ -34,5 +34,6 @@ data class PushNotificationData(
@SerialName("badge") val badge: Int,
@SerialName("messageId") val messageId: String,
@SerialName("customId") val customId: String,
@SerialName("sender") val sender: PushNotificationSender?
@SerialName("sender") val sender: PushNotificationSender?,
@SerialName("url") val url: String = ""
)

View File

@ -19,19 +19,20 @@
package ch.protonmail.android.notifications.domain
import androidx.room.Database
import androidx.room.RoomDatabase
import ch.protonmail.android.data.local.DatabaseFactory
import ch.protonmail.android.data.local.NotificationDao
import ch.protonmail.android.data.local.model.Notification
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.notifications.data.remote.model.PushNotification
import ch.protonmail.android.notifications.domain.model.Notification
import me.proton.core.domain.entity.UserId
@Database(entities = [Notification::class], version = 1)
abstract class NotificationDatabase : RoomDatabase() {
interface NotificationRepository {
abstract fun getDao(): NotificationDao
suspend fun saveNotification(notification: PushNotification, userId: UserId): List<NotificationEntity>
companion object : DatabaseFactory<NotificationDatabase>(
NotificationDatabase::class,
"NotificationsDatabase.db"
)
suspend fun deleteNotification(userId: UserId, notificationId: String)
suspend fun clearNotificationsByUserId(userId: UserId)
fun clearNotifications()
fun findNotificationById(messageId: String): Notification?
}

View File

@ -30,13 +30,11 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.api.models.User
import ch.protonmail.android.api.segments.event.AlarmReceiver
import ch.protonmail.android.core.QueueNetworkUtil
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.crypto.UserCrypto
import ch.protonmail.android.data.local.model.Notification
import ch.protonmail.android.mailbox.presentation.ConversationModeEnabled
import ch.protonmail.android.notifications.data.remote.model.PushNotification
import ch.protonmail.android.notifications.data.remote.model.PushNotificationData
@ -69,7 +67,7 @@ internal class ProcessPushNotificationDataWorker @AssistedInject constructor(
private val alarmReceiver: AlarmReceiver,
private val queueNetworkUtil: QueueNetworkUtil,
private val userManager: UserManager,
private val databaseProvider: DatabaseProvider,
private val notificationRepository: NotificationRepository,
private val messageRepository: MessageRepository,
private val sessionManager: SessionManager,
private val conversationModeEnabled: ConversationModeEnabled
@ -149,7 +147,7 @@ internal class ProcessPushNotificationDataWorker @AssistedInject constructor(
val isScheduledSnoozeEnabled = userManager.isSnoozeScheduledEnabled()
if (!isQuickSnoozeEnabled && (!isScheduledSnoozeEnabled || !shouldSuppressNotification())) {
sendNotification(userId, user, messageId, notificationBody, sender, isPrimaryUser)
sendNotification(userId, user, messageId, notificationBody, sender, pushNotification, isPrimaryUser)
}
return Result.success()
@ -161,13 +159,13 @@ internal class ProcessPushNotificationDataWorker @AssistedInject constructor(
messageId: String,
notificationBody: String,
sender: String,
pushNotification: PushNotification,
isPrimaryUser: Boolean
) {
// Insert current Notification in Database
val notificationsDatabase = databaseProvider.provideNotificationDao(userId)
val notification = Notification(messageId, sender, notificationBody)
val notifications = notificationsDatabase.insertNewNotificationAndReturnAll(notification)
val notifications = checkNotNull(notificationRepository.saveNotification(pushNotification, userId))
val message = messageRepository.getMessage(userId, messageId)
if (notifications.size > 1) {

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.domain.model
data class Notification(
val id: NotificationId,
val notificationTitle: String,
val notificationBody: String,
val url: String,
val type: NotificationType
)

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.domain.model
/**
* Entity representing a NotificationId.
*/
@JvmInline
value class NotificationId(val value: String)

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.domain.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
const val NOTIFICATION_TYPE_EMAIL = "email"
const val NOTIFICATION_TYPE_OPEN_URL = "open_url"
@Serializable
enum class NotificationType(val type: String) {
@SerialName(NOTIFICATION_TYPE_EMAIL)
EMAIL("email"),
@SerialName(NOTIFICATION_TYPE_OPEN_URL)
OPEN_URL("open_url");
companion object {
fun fromStringOrNull(type: String): NotificationType? {
return values().find {
it.type == type
}
}
}
}

View File

@ -47,6 +47,7 @@ import ch.protonmail.android.details.presentation.MessageDetailsActivity
import ch.protonmail.android.domain.entity.Name
import ch.protonmail.android.domain.entity.user.User
import ch.protonmail.android.mailbox.presentation.MailboxActivity
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.receivers.EXTRA_NOTIFICATION_DELETE_MESSAGE
import ch.protonmail.android.utils.MessageUtils
import ch.protonmail.android.utils.buildArchiveIntent
@ -57,7 +58,6 @@ import me.proton.core.domain.entity.UserId
import timber.log.Timber
import javax.inject.Inject
import ch.protonmail.android.api.models.User as LegacyUser
import ch.protonmail.android.data.local.model.Notification as RoomNotification
const val CHANNEL_ID_EMAIL = "emails"
const val EXTRA_MAILBOX_LOCATION = "mailbox_location"
@ -487,7 +487,7 @@ class NotificationServer @Inject constructor(
notificationSettings: Int,
ringtoneUri: Uri?,
isNotificationVisibleInLockScreen: Boolean,
unreadNotifications: List<RoomNotification>
unreadNotifications: List<NotificationEntity>
) {
val contentPendingIntent = getMailboxActivityIntent(loggedInUser.id)

View File

@ -45,7 +45,6 @@ import ch.protonmail.android.data.local.MessageDatabase;
import ch.protonmail.android.data.local.PendingActionDatabase;
import ch.protonmail.android.data.local.model.AttachmentMetadata;
import ch.protonmail.android.data.local.model.Message;
import ch.protonmail.android.notifications.domain.NotificationDatabase;
import dagger.hilt.android.AndroidEntryPoint;
import me.proton.core.domain.entity.UserId;
import timber.log.Timber;
@ -142,7 +141,6 @@ public class AttachmentClearingService extends ProtonJobIntentService {
Context context = this;
ContactDatabase.Companion.deleteDatabase(context, userId);
MessageDatabase.Factory.deleteDatabase(context, userId);
NotificationDatabase.Companion.deleteDatabase(context, userId);
CounterDatabase.Companion.deleteDatabase(context, userId);
AttachmentMetadataDatabase.Companion.deleteDatabase(context, userId);
PendingActionDatabase.Companion.deleteDatabase(context, userId);

View File

@ -22,7 +22,6 @@ package ch.protonmail.android.usecase.delete
import android.content.Context
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.labels.domain.LabelRepository
import ch.protonmail.android.storage.AttachmentClearingService
import ch.protonmail.android.storage.MessageBodyClearingService
import ch.protonmail.android.utils.AppUtil
@ -46,8 +45,7 @@ import javax.inject.Inject
class ClearUserData @Inject constructor(
private val context: Context,
private val databaseProvider: DatabaseProvider,
private val dispatchers: DispatcherProvider,
private val labelRepository: LabelRepository
private val dispatchers: DispatcherProvider
) {
suspend operator fun invoke(userId: UserId, alsoClearContacts: Boolean = true) {
@ -61,7 +59,6 @@ class ClearUserData @Inject constructor(
val conversationDao = runCatching { databaseProvider.provideConversationDao(userId) }.getOrNull()
val counterDao = runCatching { databaseProvider.provideUnreadCounterDao(userId) }.getOrNull()
val notificationDao = runCatching { databaseProvider.provideNotificationDao(userId) }.getOrNull()
val pendingActionDao = runCatching { databaseProvider.providePendingActionDao(userId) }.getOrNull()
// Ensure that all the queries run on Io thread, as some are still blocking calls
@ -81,7 +78,6 @@ class ClearUserData @Inject constructor(
clearAttachmentsCache()
}
conversationDao?.clear()
notificationDao?.clearNotificationCache()
pendingActionDao?.run {
clearPendingSendCache()
clearPendingUploadCache()

View File

@ -63,13 +63,11 @@ import ch.protonmail.android.data.local.ContactDao;
import ch.protonmail.android.data.local.ContactDatabase;
import ch.protonmail.android.data.local.MessageDao;
import ch.protonmail.android.data.local.MessageDatabase;
import ch.protonmail.android.data.local.NotificationDao;
import ch.protonmail.android.data.local.PendingActionDao;
import ch.protonmail.android.data.local.PendingActionDatabase;
import ch.protonmail.android.events.ApiOfflineEvent;
import ch.protonmail.android.events.ForceUpgradeEvent;
import ch.protonmail.android.mailbox.data.local.ConversationDao;
import ch.protonmail.android.notifications.domain.NotificationDatabase;
import ch.protonmail.android.storage.AttachmentClearingService;
import ch.protonmail.android.storage.MessageBodyClearingService;
import me.proton.core.domain.entity.UserId;
@ -175,7 +173,6 @@ public class AppUtil {
ContactDatabase.Companion.getInstance(context, userId).getDao(),
MessageDatabase.Factory.getInstance(context, userId).getDao(),
MessageDatabase.Factory.getInstance(context, userId).getConversationDao(),
NotificationDatabase.Companion.getInstance(context, userId).getDao(),
AttachmentMetadataDatabase.Companion.getInstance(context, userId).getDao(),
PendingActionDatabase.Companion.getInstance(context, userId).getDao(),
clearDoneListener, clearContacts
@ -226,19 +223,11 @@ public class AppUtil {
notificationManager.cancelAll();
final UserManager userManager = ((ProtonMailApplication) context.getApplicationContext()).getUserManager();
final UserId userId = userManager.requireCurrentUserId();
final NotificationDao notificationDao = NotificationDatabase.Companion
.getInstance(context, userId)
.getDao();
new ClearNotificationsFromDatabaseTask(notificationDao).execute();
}
public static void clearNotifications(Context context, UserId userId) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(userId.hashCode());
final NotificationDao notificationDao = NotificationDatabase.Companion
.getInstance(context, userId)
.getDao();
new ClearNotificationsFromDatabaseTask(notificationDao).execute();
}
public static void clearNotifications(Context context, int notificationId) {
@ -303,7 +292,6 @@ public class AppUtil {
final MessageDao messageDao,
final MessageDao searchDatabase,
final ConversationDao conversationDao,
final NotificationDao notificationDao,
final AttachmentMetadataDao attachmentMetadataDao,
final PendingActionDao pendingActionDao,
final boolean clearContacts
@ -314,7 +302,6 @@ public class AppUtil {
contactDao,
messageDao,
conversationDao,
notificationDao,
attachmentMetadataDao,
pendingActionDao,
null,
@ -329,7 +316,6 @@ public class AppUtil {
final ContactDao contactDao,
final MessageDao messageDao,
final ConversationDao conversationDao,
final NotificationDao notificationDao,
final AttachmentMetadataDao attachmentMetadataDao,
final PendingActionDao pendingActionDao,
final IDBClearDone clearDone,
@ -351,7 +337,6 @@ public class AppUtil {
messageDao.clearMessagesCache();
messageDao.clearAttachmentsCache();
conversationDao.clear();
notificationDao.clearNotificationCache();
attachmentMetadataDao.clearAttachmentMetadataCache();
return null;
}
@ -392,38 +377,6 @@ public class AppUtil {
backupSharedPrefs.edit().clear().apply();
}
/**
* Deletes user's Secure Shared Preferences and preserves some important values.
*/
public static void deleteSecurePrefs(
@NonNull SharedPreferences userPreferences,
boolean deletePin
) {
String mailboxPinBackup = userPreferences.getString(PREF_PIN, null);
SharedPreferences.Editor editor = userPreferences.edit()
.clear();
if (!deletePin) {
editor.putString(PREF_PIN, mailboxPinBackup);
}
editor.apply();
}
// TODO: Rewrite with coroutines after the whole AppUtil file is converted to Kotlin
private static class ClearNotificationsFromDatabaseTask extends AsyncTask<Void, Void, Void> {
private final NotificationDao notificationDao;
ClearNotificationsFromDatabaseTask(
NotificationDao notificationDao) {
this.notificationDao = notificationDao;
}
@Override
protected Void doInBackground(Void... voids) {
notificationDao.clearNotificationCache();
return null;
}
}
public interface IDBClearDone {
void onDatabaseClearingCompleted();
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.data.mapper
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.notifications.data.remote.model.PushNotification
import ch.protonmail.android.notifications.data.remote.model.PushNotificationData
import ch.protonmail.android.notifications.data.remote.model.PushNotificationSender
import ch.protonmail.android.notifications.domain.model.NotificationType
import me.proton.core.domain.entity.UserId
import org.junit.Assert
import org.junit.Test
internal class NotificationEntityApiMapperTest {
private val testUserId = UserId("TestUserId")
private val notificationMapper = NotificationApiEntityMapper()
@Test
fun `mapping notification api model to NotificationEntity succeeds when all fields are valid`() {
// given
val pushNotification = getTestNotificationApiModel(testId = "ID")
// when
val actual = notificationMapper.toEntity(pushNotification, testUserId)
// then
val expected = getTestNotificationEntity(testUserId, testId = "ID")
Assert.assertEquals(expected, actual)
}
private fun getTestNotificationApiModel(testId: String) = PushNotification(
type = "email",
version = 2,
data = getTestNotificationDataApiModel(testId),
action = "message_created"
)
private fun getTestNotificationDataApiModel(testId: String) = PushNotificationData(
title = "ProtonMail",
subtitle = "",
body = "subject",
vibrate = 1,
sound = 1,
largeIcon = "large_icon",
smallIcon = "small_icon",
badge = 123,
messageId = testId,
customId = "123-abc",
sender = PushNotificationSender("testUser@protonmail.com", "testUser", ""),
url = "https://www.example.com/"
)
private fun getTestNotificationEntity(userId: UserId, testId: String) = NotificationEntity(
userId = userId,
messageId = testId,
notificationTitle = "testUser",
notificationBody = "subject",
url = "https://www.example.com/",
type = NotificationType.EMAIL
)
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.data.mapper
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.notifications.domain.model.Notification
import ch.protonmail.android.notifications.domain.model.NotificationId
import ch.protonmail.android.notifications.domain.model.NotificationType
import me.proton.core.domain.entity.UserId
import org.junit.Assert
import org.junit.Test
internal class NotificationEntityDomainMapperTest {
private val testUserId = UserId("TestUserId")
private val notificationMapper = NotificationEntityDomainMapper()
@Test
fun `mapping NotificationEntity to Notification`() {
// given
val notificationEntity = getTestNotificationEntity(userId = testUserId, testId = "ID")
// when
val actual = notificationMapper.toNotification(notificationEntity)
// then
val expected = getTestNotification(testId = "ID")
Assert.assertEquals(expected, actual)
}
@Test
fun `mapping Notification to NotificationEntity`() {
// given
val notification = getTestNotification(testId = "ID")
// when
val actual = notificationMapper.toEntity(notification, userId = testUserId)
// then
val expected = getTestNotificationEntity(userId = testUserId, testId = "ID")
Assert.assertEquals(expected, actual)
}
private fun getTestNotificationEntity(userId: UserId, testId: String) = NotificationEntity(
userId = userId,
messageId = testId,
notificationTitle = "testUser",
notificationBody = "subject",
url = "https://www.example.com/",
type = NotificationType.EMAIL
)
private fun getTestNotification(testId: String) = Notification(
id = NotificationId(testId),
notificationTitle = "testUser",
notificationBody = "subject",
url = "https://www.example.com/",
type = NotificationType.EMAIL
)
}

View File

@ -17,7 +17,7 @@
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.notifications.data.remote.fcm
package ch.protonmail.android.notifications.domain
import android.content.Context
import androidx.work.ListenableWorker
@ -25,22 +25,17 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.api.models.User
import ch.protonmail.android.api.segments.event.AlarmReceiver
import ch.protonmail.android.core.QueueNetworkUtil
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.crypto.UserCrypto
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.data.local.model.Notification
import ch.protonmail.android.mailbox.presentation.ConversationModeEnabled
import ch.protonmail.android.notifications.data.local.model.NotificationEntity
import ch.protonmail.android.notifications.data.remote.model.PushNotification
import ch.protonmail.android.notifications.data.remote.model.PushNotificationData
import ch.protonmail.android.notifications.data.remote.model.PushNotificationSender
import ch.protonmail.android.notifications.domain.KEY_PROCESS_PUSH_NOTIFICATION_DATA_ERROR
import ch.protonmail.android.notifications.domain.KEY_PUSH_NOTIFICATION_ENCRYPTED_MESSAGE
import ch.protonmail.android.notifications.domain.KEY_PUSH_NOTIFICATION_UID
import ch.protonmail.android.notifications.domain.ProcessPushNotificationDataWorker
import ch.protonmail.android.notifications.presentation.utils.NotificationServer
import ch.protonmail.android.repository.MessageRepository
import ch.protonmail.android.utils.AppUtil
@ -94,7 +89,7 @@ class ProcessPushNotificationDataWorkerTest {
private val alarmReceiver: AlarmReceiver = mockk(relaxed = true)
private val databaseProvider: DatabaseProvider = mockk(relaxed = true)
private val notificationRepository: NotificationRepository = mockk(relaxed = true)
private val messageRepository: MessageRepository = mockk(relaxed = true)
@ -114,7 +109,7 @@ class ProcessPushNotificationDataWorkerTest {
alarmReceiver,
queueNetworkUtil,
userManager,
databaseProvider,
notificationRepository,
messageRepository,
sessionManager,
conversationModeEnabled
@ -398,10 +393,8 @@ class ProcessPushNotificationDataWorkerTest {
// given
mockForCallingSendNotificationSuccessfully()
val mockNotification = mockk<Notification>()
every { databaseProvider.provideNotificationDao(testId) } returns mockk(relaxed = true) {
every { insertNewNotificationAndReturnAll(any()) } returns listOf(mockNotification)
}
val mockNotification = mockk<NotificationEntity>()
coEvery { notificationRepository.saveNotification(any(), any()) } returns listOf(mockNotification)
val mockMessage = mockk<Message>()
coEvery { messageRepository.getMessage(testId, "messageId") } returns mockMessage
every {
@ -460,12 +453,10 @@ class ProcessPushNotificationDataWorkerTest {
// given
mockForCallingSendNotificationSuccessfully()
val mockNotification1 = mockk<Notification>()
val mockNotification2 = mockk<Notification>()
val mockNotification1 = mockk<NotificationEntity>()
val mockNotification2 = mockk<NotificationEntity>()
val unreadNotifications = listOf(mockNotification1, mockNotification2)
every { databaseProvider.provideNotificationDao(testId) } returns mockk(relaxed = true) {
every { insertNewNotificationAndReturnAll(any()) } returns unreadNotifications
}
coEvery { notificationRepository.saveNotification(any(), any()) } returns unreadNotifications
val mockMessage = mockk<Message>()
coEvery { messageRepository.getMessage(testId, "messageId") } returns mockMessage
every { notificationServer.notifyMultipleUnreadEmail(any(), any(), any(), any(), any()) } just runs
@ -475,7 +466,7 @@ class ProcessPushNotificationDataWorkerTest {
// then
val userSlot = slot<ch.protonmail.android.domain.entity.user.User>()
val unreadNotificationsSlot = slot<List<Notification>>()
val unreadNotificationsSlot = slot<List<NotificationEntity>>()
verify {
notificationServer.notifyMultipleUnreadEmail(
capture(userSlot),
@ -496,10 +487,8 @@ class ProcessPushNotificationDataWorkerTest {
// given
mockForCallingSendNotificationSuccessfully()
val mockNotification = mockk<Notification>()
every { databaseProvider.provideNotificationDao(testId) } returns mockk(relaxed = true) {
every { insertNewNotificationAndReturnAll(any()) } returns listOf(mockNotification)
}
val mockNotification = mockk<NotificationEntity>()
coEvery { notificationRepository.saveNotification(any(), any()) } returns listOf(mockNotification)
val mockMessage = mockk<Message>()
coEvery { messageRepository.getMessage(testId, "messageId") } returns mockMessage
every { notificationServer.notifySingleNewEmail(any(), any(), any(), any(), any(), any(), any()) } just runs
@ -513,4 +502,4 @@ class ProcessPushNotificationDataWorkerTest {
assertEquals(expectedResult, workerResult)
}
}
}
}