Add some tests (mainly imported from EA). Also change type from String to SessionId, RoomId, etc.
This commit is contained in:
committed by
Benoit Marty
parent
31ff2e7e0b
commit
7e7aca4a53
@@ -21,6 +21,9 @@ import android.content.Intent
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.push.impl.intent.IntentProvider
|
||||
import io.element.android.x.MainActivity
|
||||
import javax.inject.Inject
|
||||
@@ -34,7 +37,7 @@ class IntentProviderImpl @Inject constructor(
|
||||
return Intent(context, MainActivity::class.java)
|
||||
}
|
||||
|
||||
override fun getIntent(sessionId: String, roomId: String?, threadId: String?): Intent {
|
||||
override fun getIntent(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?): Intent {
|
||||
// TODO Handle deeplink or pass parameters
|
||||
return Intent(context, MainActivity::class.java)
|
||||
}
|
||||
|
||||
71
libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt
vendored
Normal file
71
libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.core.cache
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class CircularCacheTest {
|
||||
@Test
|
||||
fun `when putting more than cache size then cache is limited to cache size`() {
|
||||
val (cache, internalData) = createIntCache(cacheSize = 3)
|
||||
|
||||
cache.putInOrder(1, 1, 1, 1, 1, 1)
|
||||
|
||||
assertThat(internalData).isEqualTo(arrayOf(1, 1, 1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when putting more than cache then acts as FIFO`() {
|
||||
val (cache, internalData) = createIntCache(cacheSize = 3)
|
||||
|
||||
cache.putInOrder(1, 2, 3, 4)
|
||||
|
||||
assertThat(internalData).isEqualTo(arrayOf(4, 2, 3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given empty cache when checking if contains key then is false`() {
|
||||
val (cache, _) = createIntCache(cacheSize = 3)
|
||||
|
||||
val result = cache.contains(1)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given cached key when checking if contains key then is true`() {
|
||||
val (cache, _) = createIntCache(cacheSize = 3)
|
||||
|
||||
cache.put(1)
|
||||
val result = cache.contains(1)
|
||||
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
private fun createIntCache(cacheSize: Int): Pair<CircularCache<Int>, Array<Int?>> {
|
||||
var internalData: Array<Int?>? = null
|
||||
val factory: (Int) -> Array<Int?> = {
|
||||
Array<Int?>(it) { null }.also { array -> internalData = array }
|
||||
}
|
||||
return CircularCache(cacheSize, factory) to internalData!!
|
||||
}
|
||||
|
||||
private fun CircularCache<Int>.putInOrder(vararg values: Int) {
|
||||
values.forEach { put(it) }
|
||||
}
|
||||
}
|
||||
@@ -20,3 +20,5 @@ import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class EventId(val value: String) : Serializable
|
||||
|
||||
fun String.asEventId() = EventId(this)
|
||||
|
||||
@@ -20,3 +20,5 @@ import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class RoomId(val value: String) : Serializable
|
||||
|
||||
fun String.asRoomId() = RoomId(this)
|
||||
|
||||
@@ -17,3 +17,5 @@
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
typealias SessionId = UserId
|
||||
|
||||
fun String.asSessionId() = SessionId(this)
|
||||
|
||||
@@ -25,3 +25,5 @@ value class SpaceId(val value: String) : Serializable
|
||||
* Value to use when no space is selected by the user.
|
||||
*/
|
||||
val MAIN_SPACE = SpaceId("!mainSpace")
|
||||
|
||||
fun String.asSpaceId() = SpaceId(this)
|
||||
|
||||
@@ -20,3 +20,5 @@ import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class ThreadId(val value: String) : Serializable
|
||||
|
||||
fun String.asThreadId() = ThreadId(this)
|
||||
|
||||
@@ -20,3 +20,5 @@ import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class UserId(val value: String) : Serializable
|
||||
|
||||
fun String.asUserId() = UserId(this)
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package io.element.android.libraries.matrix.api.notification
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
interface NotificationService {
|
||||
suspend fun getNotification(userId: String, roomId: String, eventId: String): Result<NotificationData?>
|
||||
suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result<NotificationData?>
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package io.element.android.libraries.matrix.impl.notification
|
||||
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -28,15 +31,15 @@ class RustNotificationService(
|
||||
) : NotificationService {
|
||||
private val notificationMapper: NotificationMapper = NotificationMapper()
|
||||
|
||||
override suspend fun getNotification(userId: String, roomId: String, eventId: String): Result<NotificationData?> {
|
||||
override suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result<NotificationData?> {
|
||||
return withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
org.matrix.rustcomponents.sdk.NotificationService(
|
||||
basePath = File(baseDirectory, "sessions").absolutePath,
|
||||
userId = userId
|
||||
userId = userId.value
|
||||
).use {
|
||||
// TODO Not implemented yet, see https://github.com/matrix-org/matrix-rust-sdk/issues/1628
|
||||
it.getNotificationItem(roomId, eventId)?.let { notificationItem ->
|
||||
it.getNotificationItem(roomId.value, eventId.value)?.let { notificationItem ->
|
||||
notificationMapper.map(notificationItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,10 @@ val A_USER_ID = UserId("@alice:server.org")
|
||||
val A_SESSION_ID = SessionId(A_USER_ID.value)
|
||||
val A_SPACE_ID = SpaceId("!aSpaceId")
|
||||
val A_ROOM_ID = RoomId("!aRoomId")
|
||||
val A_ROOM_ID_2 = RoomId("!aRoomId2")
|
||||
val A_THREAD_ID = ThreadId("\$aThreadId")
|
||||
val AN_EVENT_ID = EventId("\$anEventId")
|
||||
val AN_EVENT_ID_2 = EventId("\$anEventId2")
|
||||
const val A_UNIQUE_ID = "aUniqueId"
|
||||
|
||||
const val A_ROOM_NAME = "A room name"
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
package io.element.android.libraries.matrix.test.notification
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||
|
||||
class FakeNotificationService : NotificationService {
|
||||
override suspend fun getNotification(userId: String, roomId: String, eventId: String): Result<NotificationData?> {
|
||||
override suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result<NotificationData?> {
|
||||
return Result.success(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ dependencies {
|
||||
exclude(group = "com.android.support", module = "support-annotations")
|
||||
}
|
||||
|
||||
|
||||
implementation(platform(libs.google.firebase.bom))
|
||||
implementation("com.google.firebase:firebase-messaging-ktx")
|
||||
|
||||
@@ -62,7 +61,10 @@ dependencies {
|
||||
api("com.github.UnifiedPush:android-connector:2.1.1")
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.mockk)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package io.element.android.libraries.push.impl
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.push.impl.clientsecret.PushClientSecret
|
||||
@@ -98,7 +99,7 @@ class PushersManager @Inject constructor(
|
||||
} else {
|
||||
// Register the pusher to the server
|
||||
matrixClient.pushersService().setHttpPusher(
|
||||
createHttpPusher(pushKey, gateway, matrixClient.sessionId.value)
|
||||
createHttpPusher(pushKey, gateway, matrixClient.sessionId)
|
||||
).fold(
|
||||
{
|
||||
userDataStore.setCurrentRegisteredPushKey(pushKey)
|
||||
@@ -113,7 +114,7 @@ class PushersManager @Inject constructor(
|
||||
private suspend fun createHttpPusher(
|
||||
pushKey: String,
|
||||
gateway: String,
|
||||
userId: String,
|
||||
userId: SessionId,
|
||||
): SetHttpPusherData =
|
||||
SetHttpPusherData(
|
||||
pushKey = pushKey,
|
||||
@@ -167,6 +168,6 @@ class PushersManager @Inject constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TEST_EVENT_ID = "\$THIS_IS_A_FAKE_EVENT_ID"
|
||||
val TEST_EVENT_ID = EventId("\$THIS_IS_A_FAKE_EVENT_ID")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,22 @@
|
||||
|
||||
package io.element.android.libraries.push.impl.clientsecret
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
interface PushClientSecret {
|
||||
/**
|
||||
* To call when registering a pusher. It will return the existing secret or create a new one.
|
||||
*/
|
||||
suspend fun getSecretForUser(userId: String): String
|
||||
suspend fun getSecretForUser(userId: SessionId): String
|
||||
|
||||
/**
|
||||
* To call when receiving a push containing a client secret.
|
||||
* Return null if not found.
|
||||
*/
|
||||
suspend fun getUserIdFromSecret(clientSecret: String): String?
|
||||
suspend fun getUserIdFromSecret(clientSecret: String): SessionId?
|
||||
|
||||
/**
|
||||
* To call when the user signs out.
|
||||
*/
|
||||
suspend fun resetSecretForUser(userId: String)
|
||||
suspend fun resetSecretForUser(userId: SessionId)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.libraries.push.impl.clientsecret
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
@@ -25,7 +26,7 @@ class PushClientSecretImpl @Inject constructor(
|
||||
private val pushClientSecretFactory: PushClientSecretFactory,
|
||||
private val pushClientSecretStore: PushClientSecretStore,
|
||||
) : PushClientSecret {
|
||||
override suspend fun getSecretForUser(userId: String): String {
|
||||
override suspend fun getSecretForUser(userId: SessionId): String {
|
||||
val existingSecret = pushClientSecretStore.getSecret(userId)
|
||||
if (existingSecret != null) {
|
||||
return existingSecret
|
||||
@@ -35,11 +36,11 @@ class PushClientSecretImpl @Inject constructor(
|
||||
return newSecret
|
||||
}
|
||||
|
||||
override suspend fun getUserIdFromSecret(clientSecret: String): String? {
|
||||
override suspend fun getUserIdFromSecret(clientSecret: String): SessionId? {
|
||||
return pushClientSecretStore.getUserIdFromSecret(clientSecret)
|
||||
}
|
||||
|
||||
override suspend fun resetSecretForUser(userId: String) {
|
||||
override suspend fun resetSecretForUser(userId: SessionId) {
|
||||
pushClientSecretStore.resetSecret(userId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package io.element.android.libraries.push.impl.clientsecret
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
interface PushClientSecretStore {
|
||||
suspend fun storeSecret(userId: String, clientSecret: String)
|
||||
suspend fun getSecret(userId: String): String?
|
||||
suspend fun resetSecret(userId: String)
|
||||
suspend fun getUserIdFromSecret(clientSecret: String): String?
|
||||
suspend fun storeSecret(userId: SessionId, clientSecret: String)
|
||||
suspend fun getSecret(userId: SessionId): String?
|
||||
suspend fun resetSecret(userId: SessionId)
|
||||
suspend fun getUserIdFromSecret(clientSecret: String): SessionId?
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.asSessionId
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -34,29 +36,29 @@ private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(na
|
||||
class PushClientSecretStoreDataStore @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) : PushClientSecretStore {
|
||||
override suspend fun storeSecret(userId: String, clientSecret: String) {
|
||||
override suspend fun storeSecret(userId: SessionId, clientSecret: String) {
|
||||
context.dataStore.edit { settings ->
|
||||
settings[getPreferenceKeyForUser(userId)] = clientSecret
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSecret(userId: String): String? {
|
||||
override suspend fun getSecret(userId: SessionId): String? {
|
||||
return context.dataStore.data.first()[getPreferenceKeyForUser(userId)]
|
||||
}
|
||||
|
||||
override suspend fun resetSecret(userId: String) {
|
||||
override suspend fun resetSecret(userId: SessionId) {
|
||||
context.dataStore.edit { settings ->
|
||||
settings.remove(getPreferenceKeyForUser(userId))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUserIdFromSecret(clientSecret: String): String? {
|
||||
override suspend fun getUserIdFromSecret(clientSecret: String): SessionId? {
|
||||
val keyValues = context.dataStore.data.first().asMap()
|
||||
val matchingKey = keyValues.keys.firstOrNull {
|
||||
keyValues[it] == clientSecret
|
||||
}
|
||||
return matchingKey?.name
|
||||
return matchingKey?.name?.asSessionId()
|
||||
}
|
||||
|
||||
private fun getPreferenceKeyForUser(userId: String) = stringPreferencesKey(userId)
|
||||
private fun getPreferenceKeyForUser(userId: SessionId) = stringPreferencesKey(userId.value)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package io.element.android.libraries.push.impl.firebase
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.MatrixPatterns
|
||||
import io.element.android.libraries.matrix.api.core.asEventId
|
||||
import io.element.android.libraries.matrix.api.core.asRoomId
|
||||
import io.element.android.libraries.push.impl.push.PushData
|
||||
|
||||
/**
|
||||
@@ -40,8 +42,8 @@ data class PushDataFirebase(
|
||||
)
|
||||
|
||||
fun PushDataFirebase.toPushData() = PushData(
|
||||
eventId = eventId?.takeIf { MatrixPatterns.isEventId(it) },
|
||||
roomId = roomId?.takeIf { MatrixPatterns.isRoomId(it) },
|
||||
eventId = eventId?.takeIf { MatrixPatterns.isEventId(it) }?.asEventId(),
|
||||
roomId = roomId?.takeIf { MatrixPatterns.isRoomId(it) }?.asRoomId(),
|
||||
unread = unread,
|
||||
clientSecret = clientSecret,
|
||||
)
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package io.element.android.libraries.push.impl.intent
|
||||
|
||||
import android.content.Intent
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
|
||||
interface IntentProvider {
|
||||
/**
|
||||
@@ -25,8 +28,8 @@ interface IntentProvider {
|
||||
fun getMainIntent(): Intent
|
||||
|
||||
fun getIntent(
|
||||
sessionId: String,
|
||||
roomId: String?,
|
||||
threadId: String?,
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId?,
|
||||
threadId: ThreadId?,
|
||||
): Intent
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||
@@ -52,13 +53,13 @@ class NotifiableEventResolver @Inject constructor(
|
||||
private val buildMeta: BuildMeta,
|
||||
) {
|
||||
|
||||
suspend fun resolveEvent(userId: String, roomId: String, eventId: String): NotifiableEvent? {
|
||||
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||
// Restore session
|
||||
val session = matrixAuthenticationService.restoreSession(SessionId(userId)).getOrNull() ?: return null
|
||||
val session = matrixAuthenticationService.restoreSession(sessionId).getOrNull() ?: return null
|
||||
// TODO EAx, no need for a session?
|
||||
val notificationData = session.let {// TODO Use make the app crashes
|
||||
it.notificationService().getNotification(
|
||||
userId = userId,
|
||||
userId = sessionId,
|
||||
roomId = roomId,
|
||||
eventId = eventId,
|
||||
)
|
||||
@@ -72,11 +73,11 @@ class NotifiableEventResolver @Inject constructor(
|
||||
}
|
||||
).orDefault(roomId, eventId)
|
||||
|
||||
return notificationData.asNotifiableEvent(userId, roomId, eventId)
|
||||
return notificationData.asNotifiableEvent(sessionId, roomId, eventId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NotificationData.asNotifiableEvent(userId: String, roomId: String, eventId: String): NotifiableEvent {
|
||||
private fun NotificationData.asNotifiableEvent(userId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent {
|
||||
return NotifiableMessageEvent(
|
||||
sessionId = userId,
|
||||
roomId = roomId,
|
||||
@@ -105,12 +106,12 @@ private fun NotificationData.asNotifiableEvent(userId: String, roomId: String, e
|
||||
/**
|
||||
* TODO This is a temporary method for EAx
|
||||
*/
|
||||
private fun NotificationData?.orDefault(roomId: String, eventId: String): NotificationData {
|
||||
private fun NotificationData?.orDefault(roomId: RoomId, eventId: EventId): NotificationData {
|
||||
return this ?: NotificationData(
|
||||
item = MatrixTimelineItem.Event(
|
||||
event = EventTimelineItem(
|
||||
uniqueIdentifier = eventId,
|
||||
eventId = EventId(eventId),
|
||||
uniqueIdentifier = eventId.value,
|
||||
eventId = eventId,
|
||||
isEditable = false,
|
||||
isLocal = false,
|
||||
isOwn = false,
|
||||
@@ -121,18 +122,18 @@ private fun NotificationData?.orDefault(roomId: String, eventId: String): Notifi
|
||||
senderProfile = ProfileTimelineDetails.Unavailable,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
content = MessageContent(
|
||||
body = eventId,
|
||||
body = eventId.value,
|
||||
inReplyTo = null,
|
||||
isEdited = false,
|
||||
type = TextMessageType(
|
||||
body = eventId,
|
||||
body = eventId.value,
|
||||
formatted = null
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
title = roomId,
|
||||
subtitle = eventId,
|
||||
title = roomId.value,
|
||||
subtitle = eventId.value,
|
||||
isNoisy = false,
|
||||
avatarUrl = null,
|
||||
)
|
||||
|
||||
@@ -22,6 +22,11 @@ import android.content.Intent
|
||||
import androidx.core.app.RemoteInput
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.asRoomId
|
||||
import io.element.android.libraries.matrix.api.core.asSessionId
|
||||
import io.element.android.libraries.matrix.api.core.asThreadId
|
||||
import io.element.android.libraries.push.impl.log.notificationLoggerTag
|
||||
import io.element.android.services.analytics.api.AnalyticsTracker
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
@@ -46,29 +51,29 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
||||
if (intent == null || context == null) return
|
||||
context.bindings<NotificationBroadcastReceiverBindings>().inject(this)
|
||||
Timber.tag(loggerTag.value).v("NotificationBroadcastReceiver received : $intent")
|
||||
val sessionId = intent.extras?.getString(KEY_SESSION_ID) ?: return
|
||||
val sessionId = intent.extras?.getString(KEY_SESSION_ID)?.asSessionId() ?: return
|
||||
when (intent.action) {
|
||||
actionIds.smartReply ->
|
||||
handleSmartReply(intent, context)
|
||||
actionIds.dismissRoom ->
|
||||
intent.getStringExtra(KEY_ROOM_ID)?.let { roomId ->
|
||||
intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()?.let { roomId ->
|
||||
notificationDrawerManager.updateEvents { it.clearMessagesForRoom(sessionId, roomId) }
|
||||
}
|
||||
actionIds.dismissSummary ->
|
||||
notificationDrawerManager.clearAllEvents(sessionId)
|
||||
actionIds.markRoomRead ->
|
||||
intent.getStringExtra(KEY_ROOM_ID)?.let { roomId ->
|
||||
intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()?.let { roomId ->
|
||||
notificationDrawerManager.updateEvents { it.clearMessagesForRoom(sessionId, roomId) }
|
||||
handleMarkAsRead(sessionId, roomId)
|
||||
}
|
||||
actionIds.join -> {
|
||||
intent.getStringExtra(KEY_ROOM_ID)?.let { roomId ->
|
||||
intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()?.let { roomId ->
|
||||
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(sessionId, roomId) }
|
||||
handleJoinRoom(sessionId, roomId)
|
||||
}
|
||||
}
|
||||
actionIds.reject -> {
|
||||
intent.getStringExtra(KEY_ROOM_ID)?.let { roomId ->
|
||||
intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()?.let { roomId ->
|
||||
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(sessionId, roomId) }
|
||||
handleRejectRoom(sessionId, roomId)
|
||||
}
|
||||
@@ -76,7 +81,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleJoinRoom(sessionId: String, roomId: String) {
|
||||
private fun handleJoinRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
/*
|
||||
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||
val room = session.getRoom(roomId)
|
||||
@@ -93,7 +98,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
||||
*/
|
||||
}
|
||||
|
||||
private fun handleRejectRoom(sessionId: String, roomId: String) {
|
||||
private fun handleRejectRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
/*
|
||||
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||
session.coroutineScope.launch {
|
||||
@@ -104,7 +109,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
||||
*/
|
||||
}
|
||||
|
||||
private fun handleMarkAsRead(sessionId: String, roomId: String) {
|
||||
private fun handleMarkAsRead(sessionId: SessionId, roomId: RoomId) {
|
||||
/*
|
||||
activeSessionHolder.getActiveSession().let { session ->
|
||||
val room = session.getRoom(roomId)
|
||||
@@ -120,11 +125,11 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
||||
|
||||
private fun handleSmartReply(intent: Intent, context: Context) {
|
||||
val message = getReplyMessage(intent)
|
||||
val sessionId = intent.getStringExtra(KEY_SESSION_ID)
|
||||
val roomId = intent.getStringExtra(KEY_ROOM_ID)
|
||||
val threadId = intent.getStringExtra(KEY_THREAD_ID)
|
||||
val sessionId = intent.getStringExtra(KEY_SESSION_ID)?.asSessionId()
|
||||
val roomId = intent.getStringExtra(KEY_ROOM_ID)?.asRoomId()
|
||||
val threadId = intent.getStringExtra(KEY_THREAD_ID)?.asThreadId()
|
||||
|
||||
if (message.isNullOrBlank() || roomId.isNullOrBlank()) {
|
||||
if (message.isNullOrBlank() || roomId == null) {
|
||||
// ignore this event
|
||||
// Can this happen? should we update notification?
|
||||
return
|
||||
|
||||
@@ -25,6 +25,9 @@ import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.push.api.store.PushDataStore
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
@@ -86,13 +89,13 @@ class NotificationDrawerManager @Inject constructor(
|
||||
is AppNavigationState.Space -> {}
|
||||
is AppNavigationState.Room -> {
|
||||
// Cleanup notification for current room
|
||||
onEnteringRoom(appNavigationState.parentSpace.parentSession.sessionId.value, appNavigationState.roomId.value)
|
||||
onEnteringRoom(appNavigationState.parentSpace.parentSession.sessionId, appNavigationState.roomId)
|
||||
}
|
||||
is AppNavigationState.Thread -> {
|
||||
onEnteringThread(
|
||||
appNavigationState.parentRoom.parentSpace.parentSession.sessionId.value,
|
||||
appNavigationState.parentRoom.roomId.value,
|
||||
appNavigationState.threadId.value
|
||||
appNavigationState.parentRoom.parentSpace.parentSession.sessionId,
|
||||
appNavigationState.parentRoom.roomId,
|
||||
appNavigationState.threadId
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -136,7 +139,7 @@ class NotificationDrawerManager @Inject constructor(
|
||||
/**
|
||||
* Clear all known events and refresh the notification drawer.
|
||||
*/
|
||||
fun clearAllEvents(sessionId: String) {
|
||||
fun clearAllEvents(sessionId: SessionId) {
|
||||
updateEvents { it.clearMessagesForSession(sessionId) }
|
||||
}
|
||||
|
||||
@@ -144,7 +147,7 @@ class NotificationDrawerManager @Inject constructor(
|
||||
* Should be called when the application is currently opened and showing timeline for the given roomId.
|
||||
* Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room.
|
||||
*/
|
||||
private fun onEnteringRoom(sessionId: String, roomId: String) {
|
||||
private fun onEnteringRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
updateEvents {
|
||||
it.clearMessagesForRoom(sessionId, roomId)
|
||||
}
|
||||
@@ -154,7 +157,7 @@ class NotificationDrawerManager @Inject constructor(
|
||||
* Should be called when the application is currently opened and showing timeline for the given threadId.
|
||||
* Used to ignore events related to that thread (no need to display notification) and clean any existing notification on this room.
|
||||
*/
|
||||
private fun onEnteringThread(sessionId: String, roomId: String, threadId: String) {
|
||||
private fun onEnteringThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) {
|
||||
updateEvents {
|
||||
it.clearMessagesForThread(sessionId, roomId, threadId)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import io.element.android.libraries.core.cache.CircularCache
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
@@ -30,10 +34,10 @@ data class NotificationEventQueue constructor(
|
||||
* Acts as a notification debouncer to stop already dismissed push notifications from
|
||||
* displaying again when the /sync response is delayed.
|
||||
*/
|
||||
private val seenEventIds: CircularCache<String>
|
||||
private val seenEventIds: CircularCache<EventId>
|
||||
) {
|
||||
|
||||
fun markRedacted(eventIds: List<String>) {
|
||||
fun markRedacted(eventIds: List<EventId>) {
|
||||
eventIds.forEach { redactedId ->
|
||||
queue.replace(redactedId) {
|
||||
when (it) {
|
||||
@@ -45,7 +49,8 @@ data class NotificationEventQueue constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun syncRoomEvents(roomsLeft: Collection<String>, roomsJoined: Collection<String>) {
|
||||
// TODO EAx call this
|
||||
fun syncRoomEvents(roomsLeft: Collection<RoomId>, roomsJoined: Collection<RoomId>) {
|
||||
if (roomsLeft.isNotEmpty() || roomsJoined.isNotEmpty()) {
|
||||
queue.removeAll {
|
||||
when (it) {
|
||||
@@ -125,30 +130,30 @@ data class NotificationEventQueue constructor(
|
||||
)
|
||||
}
|
||||
|
||||
fun clearMemberShipNotificationForRoom(sessionId: String, roomId: String) {
|
||||
fun clearMemberShipNotificationForRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
Timber.d("clearMemberShipOfRoom $sessionId, $roomId")
|
||||
queue.removeAll { it is InviteNotifiableEvent && it.sessionId == sessionId && it.roomId == roomId }
|
||||
queue.removeAll { it is InviteNotifiableEvent && it.sessionId == sessionId && it.roomId == roomId }
|
||||
}
|
||||
|
||||
fun clearMessagesForSession(sessionId: String) {
|
||||
fun clearMessagesForSession(sessionId: SessionId) {
|
||||
Timber.d("clearMessagesForSession $sessionId")
|
||||
queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId}
|
||||
queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId }
|
||||
}
|
||||
|
||||
fun clearMessagesForRoom(sessionId: String, roomId: String) {
|
||||
fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
Timber.d("clearMessageEventOfRoom $sessionId, $roomId")
|
||||
queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId }
|
||||
queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId }
|
||||
}
|
||||
|
||||
fun clearMessagesForThread(sessionId: String, roomId: String, threadId: String) {
|
||||
fun clearMessagesForThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) {
|
||||
Timber.d("clearMessageEventOfThread $sessionId, $roomId, $threadId")
|
||||
queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId && it.threadId == threadId }
|
||||
queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId && it.threadId == threadId }
|
||||
}
|
||||
|
||||
fun rawEvents(): List<NotifiableEvent> = queue
|
||||
}
|
||||
|
||||
private fun MutableList<NotifiableEvent>.replace(eventId: String, block: (NotifiableEvent) -> NotifiableEvent) {
|
||||
private fun MutableList<NotifiableEvent>.replace(eventId: EventId, block: (NotifiableEvent) -> NotifiableEvent) {
|
||||
val indexToReplace = indexOfFirst { it.eventId == eventId }
|
||||
if (indexToReplace == -1) {
|
||||
return
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import android.app.Notification
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
|
||||
@@ -30,8 +32,8 @@ class NotificationFactory @Inject constructor(
|
||||
private val summaryGroupMessageCreator: SummaryGroupMessageCreator
|
||||
) {
|
||||
|
||||
fun Map<String, ProcessedMessageEvents>.toNotifications(
|
||||
sessionId: String,
|
||||
fun Map<RoomId, ProcessedMessageEvents>.toNotifications(
|
||||
sessionId: SessionId,
|
||||
myUserDisplayName: String,
|
||||
myUserAvatarUrl: String?
|
||||
): List<RoomNotification> {
|
||||
@@ -62,11 +64,11 @@ class NotificationFactory @Inject constructor(
|
||||
fun List<ProcessedEvent<InviteNotifiableEvent>>.toNotifications(): List<OneShotNotification> {
|
||||
return map { (processed, event) ->
|
||||
when (processed) {
|
||||
ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.roomId)
|
||||
ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.roomId.value)
|
||||
ProcessedEvent.Type.KEEP -> OneShotNotification.Append(
|
||||
notificationUtils.buildRoomInvitationNotification(event),
|
||||
OneShotNotification.Append.Meta(
|
||||
key = event.roomId,
|
||||
key = event.roomId.value,
|
||||
summaryLine = event.description,
|
||||
isNoisy = event.noisy,
|
||||
timestamp = event.timestamp
|
||||
@@ -80,11 +82,11 @@ class NotificationFactory @Inject constructor(
|
||||
fun List<ProcessedEvent<SimpleNotifiableEvent>>.toNotifications(): List<OneShotNotification> {
|
||||
return map { (processed, event) ->
|
||||
when (processed) {
|
||||
ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.eventId)
|
||||
ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.eventId.value)
|
||||
ProcessedEvent.Type.KEEP -> OneShotNotification.Append(
|
||||
notificationUtils.buildSimpleEventNotification(event),
|
||||
OneShotNotification.Append.Meta(
|
||||
key = event.eventId,
|
||||
key = event.eventId.value,
|
||||
summaryLine = event.description,
|
||||
isNoisy = event.noisy,
|
||||
timestamp = event.timestamp
|
||||
@@ -95,7 +97,7 @@ class NotificationFactory @Inject constructor(
|
||||
}
|
||||
|
||||
fun createSummaryNotification(
|
||||
sessionId: String,
|
||||
sessionId: SessionId,
|
||||
roomNotifications: List<RoomNotification>,
|
||||
invitationNotifications: List<OneShotNotification>,
|
||||
simpleNotifications: List<OneShotNotification>,
|
||||
@@ -120,10 +122,10 @@ class NotificationFactory @Inject constructor(
|
||||
}
|
||||
|
||||
sealed interface RoomNotification {
|
||||
data class Removed(val roomId: String) : RoomNotification
|
||||
data class Removed(val roomId: RoomId) : RoomNotification
|
||||
data class Message(val notification: Notification, val meta: Meta) : RoomNotification {
|
||||
data class Meta(
|
||||
val roomId: String,
|
||||
val roomId: RoomId,
|
||||
val summaryLine: CharSequence,
|
||||
val messageCount: Int,
|
||||
val latestTimestamp: Long,
|
||||
|
||||
@@ -18,27 +18,28 @@ package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import javax.inject.Inject
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
class NotificationIdProvider @Inject constructor() {
|
||||
fun getSummaryNotificationId(sessionId: String): Int {
|
||||
fun getSummaryNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + SUMMARY_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
fun getRoomMessagesNotificationId(sessionId: String): Int {
|
||||
fun getRoomMessagesNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + ROOM_MESSAGES_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
fun getRoomEventNotificationId(sessionId: String): Int {
|
||||
fun getRoomEventNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + ROOM_EVENT_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
fun getRoomInvitationNotificationId(sessionId: String): Int {
|
||||
fun getRoomInvitationNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + ROOM_INVITATION_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
private fun getOffset(sessionId: String): Int {
|
||||
private fun getOffset(sessionId: SessionId): Int {
|
||||
// TODO EAx multi account: return different value for users and persist data
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
@@ -31,7 +33,7 @@ class NotificationRenderer @Inject constructor(
|
||||
|
||||
@WorkerThread
|
||||
fun render(
|
||||
sessionId: String,
|
||||
sessionId: SessionId,
|
||||
myUserDisplayName: String,
|
||||
myUserAvatarUrl: String?,
|
||||
useCompleteNotificationFormat: Boolean,
|
||||
@@ -60,12 +62,12 @@ class NotificationRenderer @Inject constructor(
|
||||
when (wrapper) {
|
||||
is RoomNotification.Removed -> {
|
||||
Timber.d("Removing room messages notification ${wrapper.roomId}")
|
||||
notificationDisplayer.cancelNotificationMessage(wrapper.roomId, notificationIdProvider.getRoomMessagesNotificationId(sessionId))
|
||||
notificationDisplayer.cancelNotificationMessage(wrapper.roomId.value, notificationIdProvider.getRoomMessagesNotificationId(sessionId))
|
||||
}
|
||||
is RoomNotification.Message -> if (useCompleteNotificationFormat) {
|
||||
Timber.d("Updating room messages notification ${wrapper.meta.roomId}")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
wrapper.meta.roomId,
|
||||
wrapper.meta.roomId.value,
|
||||
notificationIdProvider.getRoomMessagesNotificationId(sessionId),
|
||||
wrapper.notification
|
||||
)
|
||||
@@ -125,7 +127,7 @@ class NotificationRenderer @Inject constructor(
|
||||
}
|
||||
|
||||
private fun List<ProcessedEvent<NotifiableEvent>>.groupByType(): GroupedNotificationEvents {
|
||||
val roomIdToEventMap: MutableMap<String, MutableList<ProcessedEvent<NotifiableMessageEvent>>> = LinkedHashMap()
|
||||
val roomIdToEventMap: MutableMap<RoomId, MutableList<ProcessedEvent<NotifiableMessageEvent>>> = LinkedHashMap()
|
||||
val simpleEvents: MutableList<ProcessedEvent<SimpleNotifiableEvent>> = ArrayList()
|
||||
val invitationEvents: MutableList<ProcessedEvent<InviteNotifiableEvent>> = ArrayList()
|
||||
forEach {
|
||||
@@ -145,7 +147,7 @@ private fun List<ProcessedEvent<NotifiableEvent>>.groupByType(): GroupedNotifica
|
||||
private fun <T : NotifiableEvent> ProcessedEvent<NotifiableEvent>.castedToEventType(): ProcessedEvent<T> = this as ProcessedEvent<T>
|
||||
|
||||
data class GroupedNotificationEvents(
|
||||
val roomEvents: Map<String, List<ProcessedEvent<NotifiableMessageEvent>>>,
|
||||
val roomEvents: Map<RoomId, List<ProcessedEvent<NotifiableMessageEvent>>>,
|
||||
val simpleEvents: List<ProcessedEvent<SimpleNotifiableEvent>>,
|
||||
val invitationEvents: List<ProcessedEvent<InviteNotifiableEvent>>
|
||||
)
|
||||
|
||||
@@ -44,6 +44,9 @@ import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.push.impl.intent.IntentProvider
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
@@ -215,7 +218,7 @@ class NotificationUtils @Inject constructor(
|
||||
fun buildMessagesListNotification(
|
||||
messageStyle: NotificationCompat.MessagingStyle,
|
||||
roomInfo: RoomEventGroupInfo,
|
||||
threadId: String?,
|
||||
threadId: ThreadId?,
|
||||
largeIcon: Bitmap?,
|
||||
lastMessageTimestamp: Long,
|
||||
senderDisplayNameForReplyCompat: String?,
|
||||
@@ -244,7 +247,7 @@ class NotificationUtils @Inject constructor(
|
||||
// that can be displayed in not disturb mode if white listed (the later will need compat28.x)
|
||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
// ID of the corresponding shortcut, for conversation features under API 30+
|
||||
.setShortcutId(roomInfo.roomId)
|
||||
.setShortcutId(roomInfo.roomId.value)
|
||||
// Title for API < 16 devices.
|
||||
.setContentTitle(roomInfo.roomDisplayName)
|
||||
// Content for API < 16 devices.
|
||||
@@ -259,7 +262,7 @@ class NotificationUtils @Inject constructor(
|
||||
)
|
||||
// Auto-bundling is enabled for 4 or more notifications on API 24+ (N+)
|
||||
// devices and all Wear devices. But we want a custom grouping, so we specify the groupID
|
||||
.setGroup(roomInfo.sessionId)
|
||||
.setGroup(roomInfo.sessionId.value)
|
||||
// In order to avoid notification making sound twice (due to the summary notification)
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
||||
.setSmallIcon(smallIcon)
|
||||
@@ -359,7 +362,7 @@ class NotificationUtils @Inject constructor(
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentTitle(inviteNotifiableEvent.roomName ?: buildMeta.applicationName)
|
||||
.setContentText(inviteNotifiableEvent.description)
|
||||
.setGroup(inviteNotifiableEvent.sessionId)
|
||||
.setGroup(inviteNotifiableEvent.sessionId.value)
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
||||
.setSmallIcon(smallIcon)
|
||||
.setColor(accentColor)
|
||||
@@ -446,7 +449,7 @@ class NotificationUtils @Inject constructor(
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentTitle(buildMeta.applicationName)
|
||||
.setContentText(simpleNotifiableEvent.description)
|
||||
.setGroup(simpleNotifiableEvent.sessionId)
|
||||
.setGroup(simpleNotifiableEvent.sessionId.value)
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
||||
.setSmallIcon(smallIcon)
|
||||
.setColor(accentColor)
|
||||
@@ -477,7 +480,7 @@ class NotificationUtils @Inject constructor(
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun buildOpenRoomIntent(sessionId: String, roomId: String): PendingIntent? {
|
||||
private fun buildOpenRoomIntent(sessionId: SessionId, roomId: RoomId): PendingIntent? {
|
||||
val roomIntent = intentProvider.getIntent(sessionId = sessionId, roomId = roomId, threadId = null)
|
||||
roomIntent.action = actionIds.tapToView
|
||||
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
|
||||
@@ -491,7 +494,7 @@ class NotificationUtils @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildOpenThreadIntent(roomInfo: RoomEventGroupInfo, threadId: String?): PendingIntent? {
|
||||
private fun buildOpenThreadIntent(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): PendingIntent? {
|
||||
val sessionId = roomInfo.sessionId
|
||||
val roomId = roomInfo.roomId
|
||||
val threadIntentTap = intentProvider.getIntent(sessionId = sessionId, roomId = roomId, threadId = threadId)
|
||||
@@ -507,7 +510,7 @@ class NotificationUtils @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildOpenHomePendingIntentForSummary(sessionId: String): PendingIntent {
|
||||
private fun buildOpenHomePendingIntentForSummary(sessionId: SessionId): PendingIntent {
|
||||
val intent = intentProvider.getIntent(sessionId = sessionId, roomId = null, threadId = null)
|
||||
intent.data = createIgnoredUri("tapSummary?$sessionId")
|
||||
return PendingIntent.getActivity(
|
||||
@@ -526,9 +529,9 @@ class NotificationUtils @Inject constructor(
|
||||
it will be more appropriate to use an activity. Since you have to provide your own UI.
|
||||
*/
|
||||
private fun buildQuickReplyIntent(
|
||||
sessionId: String,
|
||||
roomId: String,
|
||||
threadId: String?,
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId,
|
||||
threadId: ThreadId?,
|
||||
senderName: String?
|
||||
): PendingIntent? {
|
||||
val intent: Intent
|
||||
@@ -573,7 +576,7 @@ class NotificationUtils @Inject constructor(
|
||||
* Build the summary notification.
|
||||
*/
|
||||
fun buildSummaryListNotification(
|
||||
sessionId: String,
|
||||
sessionId: SessionId,
|
||||
style: NotificationCompat.InboxStyle?,
|
||||
compatSummary: String,
|
||||
noisy: Boolean,
|
||||
@@ -587,12 +590,12 @@ class NotificationUtils @Inject constructor(
|
||||
// used in compat < N, after summary is built based on child notifications
|
||||
.setWhen(lastMessageTimestamp)
|
||||
.setStyle(style)
|
||||
.setContentTitle(sessionId)
|
||||
.setContentTitle(sessionId.value)
|
||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
.setSmallIcon(smallIcon)
|
||||
// set content text to support devices running API level < 24
|
||||
.setContentText(compatSummary)
|
||||
.setGroup(sessionId)
|
||||
.setGroup(sessionId.value)
|
||||
// set this notification as the summary for the group
|
||||
.setGroupSummary(true)
|
||||
.setColor(accentColor)
|
||||
@@ -616,7 +619,7 @@ class NotificationUtils @Inject constructor(
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getDismissSummaryPendingIntent(sessionId: String): PendingIntent {
|
||||
private fun getDismissSummaryPendingIntent(sessionId: SessionId): PendingIntent {
|
||||
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||
intent.action = actionIds.dismissSummary
|
||||
intent.data = createIgnoredUri("deleteSummary?$sessionId")
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
/**
|
||||
* Data class to hold information about a group of notifications for a room.
|
||||
*/
|
||||
data class RoomEventGroupInfo(
|
||||
val sessionId: String,
|
||||
val roomId: String,
|
||||
val sessionId: SessionId,
|
||||
val roomId: RoomId,
|
||||
val roomDisplayName: String,
|
||||
val isDirect: Boolean = false
|
||||
) {
|
||||
|
||||
@@ -19,6 +19,8 @@ package io.element.android.libraries.push.impl.notifications
|
||||
import android.graphics.Bitmap
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.Person
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
@@ -34,9 +36,9 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||
) {
|
||||
|
||||
fun createRoomMessage(
|
||||
sessionId: String,
|
||||
sessionId: SessionId,
|
||||
events: List<NotifiableMessageEvent>,
|
||||
roomId: String,
|
||||
roomId: RoomId,
|
||||
userDisplayName: String,
|
||||
userAvatarUrl: String?
|
||||
): RoomNotification.Message {
|
||||
@@ -47,7 +49,7 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||
Person.Builder()
|
||||
.setName(userDisplayName)
|
||||
.setIcon(bitmapLoader.getUserIcon(userAvatarUrl))
|
||||
.setKey(lastKnownRoomEvent.sessionId)
|
||||
.setKey(lastKnownRoomEvent.sessionId.value)
|
||||
.build()
|
||||
).also {
|
||||
it.conversationTitle = roomName.takeIf { roomIsGroup }
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import android.app.Notification
|
||||
import androidx.core.app.NotificationCompat
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import javax.inject.Inject
|
||||
@@ -42,7 +43,7 @@ class SummaryGroupMessageCreator @Inject constructor(
|
||||
) {
|
||||
|
||||
fun createSummaryNotification(
|
||||
sessionId: String,
|
||||
sessionId: SessionId,
|
||||
roomNotifications: List<RoomNotification.Message.Meta>,
|
||||
invitationNotifications: List<OneShotNotification.Append.Meta>,
|
||||
simpleNotifications: List<OneShotNotification.Append.Meta>,
|
||||
@@ -92,7 +93,7 @@ class SummaryGroupMessageCreator @Inject constructor(
|
||||
}
|
||||
|
||||
private fun processSimpleGroupSummary(
|
||||
sessionId: String,
|
||||
sessionId: SessionId,
|
||||
summaryIsNoisy: Boolean,
|
||||
messageEventsCount: Int,
|
||||
simpleEventsCount: Int,
|
||||
|
||||
@@ -15,11 +15,15 @@
|
||||
*/
|
||||
package io.element.android.libraries.push.impl.notifications.model
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
data class InviteNotifiableEvent(
|
||||
override val sessionId: String,
|
||||
override val roomId: String,
|
||||
override val eventId: String,
|
||||
override val editedEventId: String?,
|
||||
override val sessionId: SessionId,
|
||||
override val roomId: RoomId,
|
||||
override val eventId: EventId,
|
||||
override val editedEventId: EventId?,
|
||||
override val canBeReplaced: Boolean,
|
||||
val roomName: String?,
|
||||
val noisy: Boolean,
|
||||
|
||||
@@ -15,16 +15,19 @@
|
||||
*/
|
||||
package io.element.android.libraries.push.impl.notifications.model
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Parent interface for all events which can be displayed as a Notification.
|
||||
*/
|
||||
sealed interface NotifiableEvent : Serializable {
|
||||
val sessionId: String
|
||||
val roomId: String
|
||||
val eventId: String
|
||||
val editedEventId: String?
|
||||
val sessionId: SessionId
|
||||
val roomId: RoomId
|
||||
val eventId: EventId
|
||||
val editedEventId: EventId?
|
||||
|
||||
// Used to know if event should be replaced with the one coming from eventstream
|
||||
val canBeReplaced: Boolean
|
||||
|
||||
@@ -16,16 +16,20 @@
|
||||
package io.element.android.libraries.push.impl.notifications.model
|
||||
|
||||
import android.net.Uri
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.services.appnavstate.api.AppNavigationState
|
||||
import io.element.android.services.appnavstate.api.currentRoomId
|
||||
import io.element.android.services.appnavstate.api.currentSessionId
|
||||
import io.element.android.services.appnavstate.api.currentThreadId
|
||||
|
||||
data class NotifiableMessageEvent(
|
||||
override val sessionId: String,
|
||||
override val roomId: String,
|
||||
override val eventId: String,
|
||||
override val editedEventId: String?,
|
||||
override val sessionId: SessionId,
|
||||
override val roomId: RoomId,
|
||||
override val eventId: EventId,
|
||||
override val editedEventId: EventId?,
|
||||
override val canBeReplaced: Boolean,
|
||||
val noisy: Boolean,
|
||||
val timestamp: Long,
|
||||
@@ -35,7 +39,7 @@ data class NotifiableMessageEvent(
|
||||
// We cannot use Uri? type here, as that could trigger a
|
||||
// NotSerializableException when persisting this to storage
|
||||
val imageUriString: String?,
|
||||
val threadId: String?,
|
||||
val threadId: ThreadId?,
|
||||
val roomName: String?,
|
||||
val roomIsDirect: Boolean = false,
|
||||
val roomAvatarPath: String? = null,
|
||||
@@ -59,9 +63,9 @@ data class NotifiableMessageEvent(
|
||||
fun NotifiableMessageEvent.shouldIgnoreMessageEventInRoom(
|
||||
appNavigationState: AppNavigationState?
|
||||
): Boolean {
|
||||
val currentSessionId = appNavigationState?.currentSessionId()?.value ?: return false
|
||||
return when (val currentRoomId = appNavigationState.currentRoomId()?.value) {
|
||||
val currentSessionId = appNavigationState?.currentSessionId() ?: return false
|
||||
return when (val currentRoomId = appNavigationState.currentRoomId()) {
|
||||
null -> false
|
||||
else -> sessionId == currentSessionId && roomId == currentRoomId && threadId == appNavigationState.currentThreadId()?.value
|
||||
else -> sessionId == currentSessionId && roomId == currentRoomId && threadId == appNavigationState.currentThreadId()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,15 @@
|
||||
*/
|
||||
package io.element.android.libraries.push.impl.notifications.model
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
data class SimpleNotifiableEvent(
|
||||
override val sessionId: String,
|
||||
override val roomId: String,
|
||||
override val eventId: String,
|
||||
override val editedEventId: String?,
|
||||
override val sessionId: SessionId,
|
||||
override val roomId: RoomId,
|
||||
override val eventId: EventId,
|
||||
override val editedEventId: EventId?,
|
||||
val noisy: Boolean,
|
||||
val title: String,
|
||||
val description: String,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package io.element.android.libraries.push.impl.push
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
/**
|
||||
* Represent parsed data that the app has received from a Push content.
|
||||
*
|
||||
@@ -24,8 +27,8 @@ package io.element.android.libraries.push.impl.push
|
||||
* @property unread Number of unread message.
|
||||
*/
|
||||
data class PushData(
|
||||
val eventId: String?,
|
||||
val roomId: String?,
|
||||
val eventId: EventId?,
|
||||
val roomId: RoomId?,
|
||||
val unread: Int?,
|
||||
val clientSecret: String?,
|
||||
)
|
||||
|
||||
@@ -119,7 +119,7 @@ class PushHandler @Inject constructor(
|
||||
// Get userId from client secret
|
||||
pushClientSecret.getUserIdFromSecret(clientSecret)
|
||||
} ?: run {
|
||||
matrixAuthenticationService.getLatestSessionId()?.value
|
||||
matrixAuthenticationService.getLatestSessionId()
|
||||
}
|
||||
|
||||
if (userId == null) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.element.android.libraries.push.impl.pushgateway
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.network.RetrofitFactory
|
||||
import io.element.android.libraries.push.api.gateway.PushGatewayFailure
|
||||
import javax.inject.Inject
|
||||
@@ -26,7 +27,7 @@ class PushGatewayNotifyRequest @Inject constructor(
|
||||
val url: String,
|
||||
val appId: String,
|
||||
val pushKey: String,
|
||||
val eventId: String
|
||||
val eventId: EventId
|
||||
)
|
||||
|
||||
suspend fun execute(params: Params) {
|
||||
@@ -38,7 +39,7 @@ class PushGatewayNotifyRequest @Inject constructor(
|
||||
val response = sygnalApi.notify(
|
||||
PushGatewayNotifyBody(
|
||||
PushGatewayNotification(
|
||||
eventId = params.eventId,
|
||||
eventId = params.eventId.value,
|
||||
devices = listOf(
|
||||
PushGatewayDevice(
|
||||
params.appId,
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package io.element.android.libraries.push.impl.unifiedpush
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.MatrixPatterns
|
||||
import io.element.android.libraries.matrix.api.core.asEventId
|
||||
import io.element.android.libraries.matrix.api.core.asRoomId
|
||||
import io.element.android.libraries.push.impl.push.PushData
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -55,8 +57,8 @@ data class PushDataUnifiedPushCounts(
|
||||
)
|
||||
|
||||
fun PushDataUnifiedPush.toPushData() = PushData(
|
||||
eventId = notification?.eventId?.takeIf { MatrixPatterns.isEventId(it) },
|
||||
roomId = notification?.roomId?.takeIf { MatrixPatterns.isRoomId(it) },
|
||||
eventId = notification?.eventId?.takeIf { MatrixPatterns.isEventId(it) }?.asEventId(),
|
||||
roomId = notification?.roomId?.takeIf { MatrixPatterns.isRoomId(it) }?.asRoomId(),
|
||||
unread = notification?.counts?.unread,
|
||||
clientSecret = null // TODO EAx check how client secret will be sent through UnifiedPush
|
||||
)
|
||||
|
||||
@@ -16,24 +16,26 @@
|
||||
|
||||
package io.element.android.libraries.push.impl.clientsecret
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
class InMemoryPushClientSecretStore : PushClientSecretStore {
|
||||
private val secrets = mutableMapOf<String, String>()
|
||||
private val secrets = mutableMapOf<SessionId, String>()
|
||||
|
||||
fun getSecrets(): Map<String, String> = secrets
|
||||
fun getSecrets(): Map<SessionId, String> = secrets
|
||||
|
||||
override suspend fun storeSecret(userId: String, clientSecret: String) {
|
||||
override suspend fun storeSecret(userId: SessionId, clientSecret: String) {
|
||||
secrets[userId] = clientSecret
|
||||
}
|
||||
|
||||
override suspend fun getSecret(userId: String): String? {
|
||||
override suspend fun getSecret(userId: SessionId): String? {
|
||||
return secrets[userId]
|
||||
}
|
||||
|
||||
override suspend fun resetSecret(userId: String) {
|
||||
override suspend fun resetSecret(userId: SessionId) {
|
||||
secrets.remove(userId)
|
||||
}
|
||||
|
||||
override suspend fun getUserIdFromSecret(clientSecret: String): String? {
|
||||
override suspend fun getUserIdFromSecret(clientSecret: String): SessionId? {
|
||||
return secrets.keys.firstOrNull { secrets[it] == clientSecret }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,13 @@
|
||||
package io.element.android.libraries.push.impl.clientsecret
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
private const val A_USER_ID_0 = "A_USER_ID_0"
|
||||
private const val A_USER_ID_1 = "A_USER_ID_1"
|
||||
private val A_USER_ID_0 = SessionId("A_USER_ID_0")
|
||||
private val A_USER_ID_1 = SessionId("A_USER_ID_1")
|
||||
|
||||
private const val A_UNKNOWN_SECRET = "A_UNKNOWN_SECRET"
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_SPACE_ID
|
||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeOutdatedEventDetector
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.services.appnavstate.test.anAppNavigationState
|
||||
import org.junit.Test
|
||||
|
||||
private val NOT_VIEWING_A_ROOM = anAppNavigationState()
|
||||
private val VIEWING_A_ROOM = anAppNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_ID)
|
||||
private val VIEWING_A_THREAD = anAppNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_ID, A_THREAD_ID)
|
||||
|
||||
class NotifiableEventProcessorTest {
|
||||
|
||||
private val outdatedDetector = FakeOutdatedEventDetector()
|
||||
private val eventProcessor = NotifiableEventProcessor(outdatedDetector.instance)
|
||||
|
||||
@Test
|
||||
fun `given simple events when processing then keep simple events`() {
|
||||
val events = listOf(
|
||||
aSimpleNotifiableEvent(eventId = AN_EVENT_ID),
|
||||
aSimpleNotifiableEvent(eventId = AN_EVENT_ID_2)
|
||||
)
|
||||
|
||||
val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.KEEP to events[0],
|
||||
ProcessedEvent.Type.KEEP to events[1]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given redacted simple event when processing then remove redaction event`() {
|
||||
val events = listOf(aSimpleNotifiableEvent(eventId = AN_EVENT_ID, type = "m.room.redaction"))
|
||||
|
||||
val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.REMOVE to events[0]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given invites are not auto accepted when processing then keep invitation events`() {
|
||||
val events = listOf(
|
||||
anInviteNotifiableEvent(roomId = A_ROOM_ID),
|
||||
anInviteNotifiableEvent(roomId = A_ROOM_ID_2)
|
||||
)
|
||||
|
||||
val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.KEEP to events[0],
|
||||
ProcessedEvent.Type.KEEP to events[1]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given out of date message event when processing then removes message event`() {
|
||||
val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID))
|
||||
outdatedDetector.givenEventIsOutOfDate(events[0])
|
||||
|
||||
val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.REMOVE to events[0],
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given in date message event when processing then keep message event`() {
|
||||
val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID))
|
||||
outdatedDetector.givenEventIsInDate(events[0])
|
||||
|
||||
val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.KEEP to events[0],
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given viewing the same room main timeline when processing main timeline message event then removes message`() {
|
||||
val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = null))
|
||||
|
||||
val result = eventProcessor.process(events, VIEWING_A_ROOM, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.REMOVE to events[0],
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given viewing the same thread timeline when processing thread message event then removes message`() {
|
||||
val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID))
|
||||
|
||||
val result = eventProcessor.process(events, VIEWING_A_THREAD, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.REMOVE to events[0],
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given viewing main timeline of the same room when processing thread timeline message event then keep message`() {
|
||||
val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID))
|
||||
outdatedDetector.givenEventIsInDate(events[0])
|
||||
|
||||
val result = eventProcessor.process(events, VIEWING_A_ROOM, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.KEEP to events[0],
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given viewing thread timeline of the same room when processing main timeline message event then keep message`() {
|
||||
val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID))
|
||||
outdatedDetector.givenEventIsInDate(events[0])
|
||||
|
||||
val result = eventProcessor.process(events, VIEWING_A_THREAD, renderedEvents = emptyList())
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.KEEP to events[0],
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given events are different to rendered events when processing then removes difference`() {
|
||||
val events = listOf(aSimpleNotifiableEvent(eventId = AN_EVENT_ID))
|
||||
val renderedEvents = listOf<ProcessedEvent<NotifiableEvent>>(
|
||||
ProcessedEvent(ProcessedEvent.Type.KEEP, events[0]),
|
||||
ProcessedEvent(ProcessedEvent.Type.KEEP, anInviteNotifiableEvent(eventId = AN_EVENT_ID_2))
|
||||
)
|
||||
|
||||
val result = eventProcessor.process(events, appNavigationState = NOT_VIEWING_A_ROOM, renderedEvents = renderedEvents)
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOfProcessedEvents(
|
||||
ProcessedEvent.Type.REMOVE to renderedEvents[1].event,
|
||||
ProcessedEvent.Type.KEEP to renderedEvents[0].event
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun listOfProcessedEvents(vararg event: Pair<ProcessedEvent.Type, NotifiableEvent>) = event.map {
|
||||
ProcessedEvent(it.first, it.second)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.core.cache.CircularCache
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import org.junit.Test
|
||||
|
||||
class NotificationEventQueueTest {
|
||||
|
||||
private val seenIdsCache = CircularCache.create<EventId>(5)
|
||||
|
||||
@Test
|
||||
fun `given events when redacting some then marks matching event ids as redacted`() {
|
||||
val queue = givenQueue(
|
||||
listOf(
|
||||
aSimpleNotifiableEvent(eventId = EventId("redacted-id-1")),
|
||||
aNotifiableMessageEvent(eventId = EventId("redacted-id-2")),
|
||||
anInviteNotifiableEvent(eventId = EventId("redacted-id-3")),
|
||||
aSimpleNotifiableEvent(eventId = EventId("kept-id")),
|
||||
)
|
||||
)
|
||||
|
||||
queue.markRedacted(listOf(EventId("redacted-id-1"), EventId("redacted-id-2"), EventId("redacted-id-3")))
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(
|
||||
listOf(
|
||||
aSimpleNotifiableEvent(eventId = EventId("redacted-id-1"), isRedacted = true),
|
||||
aNotifiableMessageEvent(eventId = EventId("redacted-id-2"), isRedacted = true),
|
||||
anInviteNotifiableEvent(eventId = EventId("redacted-id-3"), isRedacted = true),
|
||||
aSimpleNotifiableEvent(eventId = EventId("kept-id"), isRedacted = false),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given invite event when leaving invited room and syncing then removes event`() {
|
||||
val queue = givenQueue(listOf(anInviteNotifiableEvent(roomId = A_ROOM_ID)))
|
||||
val roomsLeft = listOf(A_ROOM_ID)
|
||||
|
||||
queue.syncRoomEvents(roomsLeft = roomsLeft, roomsJoined = emptyList())
|
||||
|
||||
assertThat(queue.rawEvents()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given invite event when joining invited room and syncing then removes event`() {
|
||||
val queue = givenQueue(listOf(anInviteNotifiableEvent(roomId = A_ROOM_ID)))
|
||||
val joinedRooms = listOf(A_ROOM_ID)
|
||||
|
||||
queue.syncRoomEvents(roomsLeft = emptyList(), roomsJoined = joinedRooms)
|
||||
|
||||
assertThat(queue.rawEvents()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given message event when leaving message room and syncing then removes event`() {
|
||||
val queue = givenQueue(listOf(aNotifiableMessageEvent(roomId = A_ROOM_ID)))
|
||||
val roomsLeft = listOf(A_ROOM_ID)
|
||||
|
||||
queue.syncRoomEvents(roomsLeft = roomsLeft, roomsJoined = emptyList())
|
||||
|
||||
assertThat(queue.rawEvents()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given events when syncing without rooms left or joined ids then does not change the events`() {
|
||||
val queue = givenQueue(
|
||||
listOf(
|
||||
aNotifiableMessageEvent(roomId = A_ROOM_ID),
|
||||
anInviteNotifiableEvent(roomId = A_ROOM_ID)
|
||||
)
|
||||
)
|
||||
|
||||
queue.syncRoomEvents(roomsLeft = emptyList(), roomsJoined = emptyList())
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(
|
||||
listOf(
|
||||
aNotifiableMessageEvent(roomId = A_ROOM_ID),
|
||||
anInviteNotifiableEvent(roomId = A_ROOM_ID)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given events then is not empty`() {
|
||||
val queue = givenQueue(listOf(aSimpleNotifiableEvent()))
|
||||
|
||||
assertThat(queue.isEmpty()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no events then is empty`() {
|
||||
val queue = givenQueue(emptyList())
|
||||
|
||||
assertThat(queue.isEmpty()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given events when clearing and adding then removes previous events and adds only new events`() {
|
||||
val queue = givenQueue(listOf(aSimpleNotifiableEvent()))
|
||||
|
||||
queue.clearAndAdd(listOf(anInviteNotifiableEvent()))
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(listOf(anInviteNotifiableEvent()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when clearing then is empty`() {
|
||||
val queue = givenQueue(listOf(aSimpleNotifiableEvent()))
|
||||
|
||||
queue.clear()
|
||||
|
||||
assertThat(queue.rawEvents()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no events when adding then adds event`() {
|
||||
val queue = givenQueue(listOf())
|
||||
|
||||
queue.add(aSimpleNotifiableEvent())
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(listOf(aSimpleNotifiableEvent()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no events when adding already seen event then ignores event`() {
|
||||
val queue = givenQueue(listOf())
|
||||
val notifiableEvent = aSimpleNotifiableEvent()
|
||||
seenIdsCache.put(notifiableEvent.eventId)
|
||||
|
||||
queue.add(notifiableEvent)
|
||||
|
||||
assertThat(queue.rawEvents()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given replaceable event when adding event with same id then updates existing event`() {
|
||||
val replaceableEvent = aSimpleNotifiableEvent(canBeReplaced = true)
|
||||
val updatedEvent = replaceableEvent.copy(title = "updated title", isUpdated = true)
|
||||
val queue = givenQueue(listOf(replaceableEvent))
|
||||
|
||||
queue.add(updatedEvent)
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(listOf(updatedEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given non replaceable event when adding event with same id then ignores event`() {
|
||||
val nonReplaceableEvent = aSimpleNotifiableEvent(canBeReplaced = false)
|
||||
val updatedEvent = nonReplaceableEvent.copy(title = "updated title")
|
||||
val queue = givenQueue(listOf(nonReplaceableEvent))
|
||||
|
||||
queue.add(updatedEvent)
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(listOf(nonReplaceableEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given event when adding new event with edited event id matching the existing event id then updates existing event`() {
|
||||
val editedEvent = aSimpleNotifiableEvent(eventId = EventId("id-to-edit"))
|
||||
val updatedEvent = editedEvent.copy(eventId = EventId("1"), editedEventId = EventId("id-to-edit"), title = "updated title", isUpdated = true)
|
||||
val queue = givenQueue(listOf(editedEvent))
|
||||
|
||||
queue.add(updatedEvent)
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(listOf(updatedEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given event when adding new event with edited event id matching the existing event edited id then updates existing event`() {
|
||||
val editedEvent = aSimpleNotifiableEvent(eventId = EventId("0"), editedEventId = EventId("id-to-edit"))
|
||||
val updatedEvent = editedEvent.copy(eventId = EventId("1"), editedEventId = EventId("id-to-edit"), title = "updated title", isUpdated = true)
|
||||
val queue = givenQueue(listOf(editedEvent))
|
||||
|
||||
queue.add(updatedEvent)
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(listOf(updatedEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when clearing membership notification then removes invite events with matching room id`() {
|
||||
val queue = givenQueue(
|
||||
listOf(
|
||||
anInviteNotifiableEvent(roomId = A_ROOM_ID),
|
||||
aNotifiableMessageEvent(roomId = A_ROOM_ID)
|
||||
)
|
||||
)
|
||||
|
||||
queue.clearMemberShipNotificationForRoom(A_SESSION_ID, A_ROOM_ID)
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(listOf(aNotifiableMessageEvent(roomId = A_ROOM_ID)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when clearing messages for room then removes message events with matching room id`() {
|
||||
val queue = givenQueue(
|
||||
listOf(
|
||||
anInviteNotifiableEvent(roomId = A_ROOM_ID),
|
||||
aNotifiableMessageEvent(roomId = A_ROOM_ID)
|
||||
)
|
||||
)
|
||||
|
||||
queue.clearMessagesForRoom(A_SESSION_ID, A_ROOM_ID)
|
||||
|
||||
assertThat(queue.rawEvents()).isEqualTo(listOf(anInviteNotifiableEvent(roomId = A_ROOM_ID)))
|
||||
}
|
||||
|
||||
private fun givenQueue(events: List<NotifiableEvent>) = NotificationEventQueue(events.toMutableList(), seenEventIds = seenIdsCache)
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationUtils
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
|
||||
import org.junit.Test
|
||||
|
||||
private val MY_AVATAR_URL: String? = null
|
||||
private val AN_INVITATION_EVENT = anInviteNotifiableEvent(roomId = A_ROOM_ID)
|
||||
private val A_SIMPLE_EVENT = aSimpleNotifiableEvent(eventId = AN_EVENT_ID)
|
||||
private val A_MESSAGE_EVENT = aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)
|
||||
|
||||
class NotificationFactoryTest {
|
||||
|
||||
private val notificationUtils = FakeNotificationUtils()
|
||||
private val roomGroupMessageCreator = FakeRoomGroupMessageCreator()
|
||||
private val summaryGroupMessageCreator = FakeSummaryGroupMessageCreator()
|
||||
|
||||
private val notificationFactory = NotificationFactory(
|
||||
notificationUtils.instance,
|
||||
roomGroupMessageCreator.instance,
|
||||
summaryGroupMessageCreator.instance
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given a room invitation when mapping to notification then is Append`() = testWith(notificationFactory) {
|
||||
val expectedNotification = notificationUtils.givenBuildRoomInvitationNotificationFor(AN_INVITATION_EVENT)
|
||||
val roomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, AN_INVITATION_EVENT))
|
||||
|
||||
val result = roomInvitation.toNotifications()
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOf(
|
||||
OneShotNotification.Append(
|
||||
notification = expectedNotification,
|
||||
meta = OneShotNotification.Append.Meta(
|
||||
key = A_ROOM_ID.value,
|
||||
summaryLine = AN_INVITATION_EVENT.description,
|
||||
isNoisy = AN_INVITATION_EVENT.noisy,
|
||||
timestamp = AN_INVITATION_EVENT.timestamp
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a missing event in room invitation when mapping to notification then is Removed`() = testWith(notificationFactory) {
|
||||
val missingEventRoomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, AN_INVITATION_EVENT))
|
||||
|
||||
val result = missingEventRoomInvitation.toNotifications()
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOf(
|
||||
OneShotNotification.Removed(
|
||||
key = A_ROOM_ID.value
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a simple event when mapping to notification then is Append`() = testWith(notificationFactory) {
|
||||
val expectedNotification = notificationUtils.givenBuildSimpleInvitationNotificationFor(A_SIMPLE_EVENT)
|
||||
val roomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_SIMPLE_EVENT))
|
||||
|
||||
val result = roomInvitation.toNotifications()
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOf(
|
||||
OneShotNotification.Append(
|
||||
notification = expectedNotification,
|
||||
meta = OneShotNotification.Append.Meta(
|
||||
key = AN_EVENT_ID.value,
|
||||
summaryLine = A_SIMPLE_EVENT.description,
|
||||
isNoisy = A_SIMPLE_EVENT.noisy,
|
||||
timestamp = AN_INVITATION_EVENT.timestamp
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a missing simple event when mapping to notification then is Removed`() = testWith(notificationFactory) {
|
||||
val missingEventRoomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, A_SIMPLE_EVENT))
|
||||
|
||||
val result = missingEventRoomInvitation.toNotifications()
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOf(
|
||||
OneShotNotification.Removed(
|
||||
key = AN_EVENT_ID.value
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given room with message when mapping to notification then delegates to room group message creator`() = testWith(notificationFactory) {
|
||||
val events = listOf(A_MESSAGE_EVENT)
|
||||
val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor(
|
||||
A_SESSION_ID, events, A_ROOM_ID, A_SESSION_ID.value, MY_AVATAR_URL
|
||||
)
|
||||
val roomWithMessage = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT)))
|
||||
|
||||
val result = roomWithMessage.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
||||
|
||||
assertThat(result).isEqualTo(listOf(expectedNotification))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a room with no events to display when mapping to notification then is Empty`() = testWith(notificationFactory) {
|
||||
val events = listOf(ProcessedEvent(ProcessedEvent.Type.REMOVE, A_MESSAGE_EVENT))
|
||||
val emptyRoom = mapOf(A_ROOM_ID to events)
|
||||
|
||||
val result = emptyRoom.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOf(
|
||||
RoomNotification.Removed(
|
||||
roomId = A_ROOM_ID
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a room with only redacted events when mapping to notification then is Empty`() = testWith(notificationFactory) {
|
||||
val redactedRoom = mapOf(A_ROOM_ID to listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(isRedacted = true))))
|
||||
|
||||
val result = redactedRoom.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
listOf(
|
||||
RoomNotification.Removed(
|
||||
roomId = A_ROOM_ID
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a room with redacted and non redacted message events when mapping to notification then redacted events are removed`() = testWith(
|
||||
notificationFactory
|
||||
) {
|
||||
val roomWithRedactedMessage = mapOf(
|
||||
A_ROOM_ID to listOf(
|
||||
ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(isRedacted = true)),
|
||||
ProcessedEvent(ProcessedEvent.Type.KEEP, A_MESSAGE_EVENT.copy(eventId = EventId("not-redacted")))
|
||||
)
|
||||
)
|
||||
val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("not-redacted")))
|
||||
val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor(
|
||||
A_SESSION_ID,
|
||||
withRedactedRemoved,
|
||||
A_ROOM_ID,
|
||||
A_SESSION_ID.value,
|
||||
MY_AVATAR_URL
|
||||
)
|
||||
|
||||
val result = roomWithRedactedMessage.toNotifications(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL)
|
||||
|
||||
assertThat(result).isEqualTo(listOf(expectedNotification))
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> testWith(receiver: T, block: T.() -> Unit) {
|
||||
receiver.block()
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import android.app.Notification
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationFactory
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
|
||||
private const val MY_USER_DISPLAY_NAME = "display-name"
|
||||
private const val MY_USER_AVATAR_URL = "avatar-url"
|
||||
private const val USE_COMPLETE_NOTIFICATION_FORMAT = true
|
||||
|
||||
private val AN_EVENT_LIST = listOf<ProcessedEvent<NotifiableEvent>>()
|
||||
private val A_PROCESSED_EVENTS = GroupedNotificationEvents(emptyMap(), emptyList(), emptyList())
|
||||
private val A_SUMMARY_NOTIFICATION = SummaryNotification.Update(mockk())
|
||||
private val A_REMOVE_SUMMARY_NOTIFICATION = SummaryNotification.Removed
|
||||
private val A_NOTIFICATION = mockk<Notification>()
|
||||
private val MESSAGE_META = RoomNotification.Message.Meta(
|
||||
summaryLine = "ignored", messageCount = 1, latestTimestamp = -1, roomId = A_ROOM_ID, shouldBing = false
|
||||
)
|
||||
private val ONE_SHOT_META = OneShotNotification.Append.Meta(key = "ignored", summaryLine = "ignored", isNoisy = false, timestamp = -1)
|
||||
|
||||
class NotificationRendererTest {
|
||||
|
||||
private val notificationDisplayer = FakeNotificationDisplayer()
|
||||
private val notificationFactory = FakeNotificationFactory()
|
||||
private val notificationIdProvider = NotificationIdProvider()
|
||||
|
||||
private val notificationRenderer = NotificationRenderer(
|
||||
notificationIdProvider = notificationIdProvider,
|
||||
notificationDisplayer = notificationDisplayer.instance,
|
||||
notificationFactory = notificationFactory.instance,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given no notifications when rendering then cancels summary notification`() {
|
||||
givenNoNotifications()
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifySummaryCancelled()
|
||||
notificationDisplayer.verifyNoOtherInteractions()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given last room message group notification is removed when rendering then remove the summary and then remove message notification`() {
|
||||
givenNotifications(roomNotifications = listOf(RoomNotification.Removed(A_ROOM_ID)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID))
|
||||
cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a room message group notification is removed when rendering then remove the message notification and update summary`() {
|
||||
givenNotifications(roomNotifications = listOf(RoomNotification.Removed(A_ROOM_ID)))
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID))
|
||||
showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a room message group notification is added when rendering then show the message notification and update summary`() {
|
||||
givenNotifications(
|
||||
roomNotifications = listOf(
|
||||
RoomNotification.Message(
|
||||
A_NOTIFICATION,
|
||||
MESSAGE_META
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
showNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), A_NOTIFICATION)
|
||||
showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given last simple notification is removed when rendering then remove the summary and then remove simple notification`() {
|
||||
givenNotifications(simpleNotifications = listOf(OneShotNotification.Removed(AN_EVENT_ID.value)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID))
|
||||
cancelNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a simple notification is removed when rendering then remove the simple notification and update summary`() {
|
||||
givenNotifications(simpleNotifications = listOf(OneShotNotification.Removed(AN_EVENT_ID.value)))
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
cancelNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID))
|
||||
showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a simple notification is added when rendering then show the simple notification and update summary`() {
|
||||
givenNotifications(
|
||||
simpleNotifications = listOf(
|
||||
OneShotNotification.Append(
|
||||
A_NOTIFICATION,
|
||||
ONE_SHOT_META.copy(key = AN_EVENT_ID.value)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
showNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID), A_NOTIFICATION)
|
||||
showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given last invitation notification is removed when rendering then remove the summary and then remove invitation notification`() {
|
||||
givenNotifications(invitationNotifications = listOf(OneShotNotification.Removed(A_ROOM_ID.value)), summaryNotification = A_REMOVE_SUMMARY_NOTIFICATION)
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID))
|
||||
cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given an invitation notification is removed when rendering then remove the invitation notification and update summary`() {
|
||||
givenNotifications(invitationNotifications = listOf(OneShotNotification.Removed(A_ROOM_ID.value)))
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID))
|
||||
showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given an invitation notification is added when rendering then show the invitation notification and update summary`() {
|
||||
givenNotifications(
|
||||
simpleNotifications = listOf(
|
||||
OneShotNotification.Append(
|
||||
A_NOTIFICATION,
|
||||
ONE_SHOT_META.copy(key = A_ROOM_ID.value)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
renderEventsAsNotifications()
|
||||
|
||||
notificationDisplayer.verifyInOrder {
|
||||
showNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID), A_NOTIFICATION)
|
||||
showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderEventsAsNotifications() {
|
||||
notificationRenderer.render(
|
||||
sessionId = A_SESSION_ID,
|
||||
myUserDisplayName = MY_USER_DISPLAY_NAME,
|
||||
myUserAvatarUrl = MY_USER_AVATAR_URL,
|
||||
useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT,
|
||||
eventsToProcess = AN_EVENT_LIST
|
||||
)
|
||||
}
|
||||
|
||||
private fun givenNoNotifications() {
|
||||
givenNotifications(emptyList(), emptyList(), emptyList(), USE_COMPLETE_NOTIFICATION_FORMAT, A_REMOVE_SUMMARY_NOTIFICATION)
|
||||
}
|
||||
|
||||
private fun givenNotifications(
|
||||
roomNotifications: List<RoomNotification> = emptyList(),
|
||||
invitationNotifications: List<OneShotNotification> = emptyList(),
|
||||
simpleNotifications: List<OneShotNotification> = emptyList(),
|
||||
useCompleteNotificationFormat: Boolean = USE_COMPLETE_NOTIFICATION_FORMAT,
|
||||
summaryNotification: SummaryNotification = A_SUMMARY_NOTIFICATION
|
||||
) {
|
||||
notificationFactory.givenNotificationsFor(
|
||||
groupedEvents = A_PROCESSED_EVENTS,
|
||||
sessionId = A_SESSION_ID,
|
||||
myUserDisplayName = MY_USER_DISPLAY_NAME,
|
||||
myUserAvatarUrl = MY_USER_AVATAR_URL,
|
||||
useCompleteNotificationFormat = useCompleteNotificationFormat,
|
||||
roomNotifications = roomNotifications,
|
||||
invitationNotifications = invitationNotifications,
|
||||
simpleNotifications = simpleNotifications,
|
||||
summaryNotification = summaryNotification
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications.fake
|
||||
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationDisplayer
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationIdProvider
|
||||
import io.mockk.confirmVerified
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import io.mockk.verifyOrder
|
||||
|
||||
class FakeNotificationDisplayer {
|
||||
val instance = mockk<NotificationDisplayer>(relaxed = true)
|
||||
|
||||
fun verifySummaryCancelled() {
|
||||
verify { instance.cancelNotificationMessage(tag = null, NotificationIdProvider().getSummaryNotificationId(A_SESSION_ID)) }
|
||||
}
|
||||
|
||||
fun verifyNoOtherInteractions() {
|
||||
confirmVerified(instance)
|
||||
}
|
||||
|
||||
fun verifyInOrder(verifyBlock: NotificationDisplayer.() -> Unit) {
|
||||
verifyOrder { verifyBlock(instance) }
|
||||
verifyNoOtherInteractions()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications.fake
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.notifications.*
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeNotificationFactory {
|
||||
val instance = mockk<NotificationFactory>()
|
||||
|
||||
fun givenNotificationsFor(
|
||||
groupedEvents: GroupedNotificationEvents,
|
||||
sessionId: SessionId,
|
||||
myUserDisplayName: String,
|
||||
myUserAvatarUrl: String?,
|
||||
useCompleteNotificationFormat: Boolean,
|
||||
roomNotifications: List<RoomNotification>,
|
||||
invitationNotifications: List<OneShotNotification>,
|
||||
simpleNotifications: List<OneShotNotification>,
|
||||
summaryNotification: SummaryNotification
|
||||
) {
|
||||
with(instance) {
|
||||
every { groupedEvents.roomEvents.toNotifications(sessionId, myUserDisplayName, myUserAvatarUrl) } returns roomNotifications
|
||||
every { groupedEvents.invitationEvents.toNotifications() } returns invitationNotifications
|
||||
every { groupedEvents.simpleEvents.toNotifications() } returns simpleNotifications
|
||||
|
||||
every {
|
||||
createSummaryNotification(
|
||||
sessionId,
|
||||
roomNotifications,
|
||||
invitationNotifications,
|
||||
simpleNotifications,
|
||||
useCompleteNotificationFormat
|
||||
)
|
||||
} returns summaryNotification
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications.fake
|
||||
|
||||
import android.app.Notification
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationUtils
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeNotificationUtils {
|
||||
val instance = mockk<NotificationUtils>()
|
||||
|
||||
fun givenBuildRoomInvitationNotificationFor(event: InviteNotifiableEvent): Notification {
|
||||
val mockNotification = mockk<Notification>()
|
||||
every { instance.buildRoomInvitationNotification(event) } returns mockNotification
|
||||
return mockNotification
|
||||
}
|
||||
|
||||
fun givenBuildSimpleInvitationNotificationFor(event: SimpleNotifiableEvent): Notification {
|
||||
val mockNotification = mockk<Notification>()
|
||||
every { instance.buildSimpleEventNotification(event) } returns mockNotification
|
||||
return mockNotification
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications.fake
|
||||
|
||||
import io.element.android.libraries.push.impl.notifications.OutdatedEventDetector
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeOutdatedEventDetector {
|
||||
val instance = mockk<OutdatedEventDetector>()
|
||||
|
||||
fun givenEventIsOutOfDate(notifiableEvent: NotifiableEvent) {
|
||||
every { instance.isMessageOutdated(notifiableEvent) } returns true
|
||||
}
|
||||
|
||||
fun givenEventIsInDate(notifiableEvent: NotifiableEvent) {
|
||||
every { instance.isMessageOutdated(notifiableEvent) } returns false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications.fake
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.notifications.RoomGroupMessageCreator
|
||||
import io.element.android.libraries.push.impl.notifications.RoomNotification
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeRoomGroupMessageCreator {
|
||||
|
||||
val instance = mockk<RoomGroupMessageCreator>()
|
||||
|
||||
fun givenCreatesRoomMessageFor(
|
||||
sessionId: SessionId,
|
||||
events: List<NotifiableMessageEvent>,
|
||||
roomId: RoomId,
|
||||
userDisplayName: String,
|
||||
userAvatarUrl: String?
|
||||
): RoomNotification.Message {
|
||||
val mockMessage = mockk<RoomNotification.Message>()
|
||||
every { instance.createRoomMessage(sessionId, events, roomId, userDisplayName, userAvatarUrl) } returns mockMessage
|
||||
return mockMessage
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications.fake
|
||||
|
||||
import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageCreator
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeSummaryGroupMessageCreator {
|
||||
|
||||
val instance = mockk<SummaryGroupMessageCreator>()
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications.fixtures
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
|
||||
|
||||
fun aSimpleNotifiableEvent(
|
||||
sessionId: SessionId = A_SESSION_ID,
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
eventId: EventId = AN_EVENT_ID,
|
||||
type: String? = null,
|
||||
isRedacted: Boolean = false,
|
||||
canBeReplaced: Boolean = false,
|
||||
editedEventId: EventId? = null
|
||||
) = SimpleNotifiableEvent(
|
||||
sessionId = sessionId,
|
||||
roomId = roomId,
|
||||
eventId = eventId,
|
||||
editedEventId = editedEventId,
|
||||
noisy = false,
|
||||
title = "title",
|
||||
description = "description",
|
||||
type = type,
|
||||
timestamp = 0,
|
||||
soundName = null,
|
||||
canBeReplaced = canBeReplaced,
|
||||
isRedacted = isRedacted
|
||||
)
|
||||
|
||||
fun anInviteNotifiableEvent(
|
||||
sessionId: SessionId = A_SESSION_ID,
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
eventId: EventId = AN_EVENT_ID,
|
||||
isRedacted: Boolean = false
|
||||
) = InviteNotifiableEvent(
|
||||
sessionId = sessionId,
|
||||
eventId = eventId,
|
||||
roomId = roomId,
|
||||
roomName = "a room name",
|
||||
editedEventId = null,
|
||||
noisy = false,
|
||||
title = "title",
|
||||
description = "description",
|
||||
type = null,
|
||||
timestamp = 0,
|
||||
soundName = null,
|
||||
canBeReplaced = false,
|
||||
isRedacted = isRedacted
|
||||
)
|
||||
|
||||
fun aNotifiableMessageEvent(
|
||||
sessionId: SessionId = A_SESSION_ID,
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
eventId: EventId = AN_EVENT_ID,
|
||||
threadId: ThreadId? = null,
|
||||
isRedacted: Boolean = false
|
||||
) = NotifiableMessageEvent(
|
||||
sessionId = sessionId,
|
||||
eventId = eventId,
|
||||
editedEventId = null,
|
||||
noisy = false,
|
||||
timestamp = 0,
|
||||
senderName = "sender-name",
|
||||
senderId = "sending-id",
|
||||
body = "message-body",
|
||||
roomId = roomId,
|
||||
threadId = threadId,
|
||||
roomName = "room-name",
|
||||
roomIsDirect = false,
|
||||
canBeReplaced = false,
|
||||
isRedacted = isRedacted,
|
||||
imageUriString = null
|
||||
)
|
||||
30
services/appnavstate/test/build.gradle.kts
Normal file
30
services/appnavstate/test/build.gradle.kts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
|
||||
@Suppress("DSL_SCOPE_VIOLATION")
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.services.appnavstate.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.libraries.matrix.api)
|
||||
api(projects.services.appnavstate.api)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.services.appnavstate.test
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.*
|
||||
import io.element.android.services.appnavstate.api.AppNavigationState
|
||||
|
||||
fun anAppNavigationState(
|
||||
sessionId: SessionId? = null,
|
||||
spaceId: SpaceId? = MAIN_SPACE,
|
||||
roomId: RoomId? = null,
|
||||
threadId: ThreadId? = null,
|
||||
): AppNavigationState {
|
||||
if (sessionId == null) {
|
||||
return AppNavigationState.Root
|
||||
}
|
||||
val session = AppNavigationState.Session(sessionId)
|
||||
if (spaceId == null) {
|
||||
return session
|
||||
}
|
||||
val space = AppNavigationState.Space(spaceId, session)
|
||||
if (roomId == null) {
|
||||
return space
|
||||
}
|
||||
val room = AppNavigationState.Room(roomId, space)
|
||||
if (threadId == null) {
|
||||
return room
|
||||
}
|
||||
return AppNavigationState.Thread(threadId, room)
|
||||
}
|
||||
Reference in New Issue
Block a user