From 6a34b999f2879c9f4ef8ba61caf349d9521687ff Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 22 Oct 2021 18:09:11 +0200 Subject: [PATCH 01/10] New SDK Api for to device and event stream listener --- .../sdk/api/session/EventStreamService.kt | 24 ++++ .../sdk/api/session/LiveEventListener.kt | 35 ++++++ .../matrix/android/sdk/api/session/Session.kt | 4 +- .../sdk/api/session/ToDeviceService.kt | 37 +++++++ .../internal/crypto/DefaultCryptoService.kt | 5 +- .../algorithms/megolm/MXMegolmDecryption.kt | 10 +- .../megolm/MXMegolmDecryptionFactory.kt | 8 +- .../session/DefaultEventStreamService.kt | 34 ++++++ .../sdk/internal/session/DefaultSession.kt | 8 +- .../session/DefaultToDeviceService.kt | 74 +++++++++++++ .../sdk/internal/session/SessionModule.kt | 8 ++ .../internal/session/StreamEventsManager.kt | 103 ++++++++++++++++++ .../room/timeline/TokenChunkEventPersistor.kt | 9 +- .../sync/handler/room/RoomSyncHandler.kt | 6 +- 14 files changed, 354 insertions(+), 11 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/EventStreamService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultEventStreamService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/EventStreamService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/EventStreamService.kt new file mode 100644 index 0000000000..a1316a5444 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/EventStreamService.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session + +interface EventStreamService { + + fun addEventStreamListener(streamListener: LiveEventListener) + + fun removeEventStreamListener(streamListener: LiveEventListener) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt new file mode 100644 index 0000000000..6fda65953a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.util.JsonDict + +interface LiveEventListener { + + fun onLiveEvent(roomId: String, event: Event) + + fun onPaginatedEvent(roomId: String, event: Event) + + fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) + + fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) + + fun onLiveToDeviceEvent(event: Event) + + // Maybe later add more, like onJoin, onLeave.. +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 3f817ec4d2..36ab007314 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -84,7 +84,9 @@ interface Session : SyncStatusService, HomeServerCapabilitiesService, SecureStorageService, - AccountService { + AccountService, + ToDeviceService, + EventStreamService { val coroutineDispatchers: MatrixCoroutineDispatchers diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt new file mode 100644 index 0000000000..45fd39fa95 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session + +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap +import java.util.UUID + +interface ToDeviceService { + + /** + * Send an event to a specific list of devices + */ + suspend fun sendToDevice(eventType: String, contentMap: MXUsersDevicesMap, txnId: String? = UUID.randomUUID().toString()) + + suspend fun sendToDevice(eventType: String, userId: String, deviceId: String, content: Content, txnId: String? = UUID.randomUUID().toString()) { + sendToDevice(eventType, mapOf(userId to listOf(deviceId)), content, txnId) + } + + suspend fun sendToDevice(eventType: String, targets: Map>, content: Content, txnId: String? = UUID.randomUUID().toString()) + + suspend fun sendEncryptedToDevice(eventType: String, targets: Map>, content: Content, txnId: String? = UUID.randomUUID().toString()) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 9dd369f426..00cd278921 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -90,6 +90,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskThread @@ -168,7 +169,8 @@ internal class DefaultCryptoService @Inject constructor( private val coroutineDispatchers: MatrixCoroutineDispatchers, private val taskExecutor: TaskExecutor, private val cryptoCoroutineScope: CoroutineScope, - private val eventDecryptor: EventDecryptor + private val eventDecryptor: EventDecryptor, + private val liveEventManager: Lazy ) : CryptoService { private val isStarting = AtomicBoolean(false) @@ -782,6 +784,7 @@ internal class DefaultCryptoService @Inject constructor( } } } + liveEventManager.get().dispatchOnLiveToDevice(event) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index ceceedc802..2ee24dfbb0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm +import dagger.Lazy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCoroutineDispatchers @@ -43,6 +44,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.session.StreamEventsManager import timber.log.Timber private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO) @@ -56,7 +58,8 @@ internal class MXMegolmDecryption(private val userId: String, private val cryptoStore: IMXCryptoStore, private val sendToDeviceTask: SendToDeviceTask, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope + private val cryptoCoroutineScope: CoroutineScope, + private val liveEventManager: Lazy ) : IMXDecrypting, IMXWithHeldExtension { var newSessionListener: NewSessionListener? = null @@ -108,12 +111,15 @@ internal class MXMegolmDecryption(private val userId: String, claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"), forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain .orEmpty() - ) + ).also { + liveEventManager.get().dispatchLiveEventDecrypted(event, it) + } } else { throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON) } }, { throwable -> + liveEventManager.get().dispatchLiveEventDecryptionFailed(event, throwable) if (throwable is MXCryptoError.OlmError) { // TODO Check the value of .message if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt index 29f9d193f8..3eba04b9f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm +import dagger.Lazy import kotlinx.coroutines.CoroutineScope import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.DeviceListManager @@ -26,6 +27,7 @@ import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.StreamEventsManager import javax.inject.Inject internal class MXMegolmDecryptionFactory @Inject constructor( @@ -38,7 +40,8 @@ internal class MXMegolmDecryptionFactory @Inject constructor( private val cryptoStore: IMXCryptoStore, private val sendToDeviceTask: SendToDeviceTask, private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val cryptoCoroutineScope: CoroutineScope + private val cryptoCoroutineScope: CoroutineScope, + private val eventsManager: Lazy ) { fun create(): MXMegolmDecryption { @@ -52,6 +55,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor( cryptoStore, sendToDeviceTask, coroutineDispatchers, - cryptoCoroutineScope) + cryptoCoroutineScope, + eventsManager) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultEventStreamService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultEventStreamService.kt new file mode 100644 index 0000000000..ed21e9f1c6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultEventStreamService.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session + +import org.matrix.android.sdk.api.session.EventStreamService +import org.matrix.android.sdk.api.session.LiveEventListener +import javax.inject.Inject + +internal class DefaultEventStreamService @Inject constructor( + private val streamEventsManager: StreamEventsManager +) : EventStreamService { + + override fun addEventStreamListener(streamListener: LiveEventListener) { + streamEventsManager.addLiveEventListener(streamListener) + } + + override fun removeEventStreamListener(streamListener: LiveEventListener) { + streamEventsManager.removeLiveEventListener(streamListener) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index c07ff48cf4..1e533158a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -27,8 +27,10 @@ import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService import org.matrix.android.sdk.api.pushrules.PushRuleService +import org.matrix.android.sdk.api.session.EventStreamService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.ToDeviceService import org.matrix.android.sdk.api.session.account.AccountService import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.cache.CacheService @@ -133,6 +135,8 @@ internal class DefaultSession @Inject constructor( private val spaceService: Lazy, private val openIdService: Lazy, private val presenceService: Lazy, + private val toDeviceService: Lazy, + private val eventStreamService: Lazy, @UnauthenticatedWithCertificate private val unauthenticatedWithCertificateOkHttpClient: Lazy ) : Session, @@ -152,7 +156,9 @@ internal class DefaultSession @Inject constructor( HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), ProfileService by profileService.get(), PresenceService by presenceService.get(), - AccountService by accountService.get() { + AccountService by accountService.get(), + ToDeviceService by toDeviceService.get(), + EventStreamService by eventStreamService.get() { override val sharedSecretStorageService: SharedSecretStorageService get() = _sharedSecretStorageService.get() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt new file mode 100644 index 0000000000..5bdb969113 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session + +import org.matrix.android.sdk.api.session.ToDeviceService +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter +import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import javax.inject.Inject + +internal class DefaultToDeviceService @Inject constructor( + private val sendToDeviceTask: DefaultSendToDeviceTask, + private val messageEncrypter: MessageEncrypter, + private val cryptoStore: IMXCryptoStore +) : ToDeviceService { + + override suspend fun sendToDevice(eventType: String, targets: Map>, content: Content, txnId: String?) { + val sendToDeviceMap = MXUsersDevicesMap() + targets.forEach { (userId, deviceIdList) -> + deviceIdList.forEach { deviceId -> + sendToDeviceMap.setObject(userId, deviceId, content) + } + } + sendToDevice(eventType, sendToDeviceMap, txnId) + } + + override suspend fun sendToDevice(eventType: String, contentMap: MXUsersDevicesMap, txnId: String?) { + sendToDeviceTask.executeRetry( + SendToDeviceTask.Params( + eventType = eventType, + contentMap = contentMap, + transactionId = txnId + ), + 3 + ) + } + + override suspend fun sendEncryptedToDevice(eventType: String, targets: Map>, content: Content, txnId: String?) { + val payloadJson = mapOf( + "type" to eventType, + "content" to content + ) + val sendToDeviceMap = MXUsersDevicesMap() + + // Should I do an ensure olm session? + targets.forEach { (userId, deviceIdList) -> + deviceIdList.forEach { deviceId -> + cryptoStore.getUserDevice(userId, deviceId)?.let { deviceInfo -> + sendToDeviceMap.setObject(userId, deviceId, messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))) + } + } + } + + sendToDevice(EventType.ENCRYPTED, sendToDeviceMap, txnId) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index e2cfea479d..cf6ff4a872 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -32,8 +32,10 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.crypto.MXCryptoConfig +import org.matrix.android.sdk.api.session.EventStreamService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.ToDeviceService import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService @@ -382,4 +384,10 @@ internal abstract class SessionModule { @Binds abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor + + @Binds + abstract fun bindToDeviceService(deviceService: DefaultToDeviceService): ToDeviceService + + @Binds + abstract fun bindEventStreamService(deviceService: DefaultEventStreamService): EventStreamService } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt new file mode 100644 index 0000000000..c39f5fded8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.LiveEventListener +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult +import timber.log.Timber +import javax.inject.Inject + +@SessionScope +internal class StreamEventsManager @Inject constructor() { + + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + private val listeners = mutableListOf() + + fun addLiveEventListener(listener: LiveEventListener) { + Timber.v("## VALR: addLiveEventListener") + listeners.add(listener) + } + + fun removeLiveEventListener(listener: LiveEventListener) { + Timber.v("## VALR: removeLiveEventListener") + listeners.remove(listener) + } + + fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { + Timber.v("## VALR: dispatchLiveEventReceived ${event.eventId}") + coroutineScope.launch { + if (!initialSync) { + listeners.forEach { + tryOrNull { + it.onLiveEvent(roomId, event) + } + } + } + } + } + + fun dispatchPaginatedEventReceived(event: Event, roomId: String) { + Timber.v("## VALR: dispatchPaginatedEventReceived ${event.eventId}") + coroutineScope.launch { + listeners.forEach { + tryOrNull { + it.onPaginatedEvent(roomId, event) + } + } + } + } + + fun dispatchLiveEventDecrypted(event: Event, result: MXEventDecryptionResult) { + Timber.v("## VALR: dispatchLiveEventDecrypted ${event.eventId}") + coroutineScope.launch { + listeners.forEach { + tryOrNull { + it.onEventDecrypted(event.eventId ?: "", event.roomId ?: "", result.clearEvent) + } + } + } + } + + fun dispatchLiveEventDecryptionFailed(event: Event, error: Throwable) { + Timber.v("## VALR: dispatchLiveEventDecryptionFailed ${event.eventId}") + coroutineScope.launch { + listeners.forEach { + tryOrNull { + it.onEventDecryptionError(event.eventId ?: "", event.roomId ?: "", error) + } + } + } + } + + fun dispatchOnLiveToDevice(event: Event) { + Timber.v("## VALR: dispatchOnLiveToDevice ${event.eventId}") + coroutineScope.launch { + listeners.forEach { + tryOrNull { + it.onLiveToDeviceEvent(event) + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 4625155c0a..62e3896ac5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -17,12 +17,12 @@ package org.matrix.android.sdk.internal.session.room.timeline import com.zhuinden.monarchy.Monarchy +import dagger.Lazy import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addStateEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.toEntity @@ -35,14 +35,16 @@ import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.session.StreamEventsManager import timber.log.Timber import javax.inject.Inject /** * Insert Chunk in DB, and eventually link next and previous chunk in db. */ -internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { +internal class TokenChunkEventPersistor @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val liveEventManager: Lazy) { enum class Result { SHOULD_FETCH_MORE, @@ -170,6 +172,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri } roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() } + liveEventManager.get().dispatchPaginatedEventReceived(event, roomId) currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index f090975cad..3fdfb473db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room +import dagger.Lazy import io.realm.Realm import io.realm.kotlin.createObject import org.matrix.android.sdk.api.session.crypto.MXCryptoError @@ -52,6 +53,7 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.clearWith +import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.mapWithProgress @@ -79,7 +81,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private val threadsAwarenessHandler: ThreadsAwarenessHandler, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, @UserId private val userId: String, - private val timelineInput: TimelineInput) { + private val timelineInput: TimelineInput, + private val liveEventService: Lazy) { sealed class HandlingStrategy { data class JOINED(val data: Map) : HandlingStrategy() @@ -364,6 +367,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle continue } eventIds.add(event.eventId) + liveEventService.get().dispatchLiveEventReceived(event, roomId, insertType == EventInsertType.INITIAL_SYNC) val isInitialSync = insertType == EventInsertType.INITIAL_SYNC From be119ea161c96567fc01d679a1a8a88097d461c5 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 22 Oct 2021 18:10:27 +0200 Subject: [PATCH 02/10] Support for lab auto rageshake UISI --- .idea/dictionaries/bmarty.xml | 1 + .../main/java/im/vector/app/AutoRageShaker.kt | 204 ++++++++++++++++++ .../main/java/im/vector/app/UISIDetector.kt | 153 +++++++++++++ .../java/im/vector/app/VectorApplication.kt | 2 + .../features/rageshake/BugReportActivity.kt | 3 +- .../app/features/rageshake/BugReporter.kt | 40 +++- .../app/features/rageshake/ReportType.kt | 4 +- .../features/settings/VectorPreferences.kt | 7 +- .../settings/VectorSettingsLabsFragment.kt | 17 +- vector/src/main/res/values/strings.xml | 5 + .../src/main/res/xml/vector_settings_labs.xml | 11 +- 11 files changed, 430 insertions(+), 17 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/AutoRageShaker.kt create mode 100644 vector/src/main/java/im/vector/app/UISIDetector.kt diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index a2e408b50d..f99842f067 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -36,6 +36,7 @@ ssss sygnal threepid + uisi unpublish unwedging vctr diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt new file mode 100644 index 0000000000..d11a24e38c --- /dev/null +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app + +import android.content.Context +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.rageshake.BugReporter +import im.vector.app.features.rageshake.ReportType +import io.reactivex.disposables.Disposable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toContent +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +const val AUTO_RS_REQUEST = "im.vector.auto_rs_request" + +@Singleton +class AutoRageShaker @Inject constructor( + private val sessionDataSource: ActiveSessionDataSource, + private val activeSessionHolder: ActiveSessionHolder, + private val bugReporter: BugReporter, + private val context: Context +) : Session.Listener { + + private lateinit var activeSessionDisposable: Disposable + private val activeSessionIds = mutableSetOf() + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private val uisiDetectors = mutableMapOf() + private var currentActiveSessionId: String? = null + + fun initialize() { + observeActiveSession() + } + + var _enabled = false + fun enable(enabled: Boolean) { + uisiDetectors.forEach { it.value.enabled = enabled } + } + + private fun observeActiveSession() { + activeSessionDisposable = sessionDataSource.observe() + .distinctUntilChanged() + .subscribe { + it.orNull()?.let { session -> + onSessionActive(session) + } + } + } + + fun decryptionErrorDetected(target: E2EMessageDetected) { + if (target.source == UISIEventSource.INITIAL_SYNC) return + if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return + coroutineScope.launch { + bugReporter.sendBugReport( + context = context, + reportType = ReportType.AUTO_UISI, + withDevicesLogs = true, + withCrashLogs = true, + withKeyRequestHistory = true, + withScreenshot = false, + theBugDescription = "UISI detected", + serverVersion = "", + canContact = false, + customFields = mapOf("auto-uisi" to buildString { + append("\neventId: ${target.eventId}") + append("\nroomId: ${target.roomId}") + append("\nsenderKey: ${target.senderKey}") + append("\nsource: ${target.source}") + append("\ndeviceId: ${target.senderDeviceId}") + append("\nuserId: ${target.senderUserId}") + append("\nsessionId: ${target.sessionId}") + }), + listener = object : BugReporter.IMXBugReportListener { + override fun onUploadCancelled() { + } + + override fun onUploadFailed(reason: String?) { + } + + override fun onProgress(progress: Int) { + } + + override fun onUploadSucceed(reportUrl: String?) { + Timber.w("## VALR Report URL is $reportUrl") + // we need to send the toDevice message to the sender + + coroutineScope.launch { + try { + activeSessionHolder.getSafeActiveSession()?.sendToDevice( + eventType = AUTO_RS_REQUEST, + userId = target.senderUserId, + deviceId = target.senderDeviceId, + content = mapOf( + "event_id" to target.eventId, + "room_id" to target.roomId, + "session_id" to target.sessionId, + "device_id" to target.senderDeviceId, + "user_id" to target.senderUserId, + "sender_key" to target.senderKey, + "matching_issue" to reportUrl + ).toContent() + ) + } catch (failure: Throwable) { + Timber.w("## VALR : failed to send auto-uisi to device") + } + } + } + }) + } + } + + fun remoteAutoUISIRequest(event: Event) { + if (event.type != AUTO_RS_REQUEST) return + if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return + + coroutineScope.launch { + val eventId = event.content?.get("event_id") + val roomId = event.content?.get("room_id") + val sessionId = event.content?.get("session_id") + val deviceId = event.content?.get("device_id") + val userId = event.content?.get("user_id") + val senderKey = event.content?.get("sender_key") + val matchingIssue = event.content?.get("matching_issue")?.toString() ?: "" + + bugReporter.sendBugReport( + context = context, + reportType = ReportType.AUTO_UISI_SENDER, + withDevicesLogs = true, + withCrashLogs = true, + withKeyRequestHistory = true, + withScreenshot = false, + theBugDescription = "UISI detected $matchingIssue", + serverVersion = "", + canContact = false, + customFields = mapOf( + "auto-uisi" to buildString { + append("\neventId: $eventId") + append("\nroomId: $roomId") + append("\nsenderKey: $senderKey") + append("\ndeviceId: $deviceId") + append("\nuserId: $userId") + append("\nsessionId: $sessionId") + }, + "matching_issue" to matchingIssue + ), + listener = null + ) + } + } + + private val detector = UISIDetector().apply { + callback = object : UISIDetector.UISIDetectorCallback { + override val reciprocateToDeviceEventType: String + get() = AUTO_RS_REQUEST + + override fun uisiDetected(source: E2EMessageDetected) { + decryptionErrorDetected(source) + } + + override fun uisiReciprocateRequest(source: Event) { + remoteAutoUISIRequest(source) + } + } + } + + fun onSessionActive(session: Session) { + val sessionId = session.sessionId + if (sessionId == currentActiveSessionId) { + return + } + this.currentActiveSessionId = sessionId + this.detector.enabled = _enabled + activeSessionIds.add(sessionId) + session.addListener(this) + session.addEventStreamListener(detector) + } + + override fun onSessionStopped(session: Session) { + uisiDetectors.get(session.sessionId)?.let { + session.removeEventStreamListener(it) + } + activeSessionIds.remove(session.sessionId) + } +} diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt new file mode 100644 index 0000000000..e12bd7adeb --- /dev/null +++ b/vector/src/main/java/im/vector/app/UISIDetector.kt @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app + +import org.matrix.android.sdk.api.session.LiveEventListener +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent +import timber.log.Timber +import java.util.Timer +import java.util.TimerTask +import java.util.concurrent.Executors + +enum class UISIEventSource { + INITIAL_SYNC, + INCREMENTAL_SYNC, + PAGINATION +} + +data class E2EMessageDetected( + val eventId: String, + val roomId: String, + val senderUserId: String, + val senderDeviceId: String, + val senderKey: String, + val sessionId: String, + val source: UISIEventSource) { + + companion object { + fun fromEvent(event: Event, roomId: String, source: UISIEventSource): E2EMessageDetected { + val encryptedContent = event.content.toModel() + + return E2EMessageDetected( + eventId = event.eventId ?: "", + roomId = roomId, + senderUserId = event.senderId ?: "", + senderDeviceId = encryptedContent?.deviceId ?: "", + senderKey = encryptedContent?.senderKey ?: "", + sessionId = encryptedContent?.sessionId ?: "", + source = source + ) + } + } +} + +class UISIDetector : LiveEventListener { + + interface UISIDetectorCallback { + val reciprocateToDeviceEventType: String + fun uisiDetected(source: E2EMessageDetected) + fun uisiReciprocateRequest(source: Event) + } + + var callback: UISIDetectorCallback? = null + + private val trackedEvents = mutableListOf>() + private val executor = Executors.newSingleThreadExecutor() + private val timer = Timer() + private val timeoutMillis = 30_000L + var enabled = false + + override fun onLiveEvent(roomId: String, event: Event) { + if (!event.isEncrypted()) return + executor.execute { + handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.INCREMENTAL_SYNC)) + } + } + + override fun onPaginatedEvent(roomId: String, event: Event) { + if (!event.isEncrypted()) return + executor.execute { + handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.PAGINATION)) + } + } + + override fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) { + executor.execute { + unTrack(eventId, roomId) + } + } + + override fun onLiveToDeviceEvent(event: Event) { + if (event.type == callback?.reciprocateToDeviceEventType) { + callback?.uisiReciprocateRequest(event) + } + } + + override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) { + executor.execute { + unTrack(eventId, roomId)?.let { + triggerUISI(it) + } +// if (throwable is MXCryptoError.OlmError) { +// if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") { +// unTrack(eventId, roomId)?.let { +// triggerUISI(it) +// } +// } +// } + } + } + + private fun handleEventReceived(detectorEvent: E2EMessageDetected) { + if (trackedEvents.any { it.first == detectorEvent }) { + Timber.w("## UISIDetector: Event ${detectorEvent.eventId} is already tracked") + } else { + // track it and start timer + val timeoutTask = object : TimerTask() { + override fun run() { + executor.execute { + unTrack(detectorEvent.eventId, detectorEvent.roomId) + Timber.v("## UISIDetector: Timeout on ${detectorEvent.eventId} ") + triggerUISI(detectorEvent) + } + } + } + trackedEvents.add(detectorEvent to timeoutTask) + timer.schedule(timeoutTask, timeoutMillis) + } + } + + private fun triggerUISI(source: E2EMessageDetected) { + Timber.i("## UISIDetector: Unable To Decrypt $source") + callback?.uisiDetected(source) + } + + private fun unTrack(eventId: String, roomId: String): E2EMessageDetected? { + val index = trackedEvents.indexOfFirst { it.first.eventId == eventId && it.first.roomId == roomId } + return if (index != -1) { + trackedEvents.removeAt(index).let { + it.second.cancel() + it.first + } + } else { + null + } + } +} diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 400fb7eb89..05d20662c7 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -96,6 +96,7 @@ class VectorApplication : @Inject lateinit var pinLocker: PinLocker @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var invitesAcceptor: InvitesAcceptor + @Inject lateinit var autoRageShaker: AutoRageShaker @Inject lateinit var vectorFileLogger: VectorFileLogger @Inject lateinit var vectorAnalytics: VectorAnalytics @@ -117,6 +118,7 @@ class VectorApplication : appContext = this vectorAnalytics.init() invitesAcceptor.initialize() + autoRageShaker.initialize() vectorUncaughtExceptionHandler.activate(this) // Remove Log handler statically added by Jitsi diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt index b27c2e9818..7455a0e5bd 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt @@ -156,6 +156,7 @@ class BugReportActivity : VectorBaseActivity() { views.bugReportEditText.text.toString(), state.serverVersion, views.bugReportButtonContactMe.isChecked, + null, object : BugReporter.IMXBugReportListener { override fun onUploadFailed(reason: String?) { try { @@ -198,7 +199,7 @@ class BugReportActivity : VectorBaseActivity() { views.bugReportProgressTextView.text = getString(R.string.send_bug_report_progress, myProgress.toString()) } - override fun onUploadSucceed() { + override fun onUploadSucceed(reportUrl: String?) { try { when (reportType) { ReportType.BUG_REPORT -> { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 5b3d194d33..0ec36e43cc 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -24,6 +24,7 @@ import android.os.Build import android.view.View import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentActivity +import com.squareup.moshi.Types import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder @@ -49,7 +50,9 @@ import okhttp3.Response import org.json.JSONException import org.json.JSONObject import org.matrix.android.sdk.api.Matrix +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes +import org.matrix.android.sdk.internal.di.MoshiProvider import timber.log.Timber import java.io.File import java.io.IOException @@ -93,6 +96,9 @@ class BugReporter @Inject constructor( // boolean to cancel the bug report private val mIsCancelled = false + val adapter = MoshiProvider.providesMoshi() + .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)) + /** * Get current Screenshot * @@ -141,7 +147,7 @@ class BugReporter @Inject constructor( /** * The bug report upload succeeded. */ - fun onUploadSucceed() + fun onUploadSucceed(reportUrl: String?) } /** @@ -166,12 +172,14 @@ class BugReporter @Inject constructor( theBugDescription: String, serverVersion: String, canContact: Boolean = false, + customFields: Map? = null, listener: IMXBugReportListener?) { // enumerate files to delete val mBugReportFiles: MutableList = ArrayList() coroutineScope.launch { var serverError: String? = null + var reportURL: String? = null withContext(Dispatchers.IO) { var bugDescription = theBugDescription val crashCallStack = getCrashDescription(context) @@ -247,9 +255,11 @@ class BugReporter @Inject constructor( if (!mIsCancelled) { val text = when (reportType) { - ReportType.BUG_REPORT -> "[Element] $bugDescription" - ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" + ReportType.BUG_REPORT -> "[Element] $bugDescription" + ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription" + ReportType.AUTO_UISI_SENDER, + ReportType.AUTO_UISI -> "[AutoUISI] $bugDescription" } // build the multi part request @@ -273,7 +283,11 @@ class BugReporter @Inject constructor( .addFormDataPart("app_language", VectorLocale.applicationLocale.toString()) .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString()) .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context)) - .addFormDataPart("server_version", serverVersion) + .addFormDataPart("server_version", serverVersion).apply { + customFields?.forEach { (name, value) -> + addFormDataPart(name, value) + } + } val buildNumber = context.getString(R.string.build_number) if (buildNumber.isNotEmpty() && buildNumber != "0") { @@ -321,11 +335,19 @@ class BugReporter @Inject constructor( builder.addFormDataPart("label", "[Element]") when (reportType) { - ReportType.BUG_REPORT -> { + ReportType.BUG_REPORT -> { /* nop */ } - ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]") + ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]") ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback") + ReportType.AUTO_UISI -> { + builder.addFormDataPart("label", "auto-uisis-receiver") + builder.addFormDataPart("label", "auto-uisis") + } + ReportType.AUTO_UISI_SENDER -> { + builder.addFormDataPart("label", "auto-uisis-sender") + builder.addFormDataPart("label", "auto-uisis") + } } if (getCrashFile(context).exists()) { @@ -417,6 +439,10 @@ class BugReporter @Inject constructor( Timber.e(e, "## sendBugReport() : failed to parse error") } } + } else { + reportURL = response?.body?.string()?.let { stringBody -> + adapter.fromJson(stringBody)?.get("report_url")?.toString() + } } } } @@ -434,7 +460,7 @@ class BugReporter @Inject constructor( if (mIsCancelled) { listener.onUploadCancelled() } else if (null == serverError) { - listener.onUploadSucceed() + listener.onUploadSucceed(reportURL) } else { listener.onUploadFailed(serverError) } diff --git a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt index 44682efb40..f9dc628914 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt @@ -19,5 +19,7 @@ package im.vector.app.features.rageshake enum class ReportType { BUG_REPORT, SUGGESTION, - SPACE_BETA_FEEDBACK + SPACE_BETA_FEEDBACK, + AUTO_UISI, + AUTO_UISI_SENDER, } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 3f423696ae..7c2b983859 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -152,6 +152,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { const val SETTINGS_LABS_ALLOW_EXTENDED_LOGS = "SETTINGS_LABS_ALLOW_EXTENDED_LOGS" const val SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE = "SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE" const val SETTINGS_LABS_SPACES_HOME_AS_ORPHAN = "SETTINGS_LABS_SPACES_HOME_AS_ORPHAN" + const val SETTINGS_LABS_AUTO_REPORT_UISI = "SETTINGS_LABS_AUTO_REPORT_UISI" const val SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME = "SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME" private const val SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY" @@ -245,7 +246,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY, SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, SETTINGS_LABS_ALLOW_EXTENDED_LOGS, - SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE, +// SETTINGS_LABS_USE_RESTRICTED_JOIN_RULE, SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, SETTINGS_USE_RAGE_SHAKE_KEY, @@ -974,6 +975,10 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_SPACES_HOME_AS_ORPHAN, false) } + fun labsAutoReportUISI(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_AUTO_REPORT_UISI, false) + } + fun prefSpacesShowAllRoomInHome(): Boolean { return defaultPrefs.getBoolean(SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME, // migration of old property diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index f457980403..76f457b5ad 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -16,13 +16,26 @@ package im.vector.app.features.settings +// import im.vector.app.AutoRageShaker import im.vector.app.R +import im.vector.app.core.preference.VectorSwitchPreference import javax.inject.Inject -class VectorSettingsLabsFragment @Inject constructor() : VectorSettingsBaseFragment() { +class VectorSettingsLabsFragment @Inject constructor( + private val vectorPreferences: VectorPreferences, +// private val autoRageShaker: AutoRageShaker +) : VectorSettingsBaseFragment() { override var titleRes = R.string.room_settings_labs_pref_title override val preferenceXmlRes = R.xml.vector_settings_labs - override fun bindPref() {} + override fun bindPref() { + findPreference(VectorPreferences.SETTINGS_LABS_AUTO_REPORT_UISI)?.let { pref -> + pref.isChecked = vectorPreferences.labsAutoReportUISI() + pref.setOnPreferenceChangeListener { _, isChecked -> +// autoRageShaker.enable(isChecked as Boolean) + true + } + } + } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 1816e0d1fa..d77e04a6f1 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3565,6 +3565,11 @@ Experimental Space - Restricted Room. Warning requires server support and experimental room version + + + Auto Report Decryption Errors. + Your system will automatically send logs when an unable to decrypt error occurs + %s invites you Looking for someone not in %s? diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index f394d1923e..0d240fd709 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -44,11 +44,6 @@ android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" android:title="@string/labs_show_unread_notifications_as_tab" /> - + + \ No newline at end of file From cf026b22b81190522a62272da082943e24545f56 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 25 Oct 2021 12:27:41 +0200 Subject: [PATCH 03/10] Fix UISIS preference listener --- .../src/main/java/im/vector/app/AutoRageShaker.kt | 15 +++++++++++++-- .../src/main/java/im/vector/app/UISIDetector.kt | 7 +++++++ .../app/features/rageshake/BugReportActivity.kt | 13 +++++++++++-- .../settings/VectorSettingsLabsFragment.kt | 9 ++------- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index d11a24e38c..7c802e93fb 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -17,9 +17,11 @@ package im.vector.app import android.content.Context +import android.content.SharedPreferences import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.ReportType +import im.vector.app.features.settings.VectorPreferences import io.reactivex.disposables.Disposable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -39,8 +41,9 @@ class AutoRageShaker @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, private val activeSessionHolder: ActiveSessionHolder, private val bugReporter: BugReporter, - private val context: Context -) : Session.Listener { + private val context: Context, + private val vectorPreferences: VectorPreferences +) : Session.Listener, SharedPreferences.OnSharedPreferenceChangeListener { private lateinit var activeSessionDisposable: Disposable private val activeSessionIds = mutableSetOf() @@ -50,10 +53,18 @@ class AutoRageShaker @Inject constructor( fun initialize() { observeActiveSession() + // It's a singleton... + vectorPreferences.subscribeToChanges(this) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + enable(vectorPreferences.labsAutoReportUISI()) } var _enabled = false fun enable(enabled: Boolean) { + if (enabled == _enabled) return + _enabled = enabled uisiDetectors.forEach { it.value.enabled = enabled } } diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt index e12bd7adeb..d6a4805e78 100644 --- a/vector/src/main/java/im/vector/app/UISIDetector.kt +++ b/vector/src/main/java/im/vector/app/UISIDetector.kt @@ -75,6 +75,7 @@ class UISIDetector : LiveEventListener { var enabled = false override fun onLiveEvent(roomId: String, event: Event) { + if (!enabled) return if (!event.isEncrypted()) return executor.execute { handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.INCREMENTAL_SYNC)) @@ -82,6 +83,7 @@ class UISIDetector : LiveEventListener { } override fun onPaginatedEvent(roomId: String, event: Event) { + if (!enabled) return if (!event.isEncrypted()) return executor.execute { handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.PAGINATION)) @@ -89,18 +91,21 @@ class UISIDetector : LiveEventListener { } override fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) { + if (!enabled) return executor.execute { unTrack(eventId, roomId) } } override fun onLiveToDeviceEvent(event: Event) { + if (!enabled) return if (event.type == callback?.reciprocateToDeviceEventType) { callback?.uisiReciprocateRequest(event) } } override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) { + if (!enabled) return executor.execute { unTrack(eventId, roomId)?.let { triggerUISI(it) @@ -116,6 +121,7 @@ class UISIDetector : LiveEventListener { } private fun handleEventReceived(detectorEvent: E2EMessageDetected) { + if (!enabled) return if (trackedEvents.any { it.first == detectorEvent }) { Timber.w("## UISIDetector: Event ${detectorEvent.eventId} is already tracked") } else { @@ -135,6 +141,7 @@ class UISIDetector : LiveEventListener { } private fun triggerUISI(source: E2EMessageDetected) { + if (!enabled) return Timber.i("## UISIDetector: Unable To Decrypt $source") callback?.uisiDetected(source) } diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt index 7455a0e5bd..02df86e14b 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt @@ -62,11 +62,11 @@ class BugReportActivity : VectorBaseActivity() { // Default screen is for bug report, so modify it for suggestion when (reportType) { - ReportType.BUG_REPORT -> { + ReportType.BUG_REPORT -> { supportActionBar?.setTitle(R.string.title_activity_bug_report) views.bugReportButtonContactMe.isVisible = true } - ReportType.SUGGESTION -> { + ReportType.SUGGESTION -> { supportActionBar?.setTitle(R.string.send_suggestion) views.bugReportFirstText.setText(R.string.send_suggestion_content) @@ -84,6 +84,9 @@ class BugReportActivity : VectorBaseActivity() { hideBugReportOptions() } + else -> { + // other types not supported here + } } } @@ -174,6 +177,9 @@ class BugReportActivity : VectorBaseActivity() { Toast.makeText(this@BugReportActivity, getString(R.string.feedback_failed, reason), Toast.LENGTH_LONG).show() } + else -> { + // nop + } } } } catch (e: Exception) { @@ -211,6 +217,9 @@ class BugReportActivity : VectorBaseActivity() { ReportType.SPACE_BETA_FEEDBACK -> { Toast.makeText(this@BugReportActivity, R.string.feedback_sent, Toast.LENGTH_LONG).show() } + else -> { + // nop + } } } catch (e: Exception) { Timber.e(e, "## onUploadSucceed() : failed to dismiss the toast") diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index 76f457b5ad..a83b4c33f4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -16,14 +16,12 @@ package im.vector.app.features.settings -// import im.vector.app.AutoRageShaker import im.vector.app.R import im.vector.app.core.preference.VectorSwitchPreference import javax.inject.Inject class VectorSettingsLabsFragment @Inject constructor( - private val vectorPreferences: VectorPreferences, -// private val autoRageShaker: AutoRageShaker + private val vectorPreferences: VectorPreferences ) : VectorSettingsBaseFragment() { override var titleRes = R.string.room_settings_labs_pref_title @@ -31,11 +29,8 @@ class VectorSettingsLabsFragment @Inject constructor( override fun bindPref() { findPreference(VectorPreferences.SETTINGS_LABS_AUTO_REPORT_UISI)?.let { pref -> + // ensure correct default pref.isChecked = vectorPreferences.labsAutoReportUISI() - pref.setOnPreferenceChangeListener { _, isChecked -> -// autoRageShaker.enable(isChecked as Boolean) - true - } } } } From 401e8e671246a6410422faef7014fc299cdc0a66 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 6 Dec 2021 09:59:53 +0100 Subject: [PATCH 04/10] use flow instead of reactivex --- vector/src/main/java/im/vector/app/AutoRageShaker.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index 7c802e93fb..5837246fdc 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -22,10 +22,12 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.ReportType import im.vector.app.features.settings.VectorPreferences -import io.reactivex.disposables.Disposable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event @@ -45,7 +47,6 @@ class AutoRageShaker @Inject constructor( private val vectorPreferences: VectorPreferences ) : Session.Listener, SharedPreferences.OnSharedPreferenceChangeListener { - private lateinit var activeSessionDisposable: Disposable private val activeSessionIds = mutableSetOf() private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val uisiDetectors = mutableMapOf() @@ -69,13 +70,14 @@ class AutoRageShaker @Inject constructor( } private fun observeActiveSession() { - activeSessionDisposable = sessionDataSource.observe() + sessionDataSource.stream() .distinctUntilChanged() - .subscribe { + .onEach { it.orNull()?.let { session -> onSessionActive(session) } } + .launchIn(coroutineScope) } fun decryptionErrorDetected(target: E2EMessageDetected) { @@ -163,7 +165,7 @@ class AutoRageShaker @Inject constructor( theBugDescription = "UISI detected $matchingIssue", serverVersion = "", canContact = false, - customFields = mapOf( + customFields = mapOf( "auto-uisi" to buildString { append("\neventId: $eventId") append("\nroomId: $roomId") From 56dac76ca8b416748e1ca285035c70f3945cd718 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 6 Dec 2021 11:42:19 +0100 Subject: [PATCH 05/10] Simple rate limiting of RS sending --- .../main/java/im/vector/app/AutoRageShaker.kt | 220 +++++++++++------- 1 file changed, 140 insertions(+), 80 deletions(-) diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index 5837246fdc..1bcd27e7bb 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -25,6 +25,9 @@ import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -52,10 +55,43 @@ class AutoRageShaker @Inject constructor( private val uisiDetectors = mutableMapOf() private var currentActiveSessionId: String? = null + // Simple in memory cache of already sent report + private data class ReportInfo( + val roomId: String, + val sessionId: String + ) + + private val alreadyReportedUisi = mutableListOf() + + private val e2eDetectedFlow = MutableSharedFlow(replay = 0) + private val matchingRSRequestFlow = MutableSharedFlow(replay = 0) + fun initialize() { observeActiveSession() // It's a singleton... vectorPreferences.subscribeToChanges(this) + + // Simple rate limit, notice that order is not + // necessarily preserved + e2eDetectedFlow + .onEach { + sendRageShake(it) + delay(2_000) + } + .catch { cause -> + Timber.w(cause, "Failed to RS") + } + .launchIn(coroutineScope) + + matchingRSRequestFlow + .onEach { + sendMatchingRageShake(it) + delay(2_000) + } + .catch { cause -> + Timber.w(cause, "Failed to send matching rageshake") + } + .launchIn(coroutineScope) } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { @@ -83,63 +119,83 @@ class AutoRageShaker @Inject constructor( fun decryptionErrorDetected(target: E2EMessageDetected) { if (target.source == UISIEventSource.INITIAL_SYNC) return if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return - coroutineScope.launch { - bugReporter.sendBugReport( - context = context, - reportType = ReportType.AUTO_UISI, - withDevicesLogs = true, - withCrashLogs = true, - withKeyRequestHistory = true, - withScreenshot = false, - theBugDescription = "UISI detected", - serverVersion = "", - canContact = false, - customFields = mapOf("auto-uisi" to buildString { - append("\neventId: ${target.eventId}") - append("\nroomId: ${target.roomId}") - append("\nsenderKey: ${target.senderKey}") - append("\nsource: ${target.source}") - append("\ndeviceId: ${target.senderDeviceId}") - append("\nuserId: ${target.senderUserId}") - append("\nsessionId: ${target.sessionId}") - }), - listener = object : BugReporter.IMXBugReportListener { - override fun onUploadCancelled() { + val shouldSendRS = synchronized(alreadyReportedUisi) { + val reportInfo = ReportInfo(target.roomId, target.sessionId) + val alreadySent = alreadyReportedUisi.contains(reportInfo) + if (!alreadySent) { + alreadyReportedUisi.add(reportInfo) + } + !alreadySent + } + if (shouldSendRS) { + coroutineScope.launch { + e2eDetectedFlow.emit(target) + } + } + } + + private fun sendRageShake(target: E2EMessageDetected) { + bugReporter.sendBugReport( + context = context, + reportType = ReportType.AUTO_UISI, + withDevicesLogs = true, + withCrashLogs = true, + withKeyRequestHistory = true, + withScreenshot = false, + theBugDescription = "UISI detected", + serverVersion = "", + canContact = false, + customFields = mapOf("auto-uisi" to buildString { + append("\neventId: ${target.eventId}") + append("\nroomId: ${target.roomId}") + append("\nsenderKey: ${target.senderKey}") + append("\nsource: ${target.source}") + append("\ndeviceId: ${target.senderDeviceId}") + append("\nuserId: ${target.senderUserId}") + append("\nsessionId: ${target.sessionId}") + }), + listener = object : BugReporter.IMXBugReportListener { + override fun onUploadCancelled() { + synchronized(alreadyReportedUisi) { + alreadyReportedUisi.remove(ReportInfo(target.roomId, target.sessionId)) } + } - override fun onUploadFailed(reason: String?) { + override fun onUploadFailed(reason: String?) { + synchronized(alreadyReportedUisi) { + alreadyReportedUisi.remove(ReportInfo(target.roomId, target.sessionId)) } + } - override fun onProgress(progress: Int) { - } + override fun onProgress(progress: Int) { + } - override fun onUploadSucceed(reportUrl: String?) { - Timber.w("## VALR Report URL is $reportUrl") - // we need to send the toDevice message to the sender + override fun onUploadSucceed(reportUrl: String?) { + Timber.w("## VALR Report URL is $reportUrl") + // we need to send the toDevice message to the sender - coroutineScope.launch { - try { - activeSessionHolder.getSafeActiveSession()?.sendToDevice( - eventType = AUTO_RS_REQUEST, - userId = target.senderUserId, - deviceId = target.senderDeviceId, - content = mapOf( - "event_id" to target.eventId, - "room_id" to target.roomId, - "session_id" to target.sessionId, - "device_id" to target.senderDeviceId, - "user_id" to target.senderUserId, - "sender_key" to target.senderKey, - "matching_issue" to reportUrl - ).toContent() - ) - } catch (failure: Throwable) { - Timber.w("## VALR : failed to send auto-uisi to device") - } + coroutineScope.launch { + try { + activeSessionHolder.getSafeActiveSession()?.sendToDevice( + eventType = AUTO_RS_REQUEST, + userId = target.senderUserId, + deviceId = target.senderDeviceId, + content = mapOf( + "event_id" to target.eventId, + "room_id" to target.roomId, + "session_id" to target.sessionId, + "device_id" to target.senderDeviceId, + "user_id" to target.senderUserId, + "sender_key" to target.senderKey, + "matching_issue" to reportUrl + ).toContent() + ) + } catch (failure: Throwable) { + Timber.w("## VALR : failed to send auto-uisi to device") } } - }) - } + } + }) } fun remoteAutoUISIRequest(event: Event) { @@ -147,40 +203,44 @@ class AutoRageShaker @Inject constructor( if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return coroutineScope.launch { - val eventId = event.content?.get("event_id") - val roomId = event.content?.get("room_id") - val sessionId = event.content?.get("session_id") - val deviceId = event.content?.get("device_id") - val userId = event.content?.get("user_id") - val senderKey = event.content?.get("sender_key") - val matchingIssue = event.content?.get("matching_issue")?.toString() ?: "" - - bugReporter.sendBugReport( - context = context, - reportType = ReportType.AUTO_UISI_SENDER, - withDevicesLogs = true, - withCrashLogs = true, - withKeyRequestHistory = true, - withScreenshot = false, - theBugDescription = "UISI detected $matchingIssue", - serverVersion = "", - canContact = false, - customFields = mapOf( - "auto-uisi" to buildString { - append("\neventId: $eventId") - append("\nroomId: $roomId") - append("\nsenderKey: $senderKey") - append("\ndeviceId: $deviceId") - append("\nuserId: $userId") - append("\nsessionId: $sessionId") - }, - "matching_issue" to matchingIssue - ), - listener = null - ) + matchingRSRequestFlow.emit(event) } } + private fun sendMatchingRageShake(event: Event) { + val eventId = event.content?.get("event_id") + val roomId = event.content?.get("room_id") + val sessionId = event.content?.get("session_id") + val deviceId = event.content?.get("device_id") + val userId = event.content?.get("user_id") + val senderKey = event.content?.get("sender_key") + val matchingIssue = event.content?.get("matching_issue")?.toString() ?: "" + + bugReporter.sendBugReport( + context = context, + reportType = ReportType.AUTO_UISI_SENDER, + withDevicesLogs = true, + withCrashLogs = true, + withKeyRequestHistory = true, + withScreenshot = false, + theBugDescription = "UISI detected $matchingIssue", + serverVersion = "", + canContact = false, + customFields = mapOf( + "auto-uisi" to buildString { + append("\neventId: $eventId") + append("\nroomId: $roomId") + append("\nsenderKey: $senderKey") + append("\ndeviceId: $deviceId") + append("\nuserId: $userId") + append("\nsessionId: $sessionId") + }, + "matching_issue" to matchingIssue + ), + listener = null + ) + } + private val detector = UISIDetector().apply { callback = object : UISIDetector.UISIDetectorCallback { override val reciprocateToDeviceEventType: String From aa89e721aa5cb27b946b2dffee28be9168a8faf3 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 6 Dec 2021 12:15:17 +0100 Subject: [PATCH 06/10] Fix enabling was broken --- vector/src/main/java/im/vector/app/AutoRageShaker.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index 1bcd27e7bb..38840b8df8 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -52,7 +52,6 @@ class AutoRageShaker @Inject constructor( private val activeSessionIds = mutableSetOf() private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - private val uisiDetectors = mutableMapOf() private var currentActiveSessionId: String? = null // Simple in memory cache of already sent report @@ -102,7 +101,7 @@ class AutoRageShaker @Inject constructor( fun enable(enabled: Boolean) { if (enabled == _enabled) return _enabled = enabled - uisiDetectors.forEach { it.value.enabled = enabled } + detector.enabled = enabled } private fun observeActiveSession() { @@ -269,9 +268,7 @@ class AutoRageShaker @Inject constructor( } override fun onSessionStopped(session: Session) { - uisiDetectors.get(session.sessionId)?.let { - session.removeEventStreamListener(it) - } + session.removeEventStreamListener(detector) activeSessionIds.remove(session.sessionId) } } From de08afdfad10813fe2dbcda7dc7d83ec09aefb3f Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 17 Dec 2021 16:43:52 +0100 Subject: [PATCH 07/10] code review --- .../sdk/internal/session/DefaultToDeviceService.kt | 3 +-- .../android/sdk/internal/session/SessionModule.kt | 12 ++++++------ .../sdk/internal/session/StreamEventsManager.kt | 12 +++++------- .../room/timeline/TokenChunkEventPersistor.kt | 2 ++ vector/src/main/java/im/vector/app/AutoRageShaker.kt | 3 +-- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt index 5bdb969113..1615b8eef9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt @@ -22,12 +22,11 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import javax.inject.Inject internal class DefaultToDeviceService @Inject constructor( - private val sendToDeviceTask: DefaultSendToDeviceTask, + private val sendToDeviceTask: SendToDeviceTask, private val messageEncrypter: MessageEncrypter, private val cryptoStore: IMXCryptoStore ) : ToDeviceService { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index cf6ff4a872..531dea1d5a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -376,6 +376,12 @@ internal abstract class SessionModule { @Binds abstract fun bindOpenIdTokenService(service: DefaultOpenIdService): OpenIdService + @Binds + abstract fun bindToDeviceService(service: DefaultToDeviceService): ToDeviceService + + @Binds + abstract fun bindEventStreamService(service: DefaultEventStreamService): EventStreamService + @Binds abstract fun bindTypingUsersTracker(tracker: DefaultTypingUsersTracker): TypingUsersTracker @@ -384,10 +390,4 @@ internal abstract class SessionModule { @Binds abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor - - @Binds - abstract fun bindToDeviceService(deviceService: DefaultToDeviceService): ToDeviceService - - @Binds - abstract fun bindEventStreamService(deviceService: DefaultEventStreamService): EventStreamService } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt index c39f5fded8..bb0ca11445 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -35,17 +35,15 @@ internal class StreamEventsManager @Inject constructor() { private val listeners = mutableListOf() fun addLiveEventListener(listener: LiveEventListener) { - Timber.v("## VALR: addLiveEventListener") listeners.add(listener) } fun removeLiveEventListener(listener: LiveEventListener) { - Timber.v("## VALR: removeLiveEventListener") listeners.remove(listener) } fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { - Timber.v("## VALR: dispatchLiveEventReceived ${event.eventId}") + Timber.v("## dispatchLiveEventReceived ${event.eventId}") coroutineScope.launch { if (!initialSync) { listeners.forEach { @@ -58,7 +56,7 @@ internal class StreamEventsManager @Inject constructor() { } fun dispatchPaginatedEventReceived(event: Event, roomId: String) { - Timber.v("## VALR: dispatchPaginatedEventReceived ${event.eventId}") + Timber.v("## dispatchPaginatedEventReceived ${event.eventId}") coroutineScope.launch { listeners.forEach { tryOrNull { @@ -69,7 +67,7 @@ internal class StreamEventsManager @Inject constructor() { } fun dispatchLiveEventDecrypted(event: Event, result: MXEventDecryptionResult) { - Timber.v("## VALR: dispatchLiveEventDecrypted ${event.eventId}") + Timber.v("## dispatchLiveEventDecrypted ${event.eventId}") coroutineScope.launch { listeners.forEach { tryOrNull { @@ -80,7 +78,7 @@ internal class StreamEventsManager @Inject constructor() { } fun dispatchLiveEventDecryptionFailed(event: Event, error: Throwable) { - Timber.v("## VALR: dispatchLiveEventDecryptionFailed ${event.eventId}") + Timber.v("## dispatchLiveEventDecryptionFailed ${event.eventId}") coroutineScope.launch { listeners.forEach { tryOrNull { @@ -91,7 +89,7 @@ internal class StreamEventsManager @Inject constructor() { } fun dispatchOnLiveToDevice(event: Event) { - Timber.v("## VALR: dispatchOnLiveToDevice ${event.eventId}") + Timber.v("## dispatchOnLiveToDevice ${event.eventId}") coroutineScope.launch { listeners.forEach { tryOrNull { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 62e3896ac5..a85f0dbdc9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addStateEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.toEntity @@ -36,6 +37,7 @@ import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.StreamEventsManager +import org.matrix.android.sdk.internal.util.awaitTransaction import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index 38840b8df8..7479b0421c 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -170,7 +170,6 @@ class AutoRageShaker @Inject constructor( } override fun onUploadSucceed(reportUrl: String?) { - Timber.w("## VALR Report URL is $reportUrl") // we need to send the toDevice message to the sender coroutineScope.launch { @@ -190,7 +189,7 @@ class AutoRageShaker @Inject constructor( ).toContent() ) } catch (failure: Throwable) { - Timber.w("## VALR : failed to send auto-uisi to device") + Timber.w("failed to send auto-uisi to device") } } } From 258d7311024ddf26b043929d9ea3d79ac4631be9 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 6 Jan 2022 15:50:41 +0100 Subject: [PATCH 08/10] Change autoUisi label + rename matching_issue --- vector/src/main/java/im/vector/app/AutoRageShaker.kt | 6 +++--- .../java/im/vector/app/features/rageshake/BugReporter.kt | 8 ++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt index 7479b0421c..ca91f728cb 100644 --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt @@ -185,7 +185,7 @@ class AutoRageShaker @Inject constructor( "device_id" to target.senderDeviceId, "user_id" to target.senderUserId, "sender_key" to target.senderKey, - "matching_issue" to reportUrl + "recipient_rageshake" to reportUrl ).toContent() ) } catch (failure: Throwable) { @@ -212,7 +212,7 @@ class AutoRageShaker @Inject constructor( val deviceId = event.content?.get("device_id") val userId = event.content?.get("user_id") val senderKey = event.content?.get("sender_key") - val matchingIssue = event.content?.get("matching_issue")?.toString() ?: "" + val matchingIssue = event.content?.get("recipient_rageshake")?.toString() ?: "" bugReporter.sendBugReport( context = context, @@ -233,7 +233,7 @@ class AutoRageShaker @Inject constructor( append("\nuserId: $userId") append("\nsessionId: $sessionId") }, - "matching_issue" to matchingIssue + "recipient_rageshake" to matchingIssue ), listener = null ) diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 0ec36e43cc..26e9cabccb 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -340,13 +340,9 @@ class BugReporter @Inject constructor( } ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]") ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback") - ReportType.AUTO_UISI -> { - builder.addFormDataPart("label", "auto-uisis-receiver") - builder.addFormDataPart("label", "auto-uisis") - } + ReportType.AUTO_UISI, ReportType.AUTO_UISI_SENDER -> { - builder.addFormDataPart("label", "auto-uisis-sender") - builder.addFormDataPart("label", "auto-uisis") + builder.addFormDataPart("label", "Z-UISI") } } From 3f0122be01b9d16261ae3bbd52a435e2354a40e5 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 10 Jan 2022 09:44:11 +0100 Subject: [PATCH 09/10] Update nb of enum classes --- tools/check/forbidden_strings_in_code.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 9db73230ee..293e0b2a58 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===118 +enum class===119 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 From a047bcbabe25a1f9cc20348125511ed2760b8910 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 10 Jan 2022 09:46:11 +0100 Subject: [PATCH 10/10] restore deprecated lab preference --- vector/src/main/res/xml/vector_settings_labs.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 0d240fd709..96d5588ee6 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -44,6 +44,11 @@ android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" android:title="@string/labs_show_unread_notifications_as_tab" /> +