Merge pull request #5639 from vector-im/feature/dla/uisi_match_web_implementation

Align Autorageshake with web implementation
This commit is contained in:
David Langley 2022-04-12 10:36:43 +01:00 committed by GitHub
commit 454a65602b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 69 additions and 95 deletions

1
changelog.d/5596.bugfix Normal file
View file

@ -0,0 +1 @@
Align auto-reporting of decryption errors implementation with web client.

1
changelog.d/5639.sdk Normal file
View file

@ -0,0 +1 @@
Include original event in live decryption listeners and update sync status naming to InitialSyncProgressing for clarity.

View file

@ -25,9 +25,9 @@ interface LiveEventListener {
fun onPaginatedEvent(roomId: String, event: Event) fun onPaginatedEvent(roomId: String, event: Event)
fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) fun onEventDecrypted(event: Event, clearEvent: JsonDict)
fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) fun onEventDecryptionError(event: Event, throwable: Throwable)
fun onLiveToDeviceEvent(event: Event) fun onLiveToDeviceEvent(event: Event)

View file

@ -28,7 +28,7 @@ interface SyncStatusService {
abstract class InitialSyncStatus : Status() abstract class InitialSyncStatus : Status()
object Idle : InitialSyncStatus() object Idle : InitialSyncStatus()
data class Progressing( data class InitialSyncProgressing(
val initSyncStep: InitSyncStep, val initSyncStep: InitSyncStep,
val percentProgress: Int = 0 val percentProgress: Int = 0
) : InitialSyncStatus() ) : InitialSyncStatus()

View file

@ -71,7 +71,7 @@ internal class StreamEventsManager @Inject constructor() {
coroutineScope.launch { coroutineScope.launch {
listeners.forEach { listeners.forEach {
tryOrNull { tryOrNull {
it.onEventDecrypted(event.eventId ?: "", event.roomId ?: "", result.clearEvent) it.onEventDecrypted(event, result.clearEvent)
} }
} }
} }
@ -82,7 +82,7 @@ internal class StreamEventsManager @Inject constructor() {
coroutineScope.launch { coroutineScope.launch {
listeners.forEach { listeners.forEach {
tryOrNull { tryOrNull {
it.onEventDecryptionError(event.eventId ?: "", event.roomId ?: "", error) it.onEventDecryptionError(event, error)
} }
} }
} }

View file

@ -72,7 +72,7 @@ internal class DefaultSyncStatusService @Inject constructor() :
// Update the progress of the leaf and all its parents // Update the progress of the leaf and all its parents
leaf.setProgress(progress) leaf.setProgress(progress)
// Then update the live data using leaf wording and root progress // Then update the live data using leaf wording and root progress
status.postValue(SyncStatusService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt())) status.postValue(SyncStatusService.Status.InitialSyncProgressing(leaf.initSyncStep, root.currentProgress.toInt()))
} }
} }
} }

View file

@ -17,9 +17,11 @@
package im.vector.app package im.vector.app
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.lifecycle.asFlow
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.BugReporter
import im.vector.app.features.rageshake.ReportType import im.vector.app.features.rageshake.ReportType
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -34,6 +36,7 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session 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.Event
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -62,10 +65,11 @@ class AutoRageShaker @Inject constructor(
private val e2eDetectedFlow = MutableSharedFlow<E2EMessageDetected>(replay = 0) private val e2eDetectedFlow = MutableSharedFlow<E2EMessageDetected>(replay = 0)
private val matchingRSRequestFlow = MutableSharedFlow<Event>(replay = 0) private val matchingRSRequestFlow = MutableSharedFlow<Event>(replay = 0)
private var hasSynced = false
private var preferenceEnabled = false
fun initialize() { fun initialize() {
observeActiveSession() observeActiveSession()
enable(vectorPreferences.labsAutoReportUISI()) preferenceEnabled = vectorPreferences.labsAutoReportUISI()
// It's a singleton... // It's a singleton...
vectorPreferences.subscribeToChanges(this) vectorPreferences.subscribeToChanges(this)
@ -74,7 +78,7 @@ class AutoRageShaker @Inject constructor(
e2eDetectedFlow e2eDetectedFlow
.onEach { .onEach {
sendRageShake(it) sendRageShake(it)
delay(2_000) delay(60_000)
} }
.catch { cause -> .catch { cause ->
Timber.w(cause, "Failed to RS") Timber.w(cause, "Failed to RS")
@ -84,7 +88,7 @@ class AutoRageShaker @Inject constructor(
matchingRSRequestFlow matchingRSRequestFlow
.onEach { .onEach {
sendMatchingRageShake(it) sendMatchingRageShake(it)
delay(2_000) delay(60_000)
} }
.catch { cause -> .catch { cause ->
Timber.w(cause, "Failed to send matching rageshake") Timber.w(cause, "Failed to send matching rageshake")
@ -93,14 +97,7 @@ class AutoRageShaker @Inject constructor(
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
enable(vectorPreferences.labsAutoReportUISI()) preferenceEnabled = vectorPreferences.labsAutoReportUISI()
}
var _enabled = false
fun enable(enabled: Boolean) {
if (enabled == _enabled) return
_enabled = enabled
detector.enabled = enabled
} }
private fun observeActiveSession() { private fun observeActiveSession() {
@ -115,7 +112,6 @@ class AutoRageShaker @Inject constructor(
} }
fun decryptionErrorDetected(target: E2EMessageDetected) { fun decryptionErrorDetected(target: E2EMessageDetected) {
if (target.source == UISIEventSource.INITIAL_SYNC) return
if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return
val shouldSendRS = synchronized(alreadyReportedUisi) { val shouldSendRS = synchronized(alreadyReportedUisi) {
val reportInfo = ReportInfo(target.roomId, target.sessionId) val reportInfo = ReportInfo(target.roomId, target.sessionId)
@ -148,7 +144,6 @@ class AutoRageShaker @Inject constructor(
append("\"room_id\": \"${target.roomId}\",") append("\"room_id\": \"${target.roomId}\",")
append("\"sender_key\": \"${target.senderKey}\",") append("\"sender_key\": \"${target.senderKey}\",")
append("\"device_id\": \"${target.senderDeviceId}\",") append("\"device_id\": \"${target.senderDeviceId}\",")
append("\"source\": \"${target.source}\",")
append("\"user_id\": \"${target.senderUserId}\",") append("\"user_id\": \"${target.senderUserId}\",")
append("\"session_id\": \"${target.sessionId}\"") append("\"session_id\": \"${target.sessionId}\"")
append("}") append("}")
@ -245,6 +240,9 @@ class AutoRageShaker @Inject constructor(
override val reciprocateToDeviceEventType: String override val reciprocateToDeviceEventType: String
get() = AUTO_RS_REQUEST get() = AUTO_RS_REQUEST
override val enabled: Boolean
get() = this@AutoRageShaker.preferenceEnabled && this@AutoRageShaker.hasSynced
override fun uisiDetected(source: E2EMessageDetected) { override fun uisiDetected(source: E2EMessageDetected) {
decryptionErrorDetected(source) decryptionErrorDetected(source)
} }
@ -261,7 +259,14 @@ class AutoRageShaker @Inject constructor(
return return
} }
this.currentActiveSessionId = sessionId this.currentActiveSessionId = sessionId
this.detector.enabled = _enabled
hasSynced = session.hasAlreadySynced()
session.getSyncStatusLive()
.asFlow()
.onEach {
hasSynced = it !is SyncStatusService.Status.InitialSyncProgressing
}
.launchIn(session.coroutineScope)
activeSessionIds.add(sessionId) activeSessionIds.add(sessionId)
session.addListener(this) session.addListener(this)
session.addEventStreamListener(detector) session.addEventStreamListener(detector)

View file

@ -16,6 +16,7 @@
package im.vector.app package im.vector.app
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.LiveEventListener 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.Event
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
@ -26,23 +27,17 @@ import java.util.Timer
import java.util.TimerTask import java.util.TimerTask
import java.util.concurrent.Executors import java.util.concurrent.Executors
enum class UISIEventSource {
INITIAL_SYNC,
INCREMENTAL_SYNC,
PAGINATION
}
data class E2EMessageDetected( data class E2EMessageDetected(
val eventId: String, val eventId: String,
val roomId: String, val roomId: String,
val senderUserId: String, val senderUserId: String,
val senderDeviceId: String, val senderDeviceId: String,
val senderKey: String, val senderKey: String,
val sessionId: String, val sessionId: String
val source: UISIEventSource) { ) {
companion object { companion object {
fun fromEvent(event: Event, roomId: String, source: UISIEventSource): E2EMessageDetected { fun fromEvent(event: Event, roomId: String): E2EMessageDetected {
val encryptedContent = event.content.toModel<EncryptedEventContent>() val encryptedContent = event.content.toModel<EncryptedEventContent>()
return E2EMessageDetected( return E2EMessageDetected(
@ -51,8 +46,7 @@ data class E2EMessageDetected(
senderUserId = event.senderId ?: "", senderUserId = event.senderId ?: "",
senderDeviceId = encryptedContent?.deviceId ?: "", senderDeviceId = encryptedContent?.deviceId ?: "",
senderKey = encryptedContent?.senderKey ?: "", senderKey = encryptedContent?.senderKey ?: "",
sessionId = encryptedContent?.sessionId ?: "", sessionId = encryptedContent?.sessionId ?: ""
source = source
) )
} }
} }
@ -61,6 +55,7 @@ data class E2EMessageDetected(
class UISIDetector : LiveEventListener { class UISIDetector : LiveEventListener {
interface UISIDetectorCallback { interface UISIDetectorCallback {
val enabled: Boolean
val reciprocateToDeviceEventType: String val reciprocateToDeviceEventType: String
fun uisiDetected(source: E2EMessageDetected) fun uisiDetected(source: E2EMessageDetected)
fun uisiReciprocateRequest(source: Event) fun uisiReciprocateRequest(source: Event)
@ -68,30 +63,16 @@ class UISIDetector : LiveEventListener {
var callback: UISIDetectorCallback? = null var callback: UISIDetectorCallback? = null
private val trackedEvents = mutableListOf<Pair<E2EMessageDetected, TimerTask>>() private val trackedEvents = mutableMapOf<String, TimerTask>()
private val executor = Executors.newSingleThreadExecutor() private val executor = Executors.newSingleThreadExecutor()
private val timer = Timer() private val timer = Timer()
private val timeoutMillis = 30_000L private val timeoutMillis = 30_000L
var enabled = false private val enabled: Boolean get() = callback?.enabled.orFalse()
override fun onLiveEvent(roomId: String, event: Event) { override fun onEventDecrypted(event: Event, clearEvent: JsonDict) {
if (!enabled) return val eventId = event.eventId
if (!event.isEncrypted()) return val roomId = event.roomId
executor.execute { if (!enabled || eventId == null || roomId == null) return
handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.INCREMENTAL_SYNC))
}
}
override fun onPaginatedEvent(roomId: String, event: Event) {
if (!enabled) return
if (!event.isEncrypted()) return
executor.execute {
handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.PAGINATION))
}
}
override fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) {
if (!enabled) return
executor.execute { executor.execute {
unTrack(eventId, roomId) unTrack(eventId, roomId)
} }
@ -104,41 +85,35 @@ class UISIDetector : LiveEventListener {
} }
} }
override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) { override fun onEventDecryptionError(event: Event, throwable: Throwable) {
if (!enabled) return val eventId = event.eventId
executor.execute { val roomId = event.roomId
unTrack(eventId, roomId)?.let { if (!enabled || eventId == null || roomId == null) return
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) { val trackerId: String = trackerId(eventId, roomId)
if (!enabled) return if (trackedEvents.containsKey(trackerId)) {
if (trackedEvents.any { it.first == detectorEvent }) { Timber.w("## UISIDetector: Event $eventId is already tracked")
Timber.w("## UISIDetector: Event ${detectorEvent.eventId} is already tracked") return
} else { }
// track it and start timer // track it and start timer
val timeoutTask = object : TimerTask() { val timeoutTask = object : TimerTask() {
override fun run() { override fun run() {
executor.execute { executor.execute {
unTrack(detectorEvent.eventId, detectorEvent.roomId) unTrack(eventId, roomId)
Timber.v("## UISIDetector: Timeout on ${detectorEvent.eventId} ") Timber.v("## UISIDetector: Timeout on $eventId")
triggerUISI(detectorEvent) triggerUISI(E2EMessageDetected.fromEvent(event, roomId))
} }
} }
} }
trackedEvents.add(detectorEvent to timeoutTask) trackedEvents[trackerId] = timeoutTask
timer.schedule(timeoutTask, timeoutMillis) timer.schedule(timeoutTask, timeoutMillis)
} }
}
override fun onLiveEvent(roomId: String, event: Event) { }
override fun onPaginatedEvent(roomId: String, event: Event) { }
private fun trackerId(eventId: String, roomId: String): String = "$roomId-$eventId"
private fun triggerUISI(source: E2EMessageDetected) { private fun triggerUISI(source: E2EMessageDetected) {
if (!enabled) return if (!enabled) return
@ -146,15 +121,7 @@ class UISIDetector : LiveEventListener {
callback?.uisiDetected(source) callback?.uisiDetected(source)
} }
private fun unTrack(eventId: String, roomId: String): E2EMessageDetected? { private fun unTrack(eventId: String, roomId: String) {
val index = trackedEvents.indexOfFirst { it.first.eventId == eventId && it.first.roomId == roomId } trackedEvents.remove(trackerId(eventId, roomId))?.cancel()
return if (index != -1) {
trackedEvents.removeAt(index).let {
it.second.cancel()
it.first
}
} else {
null
}
} }
} }

View file

@ -352,7 +352,7 @@ class HomeActivity :
private fun renderState(state: HomeActivityViewState) { private fun renderState(state: HomeActivityViewState) {
when (val status = state.syncStatusServiceStatus) { when (val status = state.syncStatusServiceStatus) {
is SyncStatusService.Status.Progressing -> { is SyncStatusService.Status.InitialSyncProgressing -> {
val initSyncStepStr = initSyncStepFormatter.format(status.initSyncStep) val initSyncStepStr = initSyncStepFormatter.format(status.initSyncStep)
Timber.v("$initSyncStepStr ${status.percentProgress}") Timber.v("$initSyncStepStr ${status.percentProgress}")
views.waitingView.root.setOnClickListener { views.waitingView.root.setOnClickListener {

View file

@ -179,7 +179,7 @@ class HomeActivityViewModel @AssistedInject constructor(
.asFlow() .asFlow()
.onEach { status -> .onEach { status ->
when (status) { when (status) {
is SyncStatusService.Status.Progressing -> { is SyncStatusService.Status.InitialSyncProgressing -> {
// Schedule a check of the bootstrap when the init sync will be finished // Schedule a check of the bootstrap when the init sync will be finished
checkBootstrap = true checkBootstrap = true
} }