mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge pull request #5639 from vector-im/feature/dla/uisi_match_web_implementation
Align Autorageshake with web implementation
This commit is contained in:
commit
454a65602b
1
changelog.d/5596.bugfix
Normal file
1
changelog.d/5596.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Align auto-reporting of decryption errors implementation with web client.
|
1
changelog.d/5639.sdk
Normal file
1
changelog.d/5639.sdk
Normal file
@ -0,0 +1 @@
|
||||
Include original event in live decryption listeners and update sync status naming to InitialSyncProgressing for clarity.
|
@ -25,9 +25,9 @@ interface LiveEventListener {
|
||||
|
||||
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)
|
||||
|
||||
|
@ -28,7 +28,7 @@ interface SyncStatusService {
|
||||
abstract class InitialSyncStatus : Status()
|
||||
|
||||
object Idle : InitialSyncStatus()
|
||||
data class Progressing(
|
||||
data class InitialSyncProgressing(
|
||||
val initSyncStep: InitSyncStep,
|
||||
val percentProgress: Int = 0
|
||||
) : InitialSyncStatus()
|
||||
|
@ -71,7 +71,7 @@ internal class StreamEventsManager @Inject constructor() {
|
||||
coroutineScope.launch {
|
||||
listeners.forEach {
|
||||
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 {
|
||||
listeners.forEach {
|
||||
tryOrNull {
|
||||
it.onEventDecryptionError(event.eventId ?: "", event.roomId ?: "", error)
|
||||
it.onEventDecryptionError(event, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ internal class DefaultSyncStatusService @Inject constructor() :
|
||||
// Update the progress of the leaf and all its parents
|
||||
leaf.setProgress(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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,11 @@
|
||||
package im.vector.app
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.asFlow
|
||||
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.session.coroutineScope
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
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.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -62,10 +65,11 @@ class AutoRageShaker @Inject constructor(
|
||||
|
||||
private val e2eDetectedFlow = MutableSharedFlow<E2EMessageDetected>(replay = 0)
|
||||
private val matchingRSRequestFlow = MutableSharedFlow<Event>(replay = 0)
|
||||
|
||||
private var hasSynced = false
|
||||
private var preferenceEnabled = false
|
||||
fun initialize() {
|
||||
observeActiveSession()
|
||||
enable(vectorPreferences.labsAutoReportUISI())
|
||||
preferenceEnabled = vectorPreferences.labsAutoReportUISI()
|
||||
// It's a singleton...
|
||||
vectorPreferences.subscribeToChanges(this)
|
||||
|
||||
@ -74,7 +78,7 @@ class AutoRageShaker @Inject constructor(
|
||||
e2eDetectedFlow
|
||||
.onEach {
|
||||
sendRageShake(it)
|
||||
delay(2_000)
|
||||
delay(60_000)
|
||||
}
|
||||
.catch { cause ->
|
||||
Timber.w(cause, "Failed to RS")
|
||||
@ -84,7 +88,7 @@ class AutoRageShaker @Inject constructor(
|
||||
matchingRSRequestFlow
|
||||
.onEach {
|
||||
sendMatchingRageShake(it)
|
||||
delay(2_000)
|
||||
delay(60_000)
|
||||
}
|
||||
.catch { cause ->
|
||||
Timber.w(cause, "Failed to send matching rageshake")
|
||||
@ -93,14 +97,7 @@ class AutoRageShaker @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
enable(vectorPreferences.labsAutoReportUISI())
|
||||
}
|
||||
|
||||
var _enabled = false
|
||||
fun enable(enabled: Boolean) {
|
||||
if (enabled == _enabled) return
|
||||
_enabled = enabled
|
||||
detector.enabled = enabled
|
||||
preferenceEnabled = vectorPreferences.labsAutoReportUISI()
|
||||
}
|
||||
|
||||
private fun observeActiveSession() {
|
||||
@ -115,7 +112,6 @@ class AutoRageShaker @Inject constructor(
|
||||
}
|
||||
|
||||
fun decryptionErrorDetected(target: E2EMessageDetected) {
|
||||
if (target.source == UISIEventSource.INITIAL_SYNC) return
|
||||
if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return
|
||||
val shouldSendRS = synchronized(alreadyReportedUisi) {
|
||||
val reportInfo = ReportInfo(target.roomId, target.sessionId)
|
||||
@ -148,7 +144,6 @@ class AutoRageShaker @Inject constructor(
|
||||
append("\"room_id\": \"${target.roomId}\",")
|
||||
append("\"sender_key\": \"${target.senderKey}\",")
|
||||
append("\"device_id\": \"${target.senderDeviceId}\",")
|
||||
append("\"source\": \"${target.source}\",")
|
||||
append("\"user_id\": \"${target.senderUserId}\",")
|
||||
append("\"session_id\": \"${target.sessionId}\"")
|
||||
append("}")
|
||||
@ -245,6 +240,9 @@ class AutoRageShaker @Inject constructor(
|
||||
override val reciprocateToDeviceEventType: String
|
||||
get() = AUTO_RS_REQUEST
|
||||
|
||||
override val enabled: Boolean
|
||||
get() = this@AutoRageShaker.preferenceEnabled && this@AutoRageShaker.hasSynced
|
||||
|
||||
override fun uisiDetected(source: E2EMessageDetected) {
|
||||
decryptionErrorDetected(source)
|
||||
}
|
||||
@ -261,7 +259,14 @@ class AutoRageShaker @Inject constructor(
|
||||
return
|
||||
}
|
||||
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)
|
||||
session.addListener(this)
|
||||
session.addEventStreamListener(detector)
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
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.events.model.Event
|
||||
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.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) {
|
||||
val sessionId: String
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun fromEvent(event: Event, roomId: String, source: UISIEventSource): E2EMessageDetected {
|
||||
fun fromEvent(event: Event, roomId: String): E2EMessageDetected {
|
||||
val encryptedContent = event.content.toModel<EncryptedEventContent>()
|
||||
|
||||
return E2EMessageDetected(
|
||||
@ -51,8 +46,7 @@ data class E2EMessageDetected(
|
||||
senderUserId = event.senderId ?: "",
|
||||
senderDeviceId = encryptedContent?.deviceId ?: "",
|
||||
senderKey = encryptedContent?.senderKey ?: "",
|
||||
sessionId = encryptedContent?.sessionId ?: "",
|
||||
source = source
|
||||
sessionId = encryptedContent?.sessionId ?: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -61,6 +55,7 @@ data class E2EMessageDetected(
|
||||
class UISIDetector : LiveEventListener {
|
||||
|
||||
interface UISIDetectorCallback {
|
||||
val enabled: Boolean
|
||||
val reciprocateToDeviceEventType: String
|
||||
fun uisiDetected(source: E2EMessageDetected)
|
||||
fun uisiReciprocateRequest(source: Event)
|
||||
@ -68,30 +63,16 @@ class UISIDetector : LiveEventListener {
|
||||
|
||||
var callback: UISIDetectorCallback? = null
|
||||
|
||||
private val trackedEvents = mutableListOf<Pair<E2EMessageDetected, TimerTask>>()
|
||||
private val trackedEvents = mutableMapOf<String, TimerTask>()
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
private val timer = Timer()
|
||||
private val timeoutMillis = 30_000L
|
||||
var enabled = false
|
||||
private val enabled: Boolean get() = callback?.enabled.orFalse()
|
||||
|
||||
override fun onLiveEvent(roomId: String, event: Event) {
|
||||
if (!enabled) return
|
||||
if (!event.isEncrypted()) return
|
||||
executor.execute {
|
||||
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
|
||||
override fun onEventDecrypted(event: Event, clearEvent: JsonDict) {
|
||||
val eventId = event.eventId
|
||||
val roomId = event.roomId
|
||||
if (!enabled || eventId == null || roomId == null) return
|
||||
executor.execute {
|
||||
unTrack(eventId, roomId)
|
||||
}
|
||||
@ -104,57 +85,43 @@ class UISIDetector : LiveEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) {
|
||||
if (!enabled) return
|
||||
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)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
override fun onEventDecryptionError(event: Event, throwable: Throwable) {
|
||||
val eventId = event.eventId
|
||||
val roomId = event.roomId
|
||||
if (!enabled || eventId == null || roomId == null) return
|
||||
|
||||
private fun handleEventReceived(detectorEvent: E2EMessageDetected) {
|
||||
if (!enabled) return
|
||||
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)
|
||||
}
|
||||
val trackerId: String = trackerId(eventId, roomId)
|
||||
if (trackedEvents.containsKey(trackerId)) {
|
||||
Timber.w("## UISIDetector: Event $eventId is already tracked")
|
||||
return
|
||||
}
|
||||
// track it and start timer
|
||||
val timeoutTask = object : TimerTask() {
|
||||
override fun run() {
|
||||
executor.execute {
|
||||
unTrack(eventId, roomId)
|
||||
Timber.v("## UISIDetector: Timeout on $eventId")
|
||||
triggerUISI(E2EMessageDetected.fromEvent(event, roomId))
|
||||
}
|
||||
}
|
||||
trackedEvents.add(detectorEvent to timeoutTask)
|
||||
timer.schedule(timeoutTask, timeoutMillis)
|
||||
}
|
||||
trackedEvents[trackerId] = timeoutTask
|
||||
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) {
|
||||
if (!enabled) return
|
||||
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
|
||||
}
|
||||
private fun unTrack(eventId: String, roomId: String) {
|
||||
trackedEvents.remove(trackerId(eventId, roomId))?.cancel()
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +352,7 @@ class HomeActivity :
|
||||
|
||||
private fun renderState(state: HomeActivityViewState) {
|
||||
when (val status = state.syncStatusServiceStatus) {
|
||||
is SyncStatusService.Status.Progressing -> {
|
||||
is SyncStatusService.Status.InitialSyncProgressing -> {
|
||||
val initSyncStepStr = initSyncStepFormatter.format(status.initSyncStep)
|
||||
Timber.v("$initSyncStepStr ${status.percentProgress}")
|
||||
views.waitingView.root.setOnClickListener {
|
||||
|
@ -179,11 +179,11 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||
.asFlow()
|
||||
.onEach { status ->
|
||||
when (status) {
|
||||
is SyncStatusService.Status.Progressing -> {
|
||||
is SyncStatusService.Status.InitialSyncProgressing -> {
|
||||
// Schedule a check of the bootstrap when the init sync will be finished
|
||||
checkBootstrap = true
|
||||
}
|
||||
is SyncStatusService.Status.Idle -> {
|
||||
is SyncStatusService.Status.Idle -> {
|
||||
if (checkBootstrap) {
|
||||
checkBootstrap = false
|
||||
maybeBootstrapCrossSigningAfterInitialSync()
|
||||
|
Loading…
Reference in New Issue
Block a user