Suspend api: continue moving away from callback

This commit is contained in:
ganfra 2022-04-06 19:02:45 +02:00
parent 9c6fccab1d
commit ed84e38a9b
14 changed files with 103 additions and 121 deletions

View file

@ -64,7 +64,7 @@ interface CryptoService {
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
suspend fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
fun getMyDevice(): CryptoDeviceInfo
@ -84,7 +84,7 @@ interface CryptoService {
fun setRoomBlacklistUnverifiedDevices(roomId: String)
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
suspend fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
fun reRequestRoomKeyForEvent(event: Event)
@ -92,7 +92,7 @@ interface CryptoService {
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>)
suspend fun fetchDevicesList(): List<DeviceInfo>
fun getMyDevicesInfo(): List<DeviceInfo>
@ -112,7 +112,7 @@ interface CryptoService {
fun discardOutboundSession(roomId: String)
@Throws(MXCryptoError::class)
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
@ -122,7 +122,7 @@ interface CryptoService {
suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo>
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
fun getLiveCryptoDeviceInfo(userId: String): Flow<List<CryptoDeviceInfo>>

View file

@ -66,7 +66,6 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -224,23 +223,12 @@ internal class DefaultCryptoService @Inject constructor(
return runBlocking { olmMachine.ownDevice() }
}
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
getDevicesTask
.configureWith {
// this.executionThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<DevicesListResponse> {
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
override fun onSuccess(data: DevicesListResponse) {
// Save in local DB
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
callback.onSuccess(data)
}
}
}
.executeBy(taskExecutor)
override suspend fun fetchDevicesList(): List<DeviceInfo> {
val devicesList = tryOrNull {
getDevicesTask.execute(Unit).devices
}.orEmpty()
cryptoStore.saveMyDevicesInfo(devicesList)
return devicesList
}
override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
@ -301,10 +289,9 @@ internal class DefaultCryptoService @Inject constructor(
*/
fun start() {
internalStart()
// Just update
fetchDevicesList(NoOpMatrixCallback())
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// Just update
fetchDevicesList()
cryptoStore.tidyUpDataBase()
}
}
@ -412,20 +399,13 @@ internal class DefaultCryptoService @Inject constructor(
* @param userId the user id
* @param deviceId the device id
*/
override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
runBlocking {
this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId, deviceId)
}
} else {
null
}
override suspend fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
if (userId.isEmpty() || deviceId.isNullOrEmpty()) return null
return olmMachine.getCryptoDeviceInfo(userId, deviceId)
}
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return runBlocking {
this@DefaultCryptoService.olmMachine.getCryptoDeviceInfo(userId)
}
override suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return olmMachine.getCryptoDeviceInfo(userId)
}
override fun getLiveCryptoDeviceInfo(userId: String): Flow<List<CryptoDeviceInfo>> {
@ -503,7 +483,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* @return the stored device keys for a user.
*/
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
override suspend fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
return this.getCryptoDeviceInfo(userId).toMutableList()
}
@ -577,10 +557,8 @@ internal class DefaultCryptoService @Inject constructor(
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return runBlocking {
olmMachine.decryptRoomEvent(event)
}
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return olmMachine.decryptRoomEvent(event)
}
/**

View file

@ -362,11 +362,11 @@ internal class OlmMachine(
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
withContext(Dispatchers.IO) {
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
val serializedEvent = adapter.toJson(event)
try {
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
val serializedEvent = adapter.toJson(event)
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId)
val deserializationAdapter =

View file

@ -156,7 +156,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
* Invoke the event decryption mechanism for a specific event
*/
private fun decryptIfNeeded(event: Event, roomId: String) {
private suspend fun decryptIfNeeded(event: Event, roomId: String) {
try {
// Event from sync does not have roomId, so add it to the event first
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")

View file

@ -43,6 +43,7 @@ internal class RealmSendingEventsDataSource(
private var roomEntity: RoomEntity? = null
private var sendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private var frozenSendingTimelineEvents: RealmList<TimelineEventEntity>? = null
private val builtEvents = ArrayList<TimelineEvent>()
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })

View file

@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
@ -126,7 +127,9 @@ internal class TimelineEventDecryptor @Inject constructor(
return
}
try {
val result = cryptoService.decryptEvent(request.event, timelineId)
val result = runBlocking {
cryptoService.decryptEvent(request.event, timelineId)
}
Timber.v("Successfully decrypted event ${event.eventId}")
realm.executeTransaction {
val eventId = event.eventId ?: return@executeTransaction

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
import dagger.Lazy
import io.realm.Realm
import io.realm.kotlin.createObject
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
@ -343,7 +344,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
return roomEntity
}
private suspend fun handleTimelineEvents(realm: Realm,
private fun handleTimelineEvents(realm: Realm,
roomId: String,
roomEntity: RoomEntity,
eventList: List<Event>,
@ -458,7 +459,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
private fun decryptIfNeeded(event: Event, roomId: String) {
try {
// Event from sync does not have roomId, so add it to the event first
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
val result = runBlocking {
cryptoService.decryptEvent(event.copy(roomId = roomId), "")
}
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,

View file

@ -131,28 +131,29 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
session.cryptoService().verificationService().getExistingTransaction(initialState.otherUserId, it) as? QrCodeVerificationTransaction
}
val hasAnyOtherSession = session.cryptoService()
.getCryptoDeviceInfo(session.myUserId)
.any {
it.deviceId != session.sessionParams.deviceId
}
viewModelScope.launch {
setState {
copy(
otherUserMxItem = userItem?.toMatrixItem(),
sasTransactionState = sasTx?.state,
qrTransactionState = qrTx?.state,
transactionId = pr?.transactionId ?: initialState.verificationId,
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
isMe = initialState.otherUserId == session.myUserId,
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
hasAnyOtherSession = hasAnyOtherSession
)
}
val hasAnyOtherSession = session.cryptoService()
.getCryptoDeviceInfo(session.myUserId)
.any {
it.deviceId != session.sessionParams.deviceId
}
if (autoReady) {
viewModelScope.launch {
setState {
copy(
otherUserMxItem = userItem?.toMatrixItem(),
sasTransactionState = sasTx?.state,
qrTransactionState = qrTx?.state,
transactionId = pr?.transactionId ?: initialState.verificationId,
pendingRequest = if (pr != null) Success(pr) else Uninitialized,
isMe = initialState.otherUserId == session.myUserId,
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
hasAnyOtherSession = hasAnyOtherSession
)
}
if (autoReady) {
// TODO, can I be here in DM mode? in this case should test if roomID is null?
session.cryptoService().verificationService()
.readyPendingVerification(
@ -480,7 +481,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}
}
private fun handleTransactionUpdate(state: VerificationBottomSheetViewState, tx: VerificationTransaction){
private fun handleTransactionUpdate(state: VerificationBottomSheetViewState, tx: VerificationTransaction) {
viewModelScope.launch {
if (state.selfVerificationMode && state.transactionId == null) {
// is this an incoming with that user

View file

@ -32,10 +32,11 @@ import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import org.matrix.android.sdk.api.NoOpMatrixCallback
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.MatrixItem
@ -58,7 +59,7 @@ data class DeviceDetectionInfo(
class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState,
session: Session,
private val vectorPreferences: VectorPreferences) :
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
VectorViewModel<UnknownDevicesState, UnknownDeviceDetectorSharedViewModel.Action, EmptyViewEvents>(initialState) {
sealed class Action : VectorViewModelAction {
data class IgnoreDevice(val deviceIds: List<String>) : Action()
@ -75,12 +76,6 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
init {
val currentSessionTs = session.cryptoService().getCryptoDeviceInfo(session.myUserId)
.firstOrNull { it.deviceId == session.sessionParams.deviceId }
?.firstTimeSeenLocalTs
?: System.currentTimeMillis()
Timber.v("## Detector - Current Session first time seen $currentSessionTs")
ignoredDeviceList.addAll(
vectorPreferences.getUnknownDeviceDismissedList().also {
Timber.v("## Detector - Remembered ignored list $it")
@ -90,10 +85,12 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
combine(
session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveMyDevicesInfo(),
session.flow().liveCrossSigningPrivateKeys()
) { cryptoList, infoList, pInfo ->
session.flow().liveCrossSigningPrivateKeys(),
session.firstTimeDeviceSeen(),
) { cryptoList, infoList, pInfo, firstTimeDeviceSeen ->
// Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}")
// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}")
Timber.v("## Detector - Current Session first time seen $firstTimeDeviceSeen")
infoList
.filter { info ->
// filter verified session, by checking the crypto device info
@ -106,7 +103,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0
DeviceDetectionInfo(
deviceInfo,
deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive,
deviceKnownSince > firstTimeDeviceSeen + 60_000, // short window to avoid false positive,
pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change
)
}
@ -125,12 +122,14 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
.sample(5_000)
.onEach {
// If we have a new crypto device change, we might want to trigger refresh of device info
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
session.cryptoService().fetchDevicesList()
}
.launchIn(viewModelScope)
// trigger a refresh of lastSeen / last Ip
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
viewModelScope.launch {
session.cryptoService().fetchDevicesList()
}
}
override fun handle(action: Action) {
@ -154,4 +153,13 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted
vectorPreferences.storeUnknownDeviceDismissedList(ignoredDeviceList)
super.onCleared()
}
private fun Session.firstTimeDeviceSeen() = flow {
val value = cryptoService().getCryptoDeviceInfo(myUserId)
.firstOrNull { it.deviceId == sessionParams.deviceId }
?.firstTimeSeenLocalTs
?: System.currentTimeMillis()
emit(value)
}
}

View file

@ -164,11 +164,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
onEach(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions ->
val nonNullTimelineEvent = timelineEvent() ?: return@onEach
eventIdFlow.tryEmit(nonNullTimelineEvent.eventId)
val events = actionsForEvent(nonNullTimelineEvent, permissions)
setState {
copy(
eventId = nonNullTimelineEvent.eventId,
messageBody = computeMessageBody(nonNullTimelineEvent),
actions = actionsForEvent(nonNullTimelineEvent, permissions)
actions = events
)
}
}
@ -246,7 +247,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}
}
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
private suspend fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
val messageContent = timelineEvent.getLastMessageContent()
val msgType = messageContent?.msgType
@ -317,7 +318,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
// TODO sent by me or sufficient power level
}
private fun ArrayList<EventSharedAction>.addActionsForSyncedState(timelineEvent: TimelineEvent,
private suspend fun ArrayList<EventSharedAction>.addActionsForSyncedState(timelineEvent: TimelineEvent,
actionPermissions: ActionPermissions,
messageContent: MessageContent?,
msgType: String?) {

View file

@ -139,20 +139,18 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
}
}
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration {
private fun getE2EDecoration(roomSummary: RoomSummary?, event: TimelineEvent): E2EDecoration = runBlocking{
if (event.root.sendState != SendState.SYNCED)
return E2EDecoration.NONE
return@runBlocking E2EDecoration.NONE
if (!roomSummary?.isEncrypted.orFalse())
return E2EDecoration.NONE
val isUserVerified = runBlocking {
session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted().orFalse()
}
return@runBlocking E2EDecoration.NONE
val isUserVerified = session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted().orFalse()
if (!isUserVerified) {
return E2EDecoration.NONE
return@runBlocking E2EDecoration.NONE
}
val ts = roomSummary?.encryptionEventTs ?: 0
val eventTs = event.root.originServerTs ?: 0
return if (event.isEncrypted()) {
return@runBlocking if (event.isEncrypted()) {
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
E2EDecoration.NONE

View file

@ -190,7 +190,7 @@ class NotifiableEventResolver @Inject constructor(
}
}
private fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
private suspend fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
if (root.isEncrypted() && root.mxDecryptionResult == null) {
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
// for now decrypt sync

View file

@ -70,12 +70,10 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import me.gujun.android.span.span
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
@ -524,7 +522,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
/**
* Build the cryptography preference section.
*/
private fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
private suspend fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
showDeviceListPref.isEnabled = devices.isNotEmpty()
showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size)
@ -580,28 +578,19 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// ==============================================================================================================
private fun refreshMyDevice() {
session.cryptoService().getUserDevices(session.myUserId).map {
DeviceInfo(
userId = session.myUserId,
deviceId = it.deviceId,
displayName = it.displayName()
)
}.let {
refreshCryptographyPreference(it)
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
session.cryptoService().getUserDevices(session.myUserId).map {
DeviceInfo(
userId = session.myUserId,
deviceId = it.deviceId,
displayName = it.displayName()
)
}.let {
refreshCryptographyPreference(it)
}
// TODO Move to a ViewModel...
val devicesList = session.cryptoService().fetchDevicesList()
refreshCryptographyPreference(devicesList)
}
// TODO Move to a ViewModel...
session.cryptoService().fetchDevicesList(object : MatrixCallback<DevicesListResponse> {
override fun onSuccess(data: DevicesListResponse) {
if (isAdded) {
refreshCryptographyPreference(data.devices.orEmpty())
}
}
override fun onFailure(failure: Throwable) {
if (isAdded) {
refreshCryptographyPreference(emptyList())
}
}
})
}
}

View file

@ -147,7 +147,7 @@ class DevicesViewModel @AssistedInject constructor(
.sample(5_000)
.onEach {
// If we have a new crypto device change, we might want to trigger refresh of device info
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
session.cryptoService().fetchDevicesList()
}
.launchIn(viewModelScope)
@ -160,7 +160,7 @@ class DevicesViewModel @AssistedInject constructor(
refreshSource.stream().throttleFirst(4_000)
.onEach {
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
session.cryptoService().fetchDevicesList()
session.cryptoService().downloadKeys(listOf(session.myUserId), true)
}
.launchIn(viewModelScope)