Benoit Marty
4 months ago
committed by
GitHub
19 changed files with 833 additions and 235 deletions
@ -0,0 +1,191 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.content.Intent |
||||||
|
import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory |
||||||
|
import io.element.android.libraries.core.log.logger.LoggerTag |
||||||
|
import io.element.android.libraries.matrix.api.MatrixClientProvider |
||||||
|
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.api.core.asEventId |
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoom |
||||||
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType |
||||||
|
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager |
||||||
|
import io.element.android.libraries.push.impl.R |
||||||
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent |
||||||
|
import io.element.android.libraries.push.impl.push.OnNotifiableEventReceived |
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider |
||||||
|
import io.element.android.services.toolbox.api.systemclock.SystemClock |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.flow.first |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import timber.log.Timber |
||||||
|
import java.util.UUID |
||||||
|
import javax.inject.Inject |
||||||
|
|
||||||
|
private val loggerTag = LoggerTag("NotificationBroadcastReceiverHandler", LoggerTag.NotificationLoggerTag) |
||||||
|
|
||||||
|
class NotificationBroadcastReceiverHandler @Inject constructor( |
||||||
|
private val appCoroutineScope: CoroutineScope, |
||||||
|
private val matrixClientProvider: MatrixClientProvider, |
||||||
|
private val sessionPreferencesStore: SessionPreferencesStoreFactory, |
||||||
|
private val notificationDrawerManager: NotificationDrawerManager, |
||||||
|
private val actionIds: NotificationActionIds, |
||||||
|
private val systemClock: SystemClock, |
||||||
|
private val onNotifiableEventReceived: OnNotifiableEventReceived, |
||||||
|
private val stringProvider: StringProvider, |
||||||
|
private val replyMessageExtractor: ReplyMessageExtractor, |
||||||
|
) { |
||||||
|
fun onReceive(intent: Intent) { |
||||||
|
val sessionId = intent.getStringExtra(NotificationBroadcastReceiver.KEY_SESSION_ID)?.let(::SessionId) ?: return |
||||||
|
val roomId = intent.getStringExtra(NotificationBroadcastReceiver.KEY_ROOM_ID)?.let(::RoomId) |
||||||
|
val threadId = intent.getStringExtra(NotificationBroadcastReceiver.KEY_THREAD_ID)?.let(::ThreadId) |
||||||
|
val eventId = intent.getStringExtra(NotificationBroadcastReceiver.KEY_EVENT_ID)?.let(::EventId) |
||||||
|
|
||||||
|
Timber.tag(loggerTag.value).d("onReceive: ${intent.action} ${intent.data} for: ${roomId?.value}/${eventId?.value}") |
||||||
|
when (intent.action) { |
||||||
|
actionIds.smartReply -> if (roomId != null) { |
||||||
|
handleSmartReply(sessionId, roomId, threadId, intent) |
||||||
|
} |
||||||
|
actionIds.dismissRoom -> if (roomId != null) { |
||||||
|
notificationDrawerManager.clearMessagesForRoom(sessionId, roomId) |
||||||
|
} |
||||||
|
actionIds.dismissSummary -> |
||||||
|
notificationDrawerManager.clearAllMessagesEvents(sessionId) |
||||||
|
actionIds.dismissInvite -> if (roomId != null) { |
||||||
|
notificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId) |
||||||
|
} |
||||||
|
actionIds.dismissEvent -> if (eventId != null) { |
||||||
|
notificationDrawerManager.clearEvent(sessionId, eventId) |
||||||
|
} |
||||||
|
actionIds.markRoomRead -> if (roomId != null) { |
||||||
|
notificationDrawerManager.clearMessagesForRoom(sessionId, roomId) |
||||||
|
handleMarkAsRead(sessionId, roomId) |
||||||
|
} |
||||||
|
actionIds.join -> if (roomId != null) { |
||||||
|
notificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId) |
||||||
|
handleJoinRoom(sessionId, roomId) |
||||||
|
} |
||||||
|
actionIds.reject -> if (roomId != null) { |
||||||
|
notificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId) |
||||||
|
handleRejectRoom(sessionId, roomId) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun handleJoinRoom(sessionId: SessionId, roomId: RoomId) = appCoroutineScope.launch { |
||||||
|
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return@launch |
||||||
|
client.joinRoom(roomId) |
||||||
|
} |
||||||
|
|
||||||
|
private fun handleRejectRoom(sessionId: SessionId, roomId: RoomId) = appCoroutineScope.launch { |
||||||
|
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return@launch |
||||||
|
client.getRoom(roomId)?.leave() |
||||||
|
} |
||||||
|
|
||||||
|
private fun handleMarkAsRead(sessionId: SessionId, roomId: RoomId) = appCoroutineScope.launch { |
||||||
|
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return@launch |
||||||
|
val isSendPublicReadReceiptsEnabled = sessionPreferencesStore.get(sessionId, this).isSendPublicReadReceiptsEnabled().first() |
||||||
|
val receiptType = if (isSendPublicReadReceiptsEnabled) { |
||||||
|
ReceiptType.READ |
||||||
|
} else { |
||||||
|
ReceiptType.READ_PRIVATE |
||||||
|
} |
||||||
|
client.getRoom(roomId)?.markAsRead(receiptType = receiptType) |
||||||
|
} |
||||||
|
|
||||||
|
private fun handleSmartReply( |
||||||
|
sessionId: SessionId, |
||||||
|
roomId: RoomId, |
||||||
|
threadId: ThreadId?, |
||||||
|
intent: Intent, |
||||||
|
) = appCoroutineScope.launch { |
||||||
|
val message = replyMessageExtractor.getReplyMessage(intent) |
||||||
|
if (message.isNullOrBlank()) { |
||||||
|
// ignore this event |
||||||
|
// Can this happen? should we update notification? |
||||||
|
return@launch |
||||||
|
} |
||||||
|
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return@launch |
||||||
|
client.getRoom(roomId)?.let { room -> |
||||||
|
sendMatrixEvent( |
||||||
|
sessionId = sessionId, |
||||||
|
roomId = roomId, |
||||||
|
threadId = threadId, |
||||||
|
room = room, |
||||||
|
message = message, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private suspend fun sendMatrixEvent( |
||||||
|
sessionId: SessionId, |
||||||
|
roomId: RoomId, |
||||||
|
threadId: ThreadId?, |
||||||
|
room: MatrixRoom, |
||||||
|
message: String, |
||||||
|
) { |
||||||
|
// Create a new event to be displayed in the notification drawer, right now |
||||||
|
val notifiableMessageEvent = NotifiableMessageEvent( |
||||||
|
sessionId = sessionId, |
||||||
|
roomId = roomId, |
||||||
|
// Generate a Fake event id |
||||||
|
eventId = EventId("\$" + UUID.randomUUID().toString()), |
||||||
|
editedEventId = null, |
||||||
|
canBeReplaced = false, |
||||||
|
senderId = sessionId, |
||||||
|
noisy = false, |
||||||
|
timestamp = systemClock.epochMillis(), |
||||||
|
senderDisambiguatedDisplayName = room.getUpdatedMember(sessionId).getOrNull() |
||||||
|
?.disambiguatedDisplayName |
||||||
|
?: stringProvider.getString(R.string.notification_sender_me), |
||||||
|
body = message, |
||||||
|
imageUriString = null, |
||||||
|
threadId = threadId, |
||||||
|
roomName = room.displayName, |
||||||
|
roomIsDirect = room.isDirect, |
||||||
|
outGoingMessage = true, |
||||||
|
) |
||||||
|
onNotifiableEventReceived.onNotifiableEventReceived(notifiableMessageEvent) |
||||||
|
|
||||||
|
if (threadId != null) { |
||||||
|
room.liveTimeline.replyMessage( |
||||||
|
eventId = threadId.asEventId(), |
||||||
|
body = message, |
||||||
|
htmlBody = null, |
||||||
|
mentions = emptyList(), |
||||||
|
fromNotification = true, |
||||||
|
) |
||||||
|
} else { |
||||||
|
room.liveTimeline.sendMessage( |
||||||
|
body = message, |
||||||
|
htmlBody = null, |
||||||
|
mentions = emptyList() |
||||||
|
) |
||||||
|
}.onFailure { |
||||||
|
Timber.e(it, "Failed to send smart reply message") |
||||||
|
onNotifiableEventReceived.onNotifiableEventReceived( |
||||||
|
notifiableMessageEvent.copy( |
||||||
|
outGoingMessageFailed = true |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.content.Intent |
||||||
|
import androidx.core.app.RemoteInput |
||||||
|
import com.squareup.anvil.annotations.ContributesBinding |
||||||
|
import io.element.android.libraries.di.AppScope |
||||||
|
import javax.inject.Inject |
||||||
|
|
||||||
|
interface ReplyMessageExtractor { |
||||||
|
fun getReplyMessage(intent: Intent): String? |
||||||
|
} |
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class) |
||||||
|
class AndroidReplyMessageExtractor @Inject constructor() : ReplyMessageExtractor { |
||||||
|
override fun getReplyMessage(intent: Intent): String? { |
||||||
|
return RemoteInput.getResultsFromIntent(intent) |
||||||
|
?.getCharSequence(NotificationBroadcastReceiver.KEY_TEXT_REPLY) |
||||||
|
?.toString() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.content.Intent |
||||||
|
|
||||||
|
class FakeReplyMessageExtractor( |
||||||
|
private val result: String? = null, |
||||||
|
) : ReplyMessageExtractor { |
||||||
|
override fun getReplyMessage(intent: Intent): String? { |
||||||
|
return result |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,474 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2024 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.content.Intent |
||||||
|
import com.google.common.truth.Truth.assertThat |
||||||
|
import io.element.android.features.preferences.api.store.SessionPreferencesStore |
||||||
|
import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory |
||||||
|
import io.element.android.libraries.matrix.api.MatrixClient |
||||||
|
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.api.core.asEventId |
||||||
|
import io.element.android.libraries.matrix.api.room.Mention |
||||||
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType |
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID |
||||||
|
import io.element.android.libraries.matrix.test.A_MESSAGE |
||||||
|
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_THREAD_ID |
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient |
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider |
||||||
|
import io.element.android.libraries.matrix.test.core.aBuildMeta |
||||||
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom |
||||||
|
import io.element.android.libraries.matrix.test.timeline.FakeTimeline |
||||||
|
import io.element.android.libraries.preferences.test.FakeSessionPreferencesStoreFactory |
||||||
|
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore |
||||||
|
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager |
||||||
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent |
||||||
|
import io.element.android.libraries.push.impl.push.FakeOnNotifiableEventReceived |
||||||
|
import io.element.android.libraries.push.impl.push.OnNotifiableEventReceived |
||||||
|
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager |
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider |
||||||
|
import io.element.android.services.toolbox.api.systemclock.SystemClock |
||||||
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider |
||||||
|
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock |
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError |
||||||
|
import io.element.android.tests.testutils.lambda.lambdaRecorder |
||||||
|
import io.element.android.tests.testutils.lambda.value |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi |
||||||
|
import kotlinx.coroutines.test.TestScope |
||||||
|
import kotlinx.coroutines.test.runCurrent |
||||||
|
import kotlinx.coroutines.test.runTest |
||||||
|
import org.junit.Test |
||||||
|
import org.junit.runner.RunWith |
||||||
|
import org.robolectric.RobolectricTestRunner |
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi |
||||||
|
@RunWith(RobolectricTestRunner::class) |
||||||
|
class NotificationBroadcastReceiverHandlerTest { |
||||||
|
private val actionIds = NotificationActionIds(aBuildMeta()) |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `When no sessionId, nothing happen`() = runTest { |
||||||
|
val sut = createNotificationBroadcastReceiverHandler() |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.join, |
||||||
|
sessionId = null |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test dismiss room without a roomId, nothing happen`() = runTest { |
||||||
|
val sut = createNotificationBroadcastReceiverHandler() |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.dismissRoom, |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test dismiss room`() = runTest { |
||||||
|
val clearMessagesForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ -> } |
||||||
|
val notificationDrawerManager = FakeNotificationDrawerManager( |
||||||
|
clearMessagesForRoomLambda = clearMessagesForRoomLambda, |
||||||
|
) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
notificationDrawerManager = notificationDrawerManager |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.dismissRoom, |
||||||
|
roomId = A_ROOM_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
runCurrent() |
||||||
|
clearMessagesForRoomLambda.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_SESSION_ID), value(A_ROOM_ID)) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test dismiss summary`() = runTest { |
||||||
|
val clearAllMessagesEventsLambda = lambdaRecorder<SessionId, Unit> { _ -> } |
||||||
|
val notificationDrawerManager = FakeNotificationDrawerManager( |
||||||
|
clearAllMessagesEventsLambda = clearAllMessagesEventsLambda, |
||||||
|
) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
notificationDrawerManager = notificationDrawerManager |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.dismissSummary, |
||||||
|
), |
||||||
|
) |
||||||
|
clearAllMessagesEventsLambda.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_SESSION_ID)) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test dismiss Invite without room`() = runTest { |
||||||
|
val sut = createNotificationBroadcastReceiverHandler() |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.dismissInvite, |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test dismiss Invite`() = runTest { |
||||||
|
val clearMembershipNotificationForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ -> } |
||||||
|
val notificationDrawerManager = FakeNotificationDrawerManager( |
||||||
|
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda, |
||||||
|
) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
notificationDrawerManager = notificationDrawerManager |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.dismissInvite, |
||||||
|
roomId = A_ROOM_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
clearMembershipNotificationForRoomLambda.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_SESSION_ID), value(A_ROOM_ID)) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test dismiss Event without event`() = runTest { |
||||||
|
val sut = createNotificationBroadcastReceiverHandler() |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.dismissEvent, |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test dismiss Event`() = runTest { |
||||||
|
val clearEventLambda = lambdaRecorder<SessionId, EventId, Unit> { _, _ -> } |
||||||
|
val notificationDrawerManager = FakeNotificationDrawerManager( |
||||||
|
clearEventLambda = clearEventLambda, |
||||||
|
) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
notificationDrawerManager = notificationDrawerManager |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.dismissEvent, |
||||||
|
eventId = AN_EVENT_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
clearEventLambda.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_SESSION_ID), value(AN_EVENT_ID)) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test mark room as read without room`() = runTest { |
||||||
|
val sut = createNotificationBroadcastReceiverHandler() |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.markRoomRead, |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test mark room as read, send public RR`() { |
||||||
|
testMarkRoomAsRead( |
||||||
|
isSendPublicReadReceiptsEnabled = true, |
||||||
|
expectedReceiptType = ReceiptType.READ |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test mark room as read, send private RR`() { |
||||||
|
testMarkRoomAsRead( |
||||||
|
isSendPublicReadReceiptsEnabled = false, |
||||||
|
expectedReceiptType = ReceiptType.READ_PRIVATE |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
private fun testMarkRoomAsRead( |
||||||
|
isSendPublicReadReceiptsEnabled: Boolean, |
||||||
|
expectedReceiptType: ReceiptType, |
||||||
|
) = runTest { |
||||||
|
val getLambda = lambdaRecorder<SessionId, CoroutineScope, SessionPreferencesStore> { _, _ -> |
||||||
|
InMemorySessionPreferencesStore( |
||||||
|
isSendPublicReadReceiptsEnabled = isSendPublicReadReceiptsEnabled |
||||||
|
) |
||||||
|
} |
||||||
|
val sessionPreferencesStore = FakeSessionPreferencesStoreFactory( |
||||||
|
getLambda = getLambda |
||||||
|
) |
||||||
|
val clearMessagesForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ -> } |
||||||
|
val matrixRoom = FakeMatrixRoom() |
||||||
|
val notificationDrawerManager = FakeNotificationDrawerManager( |
||||||
|
clearMessagesForRoomLambda = clearMessagesForRoomLambda, |
||||||
|
) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
sessionPreferencesStore = sessionPreferencesStore, |
||||||
|
matrixRoom = matrixRoom, |
||||||
|
notificationDrawerManager = notificationDrawerManager |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.markRoomRead, |
||||||
|
roomId = A_ROOM_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
runCurrent() |
||||||
|
clearMessagesForRoomLambda.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_SESSION_ID), value(A_ROOM_ID)) |
||||||
|
assertThat(matrixRoom.markAsReadCalls).isEqualTo(listOf(expectedReceiptType)) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test join room without room`() = runTest { |
||||||
|
val sut = createNotificationBroadcastReceiverHandler() |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.join, |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test join room`() = runTest { |
||||||
|
val joinRoom = lambdaRecorder<RoomId, Result<Unit>> { _ -> Result.success(Unit) } |
||||||
|
val clearMembershipNotificationForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ -> } |
||||||
|
val notificationDrawerManager = FakeNotificationDrawerManager( |
||||||
|
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda, |
||||||
|
) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
joinRoom = joinRoom, |
||||||
|
notificationDrawerManager = notificationDrawerManager, |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.join, |
||||||
|
roomId = A_ROOM_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
runCurrent() |
||||||
|
joinRoom.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_ROOM_ID)) |
||||||
|
clearMembershipNotificationForRoomLambda.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_SESSION_ID), value(A_ROOM_ID)) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test reject room without room`() = runTest { |
||||||
|
val sut = createNotificationBroadcastReceiverHandler() |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.reject, |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test reject room`() = runTest { |
||||||
|
val leaveRoom = lambdaRecorder<Result<Unit>> { Result.success(Unit) } |
||||||
|
val matrixRoom = FakeMatrixRoom().apply { |
||||||
|
leaveRoomLambda = leaveRoom |
||||||
|
} |
||||||
|
val clearMembershipNotificationForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ -> } |
||||||
|
val notificationDrawerManager = FakeNotificationDrawerManager( |
||||||
|
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda, |
||||||
|
) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
matrixRoom = matrixRoom, |
||||||
|
notificationDrawerManager = notificationDrawerManager |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.reject, |
||||||
|
roomId = A_ROOM_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
runCurrent() |
||||||
|
clearMembershipNotificationForRoomLambda.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_SESSION_ID), value(A_ROOM_ID)) |
||||||
|
leaveRoom.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with() |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test send reply without room`() = runTest { |
||||||
|
val sut = createNotificationBroadcastReceiverHandler() |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.smartReply, |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test send reply`() = runTest { |
||||||
|
val sendMessage = lambdaRecorder<String, String?, List<Mention>, Result<Unit>> { _, _, _ -> Result.success(Unit) } |
||||||
|
val replyMessage = lambdaRecorder<EventId, String, String?, List<Mention>, Boolean, Result<Unit>> { _, _, _, _, _ -> Result.success(Unit) } |
||||||
|
val liveTimeline = FakeTimeline().apply { |
||||||
|
sendMessageLambda = sendMessage |
||||||
|
replyMessageLambda = replyMessage |
||||||
|
} |
||||||
|
val matrixRoom = FakeMatrixRoom( |
||||||
|
liveTimeline = liveTimeline |
||||||
|
) |
||||||
|
val onNotifiableEventReceivedResult = lambdaRecorder<NotifiableEvent, Unit> { _ -> } |
||||||
|
val onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceivedResult = onNotifiableEventReceivedResult) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
matrixRoom = matrixRoom, |
||||||
|
onNotifiableEventReceived = onNotifiableEventReceived, |
||||||
|
replyMessageExtractor = FakeReplyMessageExtractor(A_MESSAGE) |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.smartReply, |
||||||
|
roomId = A_ROOM_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
runCurrent() |
||||||
|
sendMessage.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_MESSAGE), value(null), value(emptyList<Mention>())) |
||||||
|
onNotifiableEventReceivedResult.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
replyMessage.assertions() |
||||||
|
.isNeverCalled() |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test send reply blank message`() = runTest { |
||||||
|
val sendMessage = lambdaRecorder<String, String?, List<Mention>, Result<Unit>> { _, _, _ -> Result.success(Unit) } |
||||||
|
val liveTimeline = FakeTimeline().apply { |
||||||
|
sendMessageLambda = sendMessage |
||||||
|
} |
||||||
|
val matrixRoom = FakeMatrixRoom( |
||||||
|
liveTimeline = liveTimeline |
||||||
|
) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
matrixRoom = matrixRoom, |
||||||
|
replyMessageExtractor = FakeReplyMessageExtractor(" "), |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.smartReply, |
||||||
|
roomId = A_ROOM_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
runCurrent() |
||||||
|
sendMessage.assertions() |
||||||
|
.isNeverCalled() |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Test send reply to thread`() = runTest { |
||||||
|
val sendMessage = lambdaRecorder<String, String?, List<Mention>, Result<Unit>> { _, _, _ -> Result.success(Unit) } |
||||||
|
val replyMessage = lambdaRecorder<EventId, String, String?, List<Mention>, Boolean, Result<Unit>> { _, _, _, _, _ -> Result.success(Unit) } |
||||||
|
val liveTimeline = FakeTimeline().apply { |
||||||
|
sendMessageLambda = sendMessage |
||||||
|
replyMessageLambda = replyMessage |
||||||
|
} |
||||||
|
val matrixRoom = FakeMatrixRoom( |
||||||
|
liveTimeline = liveTimeline |
||||||
|
) |
||||||
|
val onNotifiableEventReceivedResult = lambdaRecorder<NotifiableEvent, Unit> { _ -> } |
||||||
|
val onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceivedResult = onNotifiableEventReceivedResult) |
||||||
|
val sut = createNotificationBroadcastReceiverHandler( |
||||||
|
matrixRoom = matrixRoom, |
||||||
|
onNotifiableEventReceived = onNotifiableEventReceived, |
||||||
|
replyMessageExtractor = FakeReplyMessageExtractor(A_MESSAGE) |
||||||
|
) |
||||||
|
sut.onReceive( |
||||||
|
createIntent( |
||||||
|
action = actionIds.smartReply, |
||||||
|
roomId = A_ROOM_ID, |
||||||
|
threadId = A_THREAD_ID, |
||||||
|
), |
||||||
|
) |
||||||
|
runCurrent() |
||||||
|
sendMessage.assertions() |
||||||
|
.isNeverCalled() |
||||||
|
onNotifiableEventReceivedResult.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
replyMessage.assertions() |
||||||
|
.isCalledOnce() |
||||||
|
.with(value(A_THREAD_ID.asEventId()), value(A_MESSAGE), value(null), value(emptyList<Mention>()), value(true)) |
||||||
|
} |
||||||
|
|
||||||
|
private fun createIntent( |
||||||
|
action: String, |
||||||
|
sessionId: SessionId? = A_SESSION_ID, |
||||||
|
roomId: RoomId? = null, |
||||||
|
eventId: EventId? = null, |
||||||
|
threadId: ThreadId? = null, |
||||||
|
) = Intent(action).apply { |
||||||
|
putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, sessionId?.value) |
||||||
|
putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId?.value) |
||||||
|
putExtra(NotificationBroadcastReceiver.KEY_THREAD_ID, threadId?.value) |
||||||
|
putExtra(NotificationBroadcastReceiver.KEY_EVENT_ID, eventId?.value) |
||||||
|
} |
||||||
|
|
||||||
|
private fun TestScope.createNotificationBroadcastReceiverHandler( |
||||||
|
matrixRoom: FakeMatrixRoom? = FakeMatrixRoom(), |
||||||
|
joinRoom: (RoomId) -> Result<Unit> = { lambdaError() }, |
||||||
|
matrixClient: MatrixClient? = FakeMatrixClient().apply { |
||||||
|
givenGetRoomResult(A_ROOM_ID, matrixRoom) |
||||||
|
joinRoomLambda = joinRoom |
||||||
|
}, |
||||||
|
sessionPreferencesStore: SessionPreferencesStoreFactory = FakeSessionPreferencesStoreFactory(), |
||||||
|
notificationDrawerManager: NotificationDrawerManager = FakeNotificationDrawerManager(), |
||||||
|
systemClock: SystemClock = FakeSystemClock(), |
||||||
|
onNotifiableEventReceived: OnNotifiableEventReceived = FakeOnNotifiableEventReceived(), |
||||||
|
stringProvider: StringProvider = FakeStringProvider(), |
||||||
|
replyMessageExtractor: ReplyMessageExtractor = FakeReplyMessageExtractor(), |
||||||
|
): NotificationBroadcastReceiverHandler { |
||||||
|
return NotificationBroadcastReceiverHandler( |
||||||
|
appCoroutineScope = this, |
||||||
|
matrixClientProvider = FakeMatrixClientProvider { |
||||||
|
if (matrixClient == null) { |
||||||
|
Result.failure(Exception("No matrix client")) |
||||||
|
} else { |
||||||
|
Result.success(matrixClient) |
||||||
|
} |
||||||
|
}, |
||||||
|
sessionPreferencesStore = sessionPreferencesStore, |
||||||
|
notificationDrawerManager = notificationDrawerManager, |
||||||
|
actionIds = actionIds, |
||||||
|
systemClock = systemClock, |
||||||
|
onNotifiableEventReceived = onNotifiableEventReceived, |
||||||
|
stringProvider = stringProvider, |
||||||
|
replyMessageExtractor = replyMessageExtractor, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue