From 765ea941667decb076cb06d82620d9f5a582b394 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Feb 2026 15:51:17 +0100 Subject: [PATCH 1/7] Incoming call screen: use color from theme and follow design. https://www.figma.com/design/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=16501-5740 --- .../android/features/call/impl/ui/IncomingCallScreen.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt index 682c4cec739..62de00a10b6 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt @@ -51,6 +51,9 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.ui.strings.CommonStrings +/** + * Ref: https://www.figma.com/design/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=16501-5740 + */ @Composable internal fun IncomingCallScreen( notificationData: CallNotificationData, @@ -143,7 +146,7 @@ private fun ActionButton( onClick = onClick, colors = IconButtonDefaults.filledIconButtonColors( containerColor = backgroundColor, - contentColor = Color.White, + contentColor = ElementTheme.colors.iconOnSolidPrimary, ) ) { Icon( From 194f1d2d373e9c8f85a145f69a5a8f679d2bc629 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Feb 2026 16:01:05 +0100 Subject: [PATCH 2/7] Incoming call screen: ensure buttons stay grouped in the center of the screen. --- .../android/features/call/impl/ui/IncomingCallScreen.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt index 62de00a10b6..5ec3689b449 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt @@ -97,11 +97,8 @@ internal fun IncomingCallScreen( ) } Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp, end = 24.dp, bottom = 64.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + modifier = Modifier.padding(bottom = 64.dp), + horizontalArrangement = Arrangement.spacedBy(48.dp), ) { ActionButton( size = 64.dp, @@ -111,7 +108,6 @@ internal fun IncomingCallScreen( backgroundColor = ElementTheme.colors.iconSuccessPrimary, borderColor = ElementTheme.colors.borderSuccessSubtle ) - ActionButton( size = 64.dp, onClick = onCancel, From c3fa32fcd329481058ef1aee50478d90dda439b2 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 12 Feb 2026 15:37:16 +0000 Subject: [PATCH 3/7] Update screenshots --- .../features.call.impl.ui_IncomingCallScreen_Day_0_en.png | 4 ++-- .../features.call.impl.ui_IncomingCallScreen_Night_0_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_IncomingCallScreen_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_IncomingCallScreen_Day_0_en.png index d4a981b4911..bcc1b05eb86 100644 --- a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_IncomingCallScreen_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_IncomingCallScreen_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:913d6230ab2b470dd5393344395bb9c25973318f09ecdc52499a17e9c9e8faba -size 66219 +oid sha256:4dac0f93eb31b26fa32173fbd834c7f661e4f47c79db66fa4d1536d938a4585d +size 66108 diff --git a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_IncomingCallScreen_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_IncomingCallScreen_Night_0_en.png index 0b23435bcd3..b656d1e06ce 100644 --- a/tests/uitests/src/test/snapshots/images/features.call.impl.ui_IncomingCallScreen_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.call.impl.ui_IncomingCallScreen_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f59ff395027433af611ef1aec1a1d3e5a7d670df3c77d1c5d01154199c123a71 -size 58586 +oid sha256:4c3e5ef9368d68f661350a7a31b98b3ae3fbf975bc11d6f1b9e5ac908e6699dc +size 58355 From e2ca3f3c990fa19fb532adc9ac8753b1df080fc6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Feb 2026 16:27:55 +0100 Subject: [PATCH 4/7] Fix documentation --- .../impl/receivers/DeclineCallBroadcastReceiver.kt | 2 +- .../features/call/impl/ui/CallScreenPresenter.kt | 2 +- .../features/call/impl/ui/IncomingCallActivity.kt | 2 +- .../features/call/impl/utils/ActiveCallManager.kt | 9 +++++---- .../call/utils/DefaultActiveCallManagerTest.kt | 10 +++++----- .../features/call/utils/FakeActiveCallManager.kt | 6 +++--- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt index d2cbb0184d5..902c4d7d8de 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt @@ -40,7 +40,7 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() { ?: return context.bindings().inject(this) appCoroutineScope.launch { - activeCallManager.hungUpCall(callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) + activeCallManager.hangUpCall(callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) } } } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt index 497b121da5a..ba670e03aab 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt @@ -100,7 +100,7 @@ class CallScreenPresenter( ) } onDispose { - appCoroutineScope.launch { activeCallManager.hungUpCall(callType) } + appCoroutineScope.launch { activeCallManager.hangUpCall(callType) } } } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt index 714360a702f..faedd2648cf 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt @@ -118,7 +118,7 @@ class IncomingCallActivity : AppCompatActivity() { private fun onCancel() { val activeCall = activeCallManager.activeCall.value ?: return appCoroutineScope.launch { - activeCallManager.hungUpCall(callType = activeCall.callType) + activeCallManager.hangUpCall(callType = activeCall.callType) } } } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt index 4183e225314..2c0e2e7742d 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt @@ -72,10 +72,10 @@ interface ActiveCallManager { suspend fun registerIncomingCall(notificationData: CallNotificationData) /** - * Called when the active call has been hung up. It will remove any existing UI and the active call. - * @param callType The type of call that the user hung up, either an external url one or a room one. + * Called to hang up the active call. It will hang up the call and remove any existing UI and the active call. + * @param callType The type of call that the user hangs up, either an external url one or a room one. */ - suspend fun hungUpCall(callType: CallType) + suspend fun hangUpCall(callType: CallType) /** * Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall]. @@ -192,8 +192,9 @@ class DefaultActiveCallManager( } } - override suspend fun hungUpCall(callType: CallType) = mutex.withLock { + override suspend fun hangUpCall(callType: CallType) = mutex.withLock { Timber.tag(tag).d("Hung up call: $callType") + Timber.tag(tag).d("Hang up call: $callType") val currentActiveCall = activeCall.value ?: run { Timber.tag(tag).w("No active call, ignoring hang up") return@withLock diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt index df14b4b4236..2fac7634d72 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt @@ -155,7 +155,7 @@ class DefaultActiveCallManagerTest { } @Test - fun `hungUpCall - removes existing call if the CallType matches`() = runTest { + fun `hangUpCall - removes existing call if the CallType matches`() = runTest { setupShadowPowerManager() val notificationManagerCompat = mockk(relaxed = true) val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat) @@ -165,7 +165,7 @@ class DefaultActiveCallManagerTest { assertThat(manager.activeCall.value).isNotNull() assertThat(manager.activeWakeLock?.isHeld).isTrue() - manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) + manager.hangUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) assertThat(manager.activeCall.value).isNull() assertThat(manager.activeWakeLock?.isHeld).isFalse() @@ -192,7 +192,7 @@ class DefaultActiveCallManagerTest { val notificationData = aCallNotificationData(roomId = A_ROOM_ID) manager.registerIncomingCall(notificationData) - manager.hungUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) + manager.hangUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) coVerify { room.declineCall(notificationEventId = notificationData.eventId) @@ -269,7 +269,7 @@ class DefaultActiveCallManagerTest { } @Test - fun `hungUpCall - does nothing if the CallType doesn't match`() = runTest { + fun `hangUpCall - does nothing if the CallType doesn't match`() = runTest { setupShadowPowerManager() val notificationManagerCompat = mockk(relaxed = true) val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat) @@ -278,7 +278,7 @@ class DefaultActiveCallManagerTest { assertThat(manager.activeCall.value).isNotNull() assertThat(manager.activeWakeLock?.isHeld).isTrue() - manager.hungUpCall(CallType.ExternalUrl("https://example.com")) + manager.hangUpCall(CallType.ExternalUrl("https://example.com")) assertThat(manager.activeCall.value).isNotNull() assertThat(manager.activeWakeLock?.isHeld).isTrue() diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt index 74bd1c36ac5..14b252e5723 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt @@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeActiveCallManager( var registerIncomingCallResult: (CallNotificationData) -> Unit = {}, - var hungUpCallResult: (CallType) -> Unit = {}, + var hangUpCallResult: (CallType) -> Unit = {}, var joinedCallResult: (CallType) -> Unit = {}, ) : ActiveCallManager { override val activeCall = MutableStateFlow(null) @@ -26,8 +26,8 @@ class FakeActiveCallManager( registerIncomingCallResult(notificationData) } - override suspend fun hungUpCall(callType: CallType) = simulateLongTask { - hungUpCallResult(callType) + override suspend fun hangUpCall(callType: CallType) = simulateLongTask { + hangUpCallResult(callType) } override suspend fun joinedCall(callType: CallType) = simulateLongTask { From e31acaf6b8f72270704dbcb9fa1d5606a56ebdc7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Feb 2026 17:14:49 +0100 Subject: [PATCH 5/7] Let the call be declined from the notification if the application is killed when the call is ringing. --- .../receivers/DeclineCallBroadcastReceiver.kt | 9 ++++- .../call/impl/utils/ActiveCallManager.kt | 35 +++++++++++++++---- .../call/utils/FakeActiveCallManager.kt | 6 ++-- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt index 902c4d7d8de..b9775892c3d 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt @@ -29,6 +29,7 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() { companion object { const val EXTRA_NOTIFICATION_DATA = "EXTRA_NOTIFICATION_DATA" } + @Inject lateinit var activeCallManager: ActiveCallManager @@ -40,7 +41,13 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() { ?: return context.bindings().inject(this) appCoroutineScope.launch { - activeCallManager.hangUpCall(callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId)) + activeCallManager.hangUpCall( + callType = CallType.RoomCall( + sessionId = notificationData.sessionId, + roomId = notificationData.roomId, + ), + notificationData = notificationData, + ) } } } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt index 2c0e2e7742d..e85b1011122 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt @@ -75,7 +75,10 @@ interface ActiveCallManager { * Called to hang up the active call. It will hang up the call and remove any existing UI and the active call. * @param callType The type of call that the user hangs up, either an external url one or a room one. */ - suspend fun hangUpCall(callType: CallType) + suspend fun hangUpCall( + callType: CallType, + notificationData: CallNotificationData? = null, + ) /** * Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall]. @@ -192,13 +195,30 @@ class DefaultActiveCallManager( } } - override suspend fun hangUpCall(callType: CallType) = mutex.withLock { - Timber.tag(tag).d("Hung up call: $callType") + override suspend fun hangUpCall( + callType: CallType, + notificationData: CallNotificationData?, + ) = mutex.withLock { Timber.tag(tag).d("Hang up call: $callType") + cancelIncomingCallNotification() val currentActiveCall = activeCall.value ?: run { + // activeCall.value can be null if the application has been killed while the call was ringing + // Build a currentActiveCall with the provided parameters. + if (notificationData != null) { + ActiveCall( + callType = callType, + callState = CallState.Ringing( + notificationData = notificationData, + ) + ) + } else { + null + } + } ?: run { Timber.tag(tag).w("No active call, ignoring hang up") return@withLock } + if (currentActiveCall.callType != callType) { Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring") return@withLock @@ -209,9 +229,13 @@ class DefaultActiveCallManager( matrixClientProvider.getOrRestore(notificationData.sessionId).getOrNull() ?.getRoom(notificationData.roomId) ?.declineCall(notificationData.eventId) + ?.onFailure { + Timber.e(it, "Failed to decline incoming call") + } + ?: run { + Timber.tag(tag).d("Couldn't find session or room to decline call for incoming call") + } } - - cancelIncomingCallNotification() if (activeWakeLock?.isHeld == true) { Timber.tag(tag).d("Releasing partial wakelock after hang up") activeWakeLock.release() @@ -222,7 +246,6 @@ class DefaultActiveCallManager( override suspend fun joinedCall(callType: CallType) = mutex.withLock { Timber.tag(tag).d("Joined call: $callType") - cancelIncomingCallNotification() if (activeWakeLock?.isHeld == true) { Timber.tag(tag).d("Releasing partial wakelock after joining call") diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt index 14b252e5723..2d0e126ab5f 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt @@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeActiveCallManager( var registerIncomingCallResult: (CallNotificationData) -> Unit = {}, - var hangUpCallResult: (CallType) -> Unit = {}, + var hangUpCallResult: (CallType, CallNotificationData?) -> Unit = { _, _ -> }, var joinedCallResult: (CallType) -> Unit = {}, ) : ActiveCallManager { override val activeCall = MutableStateFlow(null) @@ -26,8 +26,8 @@ class FakeActiveCallManager( registerIncomingCallResult(notificationData) } - override suspend fun hangUpCall(callType: CallType) = simulateLongTask { - hangUpCallResult(callType) + override suspend fun hangUpCall(callType: CallType, notificationData: CallNotificationData?) = simulateLongTask { + hangUpCallResult(callType, notificationData) } override suspend fun joinedCall(callType: CallType) = simulateLongTask { From 5785b3cfe8af216cc26419d89f3175c9aaf40018 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Feb 2026 18:17:13 +0100 Subject: [PATCH 6/7] Fix quality issue --- .../android/features/call/impl/utils/ActiveCallManager.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt index e85b1011122..a6663943bd1 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt @@ -74,6 +74,7 @@ interface ActiveCallManager { /** * Called to hang up the active call. It will hang up the call and remove any existing UI and the active call. * @param callType The type of call that the user hangs up, either an external url one or a room one. + * @param notificationData The data for the incoming call notification. */ suspend fun hangUpCall( callType: CallType, @@ -204,15 +205,13 @@ class DefaultActiveCallManager( val currentActiveCall = activeCall.value ?: run { // activeCall.value can be null if the application has been killed while the call was ringing // Build a currentActiveCall with the provided parameters. - if (notificationData != null) { + notificationData?.let { ActiveCall( callType = callType, callState = CallState.Ringing( notificationData = notificationData, ) ) - } else { - null } } ?: run { Timber.tag(tag).w("No active call, ignoring hang up") From 459e70f124af6a266d94432357b676bbfe7ba814 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Feb 2026 18:22:05 +0100 Subject: [PATCH 7/7] Fix test and add a new one. --- .../utils/DefaultActiveCallManagerTest.kt | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt index 2fac7634d72..6a4a215aec3 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt @@ -199,6 +199,34 @@ class DefaultActiveCallManagerTest { } } + @Test + fun `Decline event - Hangup on a unknown call should send a decline event`() = runTest { + setupShadowPowerManager() + val notificationManagerCompat = mockk(relaxed = true) + + val room = mockk(relaxed = true) + + val matrixClient = FakeMatrixClient().apply { + givenGetRoomResult(A_ROOM_ID, room) + } + val clientProvider = FakeMatrixClientProvider({ Result.success(matrixClient) }) + + val manager = createActiveCallManager( + matrixClientProvider = clientProvider, + notificationManagerCompat = notificationManagerCompat + ) + + val notificationData = aCallNotificationData(roomId = A_ROOM_ID) + // Do not register the incoming call, so the manager doesn't know about it + manager.hangUpCall( + callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId), + notificationData = notificationData, + ) + coVerify { + room.declineCall(notificationEventId = notificationData.eventId) + } + } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `Decline event - Declining from another session should stop ringing`() = runTest { @@ -282,7 +310,8 @@ class DefaultActiveCallManagerTest { assertThat(manager.activeCall.value).isNotNull() assertThat(manager.activeWakeLock?.isHeld).isTrue() - verify(exactly = 0) { notificationManagerCompat.cancel(notificationId) } + // The notification is always cancelled do not block the user + verify(exactly = 1) { notificationManagerCompat.cancel(notificationId) } } @OptIn(ExperimentalCoroutinesApi::class)