Let DefaultNotificationDrawerManager use NotificationDisplayer instead of NotificationManagerCompat

This commit is contained in:
Benoit Marty 2025-10-27 08:26:12 +01:00 committed by Benoit Marty
parent 55517e5cb1
commit fbecf8d34f
17 changed files with 136 additions and 111 deletions

View file

@ -93,6 +93,7 @@ dependencies {
testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.matrixuiTest)
testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.push.test)
testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.test)
testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.appnavstate.test)

View file

@ -18,7 +18,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest

View file

@ -33,9 +33,9 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder
import io.element.android.libraries.push.api.notifications.ForegroundServiceType import io.element.android.libraries.push.api.notifications.ForegroundServiceType
import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
@ -415,6 +415,7 @@ class DefaultActiveCallManagerTest {
verify { notificationManagerCompat.cancel(any()) } verify { notificationManagerCompat.cancel(any()) }
} }
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@Test @Test
fun `IncomingCall - ignore expired ring lifetime`() = runTest { fun `IncomingCall - ignore expired ring lifetime`() = runTest {

View file

@ -0,0 +1,20 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.matrix.ui.test"
}
dependencies {
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui)
implementation(libs.coil.compose)
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.ui.test.media
import coil3.ComponentRegistry
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import coil3.request.Disposable
import coil3.request.ImageRequest
import coil3.request.ImageResult
class FakeImageLoader : ImageLoader {
private val executedRequests = mutableListOf<ImageRequest>()
override val defaults: ImageRequest.Defaults
get() = error("Not implemented")
override val components: ComponentRegistry
get() = error("Not implemented")
override val memoryCache: MemoryCache?
get() = error("Not implemented")
override val diskCache: DiskCache?
get() = error("Not implemented")
override fun enqueue(request: ImageRequest): Disposable {
error("Not implemented")
}
override suspend fun execute(request: ImageRequest): ImageResult {
executedRequests.add(request)
error("Not implemented")
}
override fun shutdown() {
error("Not implemented")
}
override fun newBuilder(): ImageLoader.Builder {
error("Not implemented")
}
fun getExecutedRequestsData(): List<Any> {
return executedRequests.map { it.data }
}
}

View file

@ -1,21 +1,22 @@
/* /*
* Copyright 2024 New Vector Ltd. * Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.libraries.push.test.notifications package io.element.android.libraries.matrix.ui.test.media
import coil3.ImageLoader import coil3.ImageLoader
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
class FakeImageLoaderHolder : ImageLoaderHolder { class FakeImageLoaderHolder(
private val fakeImageLoader = FakeImageLoader() val fakeImageLoader: ImageLoader = FakeImageLoader(),
) : ImageLoaderHolder {
override fun get(client: MatrixClient): ImageLoader { override fun get(client: MatrixClient): ImageLoader {
return fakeImageLoader.getImageLoader() return fakeImageLoader
} }
override fun remove(sessionId: SessionId) { override fun remove(sessionId: SessionId) {

View file

@ -70,6 +70,7 @@ dependencies {
testCommonDependencies(libs) testCommonDependencies(libs)
testImplementation(libs.coil.test) testImplementation(libs.coil.test)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.matrixuiTest)
testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.sessionStorage.test) testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.push.test)

View file

@ -8,7 +8,6 @@
package io.element.android.libraries.push.impl.notifications package io.element.android.libraries.push.impl.notifications
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.core.app.NotificationManagerCompat
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.SingleIn
@ -46,7 +45,7 @@ private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag.
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultNotificationDrawerManager( class DefaultNotificationDrawerManager(
private val notificationManager: NotificationManagerCompat, private val notificationDisplayer: NotificationDisplayer,
private val notificationRenderer: NotificationRenderer, private val notificationRenderer: NotificationRenderer,
private val appNavigationStateService: AppNavigationStateService, private val appNavigationStateService: AppNavigationStateService,
@AppCoroutineScope @AppCoroutineScope
@ -124,7 +123,7 @@ class DefaultNotificationDrawerManager(
* Clear all known message events for a [sessionId]. * Clear all known message events for a [sessionId].
*/ */
override fun clearAllMessagesEvents(sessionId: SessionId) { override fun clearAllMessagesEvents(sessionId: SessionId) {
notificationManager.cancel(null, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) notificationDisplayer.cancelNotification(null, NotificationIdProvider.getRoomMessagesNotificationId(sessionId))
clearSummaryNotificationIfNeeded(sessionId) clearSummaryNotificationIfNeeded(sessionId)
} }
@ -133,7 +132,7 @@ class DefaultNotificationDrawerManager(
*/ */
fun clearAllEvents(sessionId: SessionId) { fun clearAllEvents(sessionId: SessionId) {
activeNotificationsProvider.getNotificationsForSession(sessionId) activeNotificationsProvider.getNotificationsForSession(sessionId)
.forEach { notificationManager.cancel(it.tag, it.id) } .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) }
} }
/** /**
@ -142,7 +141,7 @@ class DefaultNotificationDrawerManager(
* Can also be called when a notification for this room is dismissed by the user. * Can also be called when a notification for this room is dismissed by the user.
*/ */
override fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) { override fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) {
notificationManager.cancel(roomId.value, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) notificationDisplayer.cancelNotification(roomId.value, NotificationIdProvider.getRoomMessagesNotificationId(sessionId))
clearSummaryNotificationIfNeeded(sessionId) clearSummaryNotificationIfNeeded(sessionId)
} }
@ -158,7 +157,7 @@ class DefaultNotificationDrawerManager(
override fun clearMembershipNotificationForSession(sessionId: SessionId) { override fun clearMembershipNotificationForSession(sessionId: SessionId) {
activeNotificationsProvider.getMembershipNotificationForSession(sessionId) activeNotificationsProvider.getMembershipNotificationForSession(sessionId)
.forEach { notificationManager.cancel(it.tag, it.id) } .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) }
clearSummaryNotificationIfNeeded(sessionId) clearSummaryNotificationIfNeeded(sessionId)
} }
@ -167,7 +166,7 @@ class DefaultNotificationDrawerManager(
*/ */
override fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId) { override fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId) {
activeNotificationsProvider.getMembershipNotificationForRoom(sessionId, roomId) activeNotificationsProvider.getMembershipNotificationForRoom(sessionId, roomId)
.forEach { notificationManager.cancel(it.tag, it.id) } .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) }
clearSummaryNotificationIfNeeded(sessionId) clearSummaryNotificationIfNeeded(sessionId)
} }
@ -176,14 +175,14 @@ class DefaultNotificationDrawerManager(
*/ */
override fun clearEvent(sessionId: SessionId, eventId: EventId) { override fun clearEvent(sessionId: SessionId, eventId: EventId) {
val id = NotificationIdProvider.getRoomEventNotificationId(sessionId) val id = NotificationIdProvider.getRoomEventNotificationId(sessionId)
notificationManager.cancel(eventId.value, id) notificationDisplayer.cancelNotification(eventId.value, id)
clearSummaryNotificationIfNeeded(sessionId) clearSummaryNotificationIfNeeded(sessionId)
} }
private fun clearSummaryNotificationIfNeeded(sessionId: SessionId) { private fun clearSummaryNotificationIfNeeded(sessionId: SessionId) {
val summaryNotification = activeNotificationsProvider.getSummaryNotification(sessionId) val summaryNotification = activeNotificationsProvider.getSummaryNotification(sessionId)
if (summaryNotification != null && activeNotificationsProvider.count(sessionId) == 1) { if (summaryNotification != null && activeNotificationsProvider.count(sessionId) == 1) {
notificationManager.cancel(null, summaryNotification.id) notificationDisplayer.cancelNotification(null, summaryNotification.id)
} }
} }

View file

@ -61,6 +61,7 @@ class DefaultNotificationDisplayer(
companion object { companion object {
private const val TAG_DIAGNOSTIC = "DIAGNOSTIC" private const val TAG_DIAGNOSTIC = "DIAGNOSTIC"
/* ========================================================================================== /* ==========================================================================================
* IDs for notifications * IDs for notifications
* ========================================================================================== */ * ========================================================================================== */

View file

@ -18,12 +18,12 @@ import io.element.android.libraries.matrix.test.A_TIMESTAMP
import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL
import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.matrix.ui.media.MediaRequestData
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader
import io.element.android.libraries.push.impl.notifications.factories.MARK_AS_READ_ACTION_TITLE import io.element.android.libraries.push.impl.notifications.factories.MARK_AS_READ_ACTION_TITLE
import io.element.android.libraries.push.impl.notifications.factories.QUICK_REPLY_ACTION_TITLE import io.element.android.libraries.push.impl.notifications.factories.QUICK_REPLY_ACTION_TITLE
import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams
import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.libraries.push.test.notifications.FakeImageLoader
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
@ -51,7 +51,7 @@ class DefaultBaseRoomGroupMessageCreatorTest {
) )
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
existingNotification = null, existingNotification = null,
threadId = null, threadId = null,
) )
@ -59,7 +59,7 @@ class DefaultBaseRoomGroupMessageCreatorTest {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_LOW) assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_LOW)
assertThat(result.`when`).isEqualTo(A_TIMESTAMP) assertThat(result.`when`).isEqualTo(A_TIMESTAMP)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty()
} }
@Test @Test
@ -74,13 +74,13 @@ class DefaultBaseRoomGroupMessageCreatorTest {
) )
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
existingNotification = null, existingNotification = null,
threadId = null, threadId = null,
) )
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_DEFAULT) assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_DEFAULT)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty()
} }
@Test @Test
@ -141,12 +141,12 @@ class DefaultBaseRoomGroupMessageCreatorTest {
) )
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
existingNotification = null, existingNotification = null,
threadId = null, threadId = null,
) )
assertThat(result.number).isEqualTo(1) assertThat(result.number).isEqualTo(1)
assertThat(fakeImageLoader.getCoilRequests()).containsExactlyElementsIn(expectedCoilRequests) assertThat(fakeImageLoader.getExecutedRequestsData()).containsExactlyElementsIn(expectedCoilRequests)
} }
@Test @Test
@ -160,7 +160,7 @@ class DefaultBaseRoomGroupMessageCreatorTest {
aNotifiableMessageEvent(timestamp = A_TIMESTAMP + 10), aNotifiableMessageEvent(timestamp = A_TIMESTAMP + 10),
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
existingNotification = null, existingNotification = null,
threadId = null, threadId = null,
) )
@ -173,7 +173,7 @@ class DefaultBaseRoomGroupMessageCreatorTest {
QUICK_REPLY_ACTION_TITLE.takeIf { NotificationConfig.SHOW_QUICK_REPLY_ACTION }, QUICK_REPLY_ACTION_TITLE.takeIf { NotificationConfig.SHOW_QUICK_REPLY_ACTION },
) )
) )
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty()
} }
@Test @Test
@ -189,7 +189,7 @@ class DefaultBaseRoomGroupMessageCreatorTest {
), ),
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
existingNotification = null, existingNotification = null,
threadId = null, threadId = null,
) )
@ -199,7 +199,7 @@ class DefaultBaseRoomGroupMessageCreatorTest {
MARK_AS_READ_ACTION_TITLE.takeIf { NotificationConfig.SHOW_MARK_AS_READ_ACTION } MARK_AS_READ_ACTION_TITLE.takeIf { NotificationConfig.SHOW_MARK_AS_READ_ACTION }
) )
) )
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty()
} }
@Test @Test
@ -214,13 +214,13 @@ class DefaultBaseRoomGroupMessageCreatorTest {
), ),
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
existingNotification = null, existingNotification = null,
threadId = null, threadId = null,
) )
assertThat(result.number).isEqualTo(1) assertThat(result.number).isEqualTo(1)
assertThat(result.`when`).isEqualTo(A_TIMESTAMP) assertThat(result.`when`).isEqualTo(A_TIMESTAMP)
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty()
} }
} }

View file

@ -9,7 +9,6 @@ package io.element.android.libraries.push.impl.notifications
import android.app.Notification import android.app.Notification
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.core.app.NotificationManagerCompat
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService
@ -22,14 +21,15 @@ import io.element.android.libraries.matrix.test.A_THREAD_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder
import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator 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.fake.FakeSummaryGroupMessageCreator
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
import io.element.android.services.appnavstate.api.AppNavigationState import io.element.android.services.appnavstate.api.AppNavigationState
@ -43,19 +43,14 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@RunWith(RobolectricTestRunner::class)
class DefaultNotificationDrawerManagerTest { class DefaultNotificationDrawerManagerTest {
@Test @Test
fun `clearAllEvents should have no effect when queue is empty`() = runTest { fun `clearAllEvents should have no effect when queue is empty`() = runTest {
@ -183,10 +178,12 @@ class DefaultNotificationDrawerManagerTest {
@Test @Test
fun `clearSummaryNotificationIfNeeded will run after clearing all other notifications`() = runTest { fun `clearSummaryNotificationIfNeeded will run after clearing all other notifications`() = runTest {
val notificationManager = mockk<NotificationManagerCompat> { val cancelNotificationResult = lambdaRecorder<String?, Int, Unit> { _, _ -> }
every { cancel(any(), any()) } returns Unit val notificationDisplayer = FakeNotificationDisplayer(
} cancelNotificationResult = cancelNotificationResult,
)
val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID) val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID)
val roomMessageId = NotificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)
val activeNotificationsProvider = FakeActiveNotificationsProvider( val activeNotificationsProvider = FakeActiveNotificationsProvider(
getSummaryNotificationResult = { getSummaryNotificationResult = {
mockk { mockk {
@ -196,7 +193,7 @@ class DefaultNotificationDrawerManagerTest {
countResult = { 1 }, countResult = { 1 },
) )
val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager(
notificationManager = notificationManager, notificationDisplayer = notificationDisplayer,
activeNotificationsProvider = activeNotificationsProvider, activeNotificationsProvider = activeNotificationsProvider,
) )
@ -204,13 +201,16 @@ class DefaultNotificationDrawerManagerTest {
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID) defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID)
// Verify we asked to cancel the notification with summaryId // Verify we asked to cancel the notification with summaryId
verify { notificationManager.cancel(null, summaryId) } cancelNotificationResult.assertions().isCalledExactly(2).withSequence(
listOf(value(null), value(roomMessageId)),
listOf(value(null), value(summaryId)),
)
defaultNotificationDrawerManager.destroy() defaultNotificationDrawerManager.destroy()
} }
private fun TestScope.createDefaultNotificationDrawerManager( private fun TestScope.createDefaultNotificationDrawerManager(
notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(RuntimeEnvironment.getApplication()), notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(),
appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(), appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(),
roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(), roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(),
summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(), summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(),
@ -219,11 +219,10 @@ class DefaultNotificationDrawerManagerTest {
sessionStore: SessionStore = InMemorySessionStore(), sessionStore: SessionStore = InMemorySessionStore(),
enterpriseService: EnterpriseService = FakeEnterpriseService(), enterpriseService: EnterpriseService = FakeEnterpriseService(),
): DefaultNotificationDrawerManager { ): DefaultNotificationDrawerManager {
val context = RuntimeEnvironment.getApplication()
return DefaultNotificationDrawerManager( return DefaultNotificationDrawerManager(
notificationManager = notificationManager, notificationDisplayer = notificationDisplayer,
notificationRenderer = NotificationRenderer( notificationRenderer = NotificationRenderer(
notificationDisplayer = DefaultNotificationDisplayer(context, NotificationManagerCompat.from(context)), notificationDisplayer = FakeNotificationDisplayer(),
notificationDataFactory = DefaultNotificationDataFactory( notificationDataFactory = DefaultNotificationDataFactory(
notificationCreator = FakeNotificationCreator(), notificationCreator = FakeNotificationCreator(),
roomGroupMessageCreator = roomGroupMessageCreator, roomGroupMessageCreator = roomGroupMessageCreator,

View file

@ -15,22 +15,19 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notification.FakeNotificationService
import io.element.android.libraries.matrix.test.notification.aNotificationData import io.element.android.libraries.matrix.test.notification.aNotificationData
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class DefaultOnMissedCallNotificationHandlerTest { class DefaultOnMissedCallNotificationHandlerTest {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@Test @Test
@ -50,7 +47,7 @@ class DefaultOnMissedCallNotificationHandlerTest {
val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler( val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler(
matrixClientProvider = matrixClientProvider, matrixClientProvider = matrixClientProvider,
defaultNotificationDrawerManager = DefaultNotificationDrawerManager( defaultNotificationDrawerManager = DefaultNotificationDrawerManager(
notificationManager = mockk(relaxed = true), notificationDisplayer = FakeNotificationDisplayer(),
notificationRenderer = createNotificationRenderer( notificationRenderer = createNotificationRenderer(
notificationDataFactory = dataFactory, notificationDataFactory = dataFactory,
), ),

View file

@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_EVENT_ID 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_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader
import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
@ -21,7 +22,6 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGrou
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent 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.aSimpleNotifiableEvent
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
import io.element.android.libraries.push.test.notifications.FakeImageLoader
import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
@ -98,7 +98,7 @@ class NotificationDataFactoryTest {
events = events, events = events,
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
threadId = null, threadId = null,
imageLoader = FakeImageLoader().getImageLoader(), imageLoader = FakeImageLoader(),
existingNotification = null, existingNotification = null,
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
@ -113,12 +113,12 @@ class NotificationDataFactoryTest {
notificationAccountParams = aNotificationAccountParams( notificationAccountParams = aNotificationAccountParams(
user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL),
), ),
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
) )
assertThat(result.size).isEqualTo(1) assertThat(result.size).isEqualTo(1)
assertThat(result.first().isDataEqualTo(expectedNotification)).isTrue() assertThat(result.first().isDataEqualTo(expectedNotification)).isTrue()
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty()
} }
@Test @Test
@ -129,10 +129,10 @@ class NotificationDataFactoryTest {
notificationAccountParams = aNotificationAccountParams( notificationAccountParams = aNotificationAccountParams(
user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL),
), ),
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
) )
assertThat(result).isEmpty() assertThat(result).isEmpty()
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty()
} }
@Test @Test
@ -152,7 +152,7 @@ class NotificationDataFactoryTest {
events = withRedactedRemoved, events = withRedactedRemoved,
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
threadId = null, threadId = null,
imageLoader = FakeImageLoader().getImageLoader(), imageLoader = FakeImageLoader(),
existingNotification = null, existingNotification = null,
), ),
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
@ -168,12 +168,12 @@ class NotificationDataFactoryTest {
notificationAccountParams = aNotificationAccountParams( notificationAccountParams = aNotificationAccountParams(
user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL),
), ),
imageLoader = fakeImageLoader.getImageLoader(), imageLoader = fakeImageLoader,
) )
assertThat(result.size).isEqualTo(1) assertThat(result.size).isEqualTo(1)
assertThat(result.first().isDataEqualTo(expectedNotification)).isTrue() assertThat(result.first().isDataEqualTo(expectedNotification)).isTrue()
assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty()
} }
} }

View file

@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_EVENT_ID 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_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader
import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
@ -25,7 +26,6 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiable
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent 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.fixtures.anInviteNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.libraries.push.test.notifications.FakeImageLoader
import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider
@ -113,7 +113,7 @@ class NotificationRendererTest {
MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL), MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL),
useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT, useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT,
eventsToProcess = events, eventsToProcess = events,
imageLoader = FakeImageLoader().getImageLoader(), imageLoader = FakeImageLoader(),
) )
} }
} }

View file

@ -20,9 +20,9 @@ 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_SESSION_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.A_SESSION_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder
import io.element.android.libraries.push.impl.notifications.factories.FakeIntentProvider import io.element.android.libraries.push.impl.notifications.factories.FakeIntentProvider
import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi

View file

@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.A_THREAD_ID
import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
import io.element.android.libraries.push.impl.notifications.DefaultNotificationBitmapLoader import io.element.android.libraries.push.impl.notifications.DefaultNotificationBitmapLoader
import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationActionIds
@ -36,7 +37,6 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiable
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
import io.element.android.libraries.push.test.notifications.FakeImageLoader
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
@ -246,7 +246,7 @@ class DefaultNotificationCreatorTest {
lastMessageTimestamp = 123_456L, lastMessageTimestamp = 123_456L,
tickerText = "tickerText", tickerText = "tickerText",
existingNotification = null, existingNotification = null,
imageLoader = FakeImageLoader().getImageLoader(), imageLoader = FakeImageLoader(),
events = listOf(aNotifiableMessageEvent()), events = listOf(aNotifiableMessageEvent()),
) )
result.commonAssertions() result.commonAssertions()
@ -271,7 +271,7 @@ class DefaultNotificationCreatorTest {
lastMessageTimestamp = 123_456L, lastMessageTimestamp = 123_456L,
tickerText = "tickerText", tickerText = "tickerText",
existingNotification = null, existingNotification = null,
imageLoader = FakeImageLoader().getImageLoader(), imageLoader = FakeImageLoader(),
events = listOf(aNotifiableMessageEvent()), events = listOf(aNotifiableMessageEvent()),
) )
result.commonAssertions() result.commonAssertions()

View file

@ -1,45 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.push.test.notifications
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import coil3.ImageLoader
import coil3.test.FakeImageLoaderEngine
import coil3.test.intercept
import org.robolectric.RuntimeEnvironment
class FakeImageLoader {
private val coilRequests = mutableListOf<Any>()
private var cache: ImageLoader? = null
fun getImageLoader(): ImageLoader {
return cache ?: ImageLoader.Builder(RuntimeEnvironment.getApplication())
.components {
val engine = FakeImageLoaderEngine.Builder()
.intercept(
predicate = {
coilRequests.add(it)
true
},
drawable = ColorDrawable(Color.BLUE)
)
.build()
add(engine)
}
.build()
.also {
cache = it
}
}
fun getCoilRequests(): List<Any> {
return coilRequests.toList()
}
}