Merge pull request #605 from vector-im/feature/fixing_crashes

Feature/fixing crashes
This commit is contained in:
ganfra 2019-10-07 14:51:50 +02:00 committed by GitHub
commit 0632870be1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 167 additions and 97 deletions

View file

@ -32,41 +32,38 @@ import java.util.zip.GZIPInputStream
* Get realm, invoke the action, close realm, and return the result of the action * Get realm, invoke the action, close realm, and return the result of the action
*/ */
fun <T> doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T): T { fun <T> doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T): T {
val realm = Realm.getInstance(realmConfiguration) return Realm.getInstance(realmConfiguration).use { realm ->
val result = action.invoke(realm) action.invoke(realm)
realm.close() }
return result
} }
/** /**
* Get realm, do the query, copy from realm, close realm, and return the copied result * Get realm, do the query, copy from realm, close realm, and return the copied result
*/ */
fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? { fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? {
val realm = Realm.getInstance(realmConfiguration) return Realm.getInstance(realmConfiguration).use { realm ->
val result = action.invoke(realm) val result = action.invoke(realm)
val copiedResult = result?.let { realm.copyFromRealm(result) } result?.let { realm.copyFromRealm(it) }
realm.close() }
return copiedResult
} }
/** /**
* Get realm, do the list query, copy from realm, close realm, and return the copied result * Get realm, do the list query, copy from realm, close realm, and return the copied result
*/ */
fun <T : RealmObject> doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable<T>): Iterable<T> { fun <T : RealmObject> doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable<T>): Iterable<T> {
val realm = Realm.getInstance(realmConfiguration) return Realm.getInstance(realmConfiguration).use { realm ->
val result = action.invoke(realm) val result = action.invoke(realm)
val copiedResult = realm.copyFromRealm(result) realm.copyFromRealm(result)
realm.close() }
return copiedResult
} }
/** /**
* Get realm instance, invoke the action in a transaction and close realm * Get realm instance, invoke the action in a transaction and close realm
*/ */
fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) {
val realm = Realm.getInstance(realmConfiguration) Realm.getInstance(realmConfiguration).use { realm ->
realm.executeTransaction { action.invoke(it) } realm.executeTransaction { action.invoke(realm) }
realm.close() }
} }
/** /**

View file

@ -360,7 +360,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
return return
} }
doRealmTransaction(realmConfiguration) { doRealmTransaction(realmConfiguration) { realm ->
sessions.forEach { session -> sessions.forEach { session ->
var sessionIdentifier: String? = null var sessionIdentifier: String? = null
@ -387,7 +387,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
putInboundGroupSession(session) putInboundGroupSession(session)
} }
it.insertOrUpdate(realmOlmInboundGroupSession) realm.insertOrUpdate(realmOlmInboundGroupSession)
} }
} }
} }

View file

@ -50,6 +50,8 @@ import im.vector.matrix.android.internal.session.room.relation.FindReactionEvent
import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask
import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask
import im.vector.matrix.android.internal.session.room.timeline.*
import im.vector.matrix.android.internal.session.room.timeline.ClearUnlinkedEventsTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
@ -120,6 +122,9 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
@Binds
abstract fun bindClearUnlinkedEventsTask(clearUnlinkedEventsTask: DefaultClearUnlinkedEventsTask): ClearUnlinkedEventsTask
@Binds @Binds
abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask

View file

@ -0,0 +1,50 @@
/*
* Copyright 2019 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.matrix.android.internal.session.room.timeline
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.helper.deleteOnCascade
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import javax.inject.Inject
internal interface ClearUnlinkedEventsTask : Task<ClearUnlinkedEventsTask.Params, Unit> {
data class Params(val roomId: String)
}
internal class DefaultClearUnlinkedEventsTask @Inject constructor(private val monarchy: Monarchy) : ClearUnlinkedEventsTask {
override suspend fun execute(params: ClearUnlinkedEventsTask.Params) {
monarchy.awaitTransaction { localRealm ->
val unlinkedChunks = ChunkEntity
.where(localRealm, roomId = params.roomId)
.equalTo("${ChunkEntityFields.TIMELINE_EVENTS.ROOT}.${EventEntityFields.IS_UNLINKED}", true)
.findAll()
unlinkedChunks.forEach {
it.deleteOnCascade()
}
}
}
}

View file

@ -53,6 +53,8 @@ import io.realm.RealmConfiguration
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.Sort import io.realm.Sort
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -71,6 +73,7 @@ internal class DefaultTimeline(
private val realmConfiguration: RealmConfiguration, private val realmConfiguration: RealmConfiguration,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val clearUnlinkedEventsTask: ClearUnlinkedEventsTask,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
@ -176,11 +179,10 @@ internal class DefaultTimeline(
override fun start() { override fun start() {
if (isStarted.compareAndSet(false, true)) { if (isStarted.compareAndSet(false, true)) {
Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
eventDecryptor.start()
BACKGROUND_HANDLER.post { BACKGROUND_HANDLER.post {
eventDecryptor.start()
val realm = Realm.getInstance(realmConfiguration) val realm = Realm.getInstance(realmConfiguration)
backgroundRealm.set(realm) backgroundRealm.set(realm)
clearUnlinkedEvents(realm)
roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()?.also { roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()?.also {
it.sendingTimelineEvents.addChangeListener { _ -> it.sendingTimelineEvents.addChangeListener { _ ->
@ -215,18 +217,25 @@ internal class DefaultTimeline(
BACKGROUND_HANDLER.removeCallbacksAndMessages(null) BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
BACKGROUND_HANDLER.post { BACKGROUND_HANDLER.post {
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners() roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
eventRelations.removeAllChangeListeners() if (this::eventRelations.isInitialized) {
filteredEvents.removeAllChangeListeners() eventRelations.removeAllChangeListeners()
}
if (this::filteredEvents.isInitialized) {
filteredEvents.removeAllChangeListeners()
}
hiddenReadMarker.dispose() hiddenReadMarker.dispose()
if (settings.buildReadReceipts) { if (settings.buildReadReceipts) {
hiddenReadReceipts.dispose() hiddenReadReceipts.dispose()
} }
clearAllValues() clearAllValues()
backgroundRealm.getAndSet(null).also { backgroundRealm.getAndSet(null).also {
it.close() it?.close()
} }
eventDecryptor.destroy()
} }
eventDecryptor.destroy() clearUnlinkedEventsTask
.configureWith(ClearUnlinkedEventsTask.Params(roomId))
.executeBy(taskExecutor)
} }
} }
@ -500,9 +509,9 @@ internal class DefaultTimeline(
return return
} }
val params = PaginationTask.Params(roomId = roomId, val params = PaginationTask.Params(roomId = roomId,
from = token, from = token,
direction = direction.toPaginationDirection(), direction = direction.toPaginationDirection(),
limit = limit) limit = limit)
Timber.v("Should fetch $limit items $direction") Timber.v("Should fetch $limit items $direction")
cancelableBag += paginationTask cancelableBag += paginationTask
@ -578,7 +587,7 @@ internal class DefaultTimeline(
val timelineEvent = buildTimelineEvent(eventEntity) val timelineEvent = buildTimelineEvent(eventEntity)
if (timelineEvent.isEncrypted() if (timelineEvent.isEncrypted()
&& timelineEvent.root.mxDecryptionResult == null) { && timelineEvent.root.mxDecryptionResult == null) {
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) } timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
} }
@ -640,18 +649,6 @@ internal class DefaultTimeline(
} }
} }
private fun clearUnlinkedEvents(realm: Realm) {
realm.executeTransaction { localRealm ->
val unlinkedChunks = ChunkEntity
.where(localRealm, roomId = roomId)
.equalTo("${ChunkEntityFields.TIMELINE_EVENTS.ROOT}.${EventEntityFields.IS_UNLINKED}", true)
.findAll()
unlinkedChunks.forEach {
it.deleteOnCascade()
}
}
}
private fun fetchEvent(eventId: String) { private fun fetchEvent(eventId: String) {
val params = GetContextOfEventTask.Params(roomId, eventId) val params = GetContextOfEventTask.Params(roomId, eventId)
cancelableBag += contextOfEventTask.configureWith(params).executeBy(taskExecutor) cancelableBag += contextOfEventTask.configureWith(params).executeBy(taskExecutor)

View file

@ -41,7 +41,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventMapper: TimelineEventMapper,
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
private val clearUnlinkedEventsTask: ClearUnlinkedEventsTask
) : TimelineService { ) : TimelineService {
@AssistedInject.Factory @AssistedInject.Factory
@ -55,6 +56,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
monarchy.realmConfiguration, monarchy.realmConfiguration,
taskExecutor, taskExecutor,
contextOfEventTask, contextOfEventTask,
clearUnlinkedEventsTask,
paginationTask, paginationTask,
cryptoService, cryptoService,
timelineEventMapper, timelineEventMapper,

View file

@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -53,18 +54,20 @@ internal class TimelineEventDecryptor(
} }
private val executor = Executors.newSingleThreadExecutor() private var executor: ExecutorService? = null
private val existingRequests = HashSet<String>() private val existingRequests = HashSet<String>()
private val unknownSessionsFailure = HashMap<String, MutableList<String>>() private val unknownSessionsFailure = HashMap<String, MutableList<String>>()
fun start() { fun start() {
executor = Executors.newSingleThreadExecutor()
cryptoService.addNewSessionListener(newSessionListener) cryptoService.addNewSessionListener(newSessionListener)
} }
fun destroy() { fun destroy() {
cryptoService.removeSessionListener(newSessionListener) cryptoService.removeSessionListener(newSessionListener)
executor.shutdownNow() executor?.shutdownNow()
executor = null
unknownSessionsFailure.clear() unknownSessionsFailure.clear()
existingRequests.clear() existingRequests.clear()
} }
@ -85,11 +88,9 @@ internal class TimelineEventDecryptor(
} }
} }
} }
executor.execute { executor?.execute {
Realm.getInstance(realmConfiguration).use { realm -> Realm.getInstance(realmConfiguration).use { realm ->
realm.executeTransaction { processDecryptRequest(eventId, realm)
processDecryptRequest(eventId, it)
}
} }
} }
} }
@ -104,12 +105,16 @@ internal class TimelineEventDecryptor(
try { try {
val result = cryptoService.decryptEvent(event, timelineId) val result = cryptoService.decryptEvent(event, timelineId)
Timber.v("Successfully decrypted event ${eventId}") Timber.v("Successfully decrypted event ${eventId}")
eventEntity.setDecryptionResult(result) realm.executeTransaction {
eventEntity.setDecryptionResult(result)
}
} catch (e: MXCryptoError) { } catch (e: MXCryptoError) {
Timber.v("Failed to decrypt event ${eventId} ${e}") Timber.v("Failed to decrypt event ${eventId} ${e}")
if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
//Keep track of unknown sessions to automatically try to decrypt on new session //Keep track of unknown sessions to automatically try to decrypt on new session
eventEntity.decryptionErrorCode = e.errorType.name realm.executeTransaction {
eventEntity.decryptionErrorCode = e.errorType.name
}
event.content?.toModel<EncryptedEventContent>()?.let { content -> event.content?.toModel<EncryptedEventContent>()?.let { content ->
content.sessionId?.let { sessionId -> content.sessionId?.let { sessionId ->
synchronized(unknownSessionsFailure) { synchronized(unknownSessionsFailure) {

View file

@ -132,7 +132,9 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
* Dispose the realm query subscription. Has to be called on an HandlerThread * Dispose the realm query subscription. Has to be called on an HandlerThread
*/ */
fun dispose() { fun dispose() {
this.hiddenReadReceipts.removeAllChangeListeners() if (this::hiddenReadReceipts.isInitialized) {
this.hiddenReadReceipts.removeAllChangeListeners()
}
} }
/** /**

View file

@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressServi
import im.vector.matrix.android.internal.session.mapWithProgress import im.vector.matrix.android.internal.session.mapWithProgress
import im.vector.matrix.android.internal.session.sync.model.GroupsSyncResponse import im.vector.matrix.android.internal.session.sync.model.GroupsSyncResponse
import im.vector.matrix.android.internal.session.sync.model.InvitedGroupSync import im.vector.matrix.android.internal.session.sync.model.InvitedGroupSync
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm import io.realm.Realm
import javax.inject.Inject import javax.inject.Inject
@ -36,8 +37,8 @@ internal class GroupSyncHandler @Inject constructor(private val monarchy: Monarc
data class LEFT(val data: Map<String, Any>) : HandlingStrategy() data class LEFT(val data: Map<String, Any>) : HandlingStrategy()
} }
fun handle(roomsSyncResponse: GroupsSyncResponse, reporter: DefaultInitialSyncProgressService? = null) { suspend fun handle(roomsSyncResponse: GroupsSyncResponse, reporter: DefaultInitialSyncProgressService? = null) {
monarchy.runTransactionSync { realm -> monarchy.awaitTransaction { realm ->
handleGroupSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter) handleGroupSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter)
handleGroupSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter) handleGroupSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter)
handleGroupSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter) handleGroupSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter)

View file

@ -51,6 +51,7 @@ import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse
import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
@ -73,13 +74,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy() data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
} }
fun handle(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService? = null) { suspend fun handle(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService? = null) {
monarchy.runTransactionSync { realm -> monarchy.awaitTransaction { realm ->
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter) handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter)
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter) handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter)
handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter) handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter)
} }
//handle event for bing rule checks //handle event for bing rule checks
checkPushRules(roomsSyncResponse) checkPushRules(roomsSyncResponse)
@ -126,6 +126,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomSync: RoomSync, roomSync: RoomSync,
isInitialSync: Boolean): RoomEntity { isInitialSync: Boolean): RoomEntity {
Timber.v("Handle join sync for room $roomId") Timber.v("Handle join sync for room $roomId")
if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) { if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {

View file

@ -32,7 +32,7 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
private val cryptoService: DefaultCryptoService, private val cryptoService: DefaultCryptoService,
private val initialSyncProgressService: DefaultInitialSyncProgressService) { private val initialSyncProgressService: DefaultInitialSyncProgressService) {
suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean) { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?) {
val isInitialSync = fromToken == null val isInitialSync = fromToken == null
Timber.v("Start handling sync, is InitialSync: $isInitialSync") Timber.v("Start handling sync, is InitialSync: $isInitialSync")
val reporter = initialSyncProgressService.takeIf { isInitialSync } val reporter = initialSyncProgressService.takeIf { isInitialSync }

View file

@ -77,7 +77,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
} }
throw throwable throw throwable
} }
syncResponseHandler.handleResponse(syncResponse, token, false) syncResponseHandler.handleResponse(syncResponse, token)
syncTokenStore.saveToken(syncResponse.nextBatch) syncTokenStore.saveToken(syncResponse.nextBatch)
if (isInitialSync) { if (isInitialSync) {
initialSyncProgressService.endAll() initialSyncProgressService.endAll()

View file

@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHel
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm import io.realm.Realm
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -62,8 +63,8 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
savePushRulesTask.execute(SavePushRulesTask.Params(userAccountDataPushRules.content)) savePushRulesTask.execute(SavePushRulesTask.Params(userAccountDataPushRules.content))
} }
private fun handleDirectChatRooms(directMessages: UserAccountDataDirectMessages) { private suspend fun handleDirectChatRooms(directMessages: UserAccountDataDirectMessages) {
monarchy.runTransactionSync { realm -> monarchy.awaitTransaction { realm ->
val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm) val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
oldDirectRooms.forEach { oldDirectRooms.forEach {
it.isDirect = false it.isDirect = false

View file

@ -97,8 +97,9 @@ class SASVerificationActivity : SimpleFragmentActivity() {
} }
if (isIncoming) { if (isIncoming) {
val incoming = viewModel.transaction as IncomingSasVerificationTransaction val incoming = viewModel.transaction as? IncomingSasVerificationTransaction
when (incoming.uxState) { when (incoming?.uxState) {
null,
IncomingSasVerificationTransaction.UxState.UNKNOWN, IncomingSasVerificationTransaction.UxState.UNKNOWN,
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT, IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT,
IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> { IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> {

View file

@ -23,24 +23,17 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_ import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import javax.inject.Inject import javax.inject.Inject
class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider, class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val informationDataFactory: MessageInformationDataFactory) { private val informationDataFactory: MessageInformationDataFactory) {
fun create(event: TimelineEvent, fun create(text: String,
informationData: MessageInformationData,
highlight: Boolean, highlight: Boolean,
readMarkerVisible: Boolean, callback: TimelineEventController.Callback?): DefaultItem {
callback: TimelineEventController.Callback?,
exception: Exception? = null): DefaultItem? {
val text = if (exception == null) {
"${event.root.getClearType()} events are not yet handled"
} else {
"an exception occurred when rendering the event ${event.root.eventId}"
}
val informationData = informationDataFactory.create(event, null, readMarkerVisible)
return DefaultItem_() return DefaultItem_()
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)
@ -52,4 +45,18 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava
.readReceiptsCallback(callback) .readReceiptsCallback(callback)
} }
fun create(event: TimelineEvent,
highlight: Boolean,
readMarkerVisible: Boolean,
callback: TimelineEventController.Callback?,
exception: Exception? = null): DefaultItem {
val text = if (exception == null) {
"${event.root.getClearType()} events are not yet handled"
} else {
"an exception occurred when rendering the event ${event.root.eventId}"
}
val informationData = informationDataFactory.create(event, null, readMarkerVisible)
return create(text, informationData, highlight, callback)
}
} }

View file

@ -73,6 +73,7 @@ class MessageItemFactory @Inject constructor(
private val messageInformationDataFactory: MessageInformationDataFactory, private val messageInformationDataFactory: MessageInformationDataFactory,
private val messageItemAttributesFactory: MessageItemAttributesFactory, private val messageItemAttributesFactory: MessageItemAttributesFactory,
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
private val defaultItemFactory: DefaultItemFactory,
private val noticeItemFactory: NoticeItemFactory, private val noticeItemFactory: NoticeItemFactory,
private val avatarSizeProvider: AvatarSizeProvider) { private val avatarSizeProvider: AvatarSizeProvider) {
@ -93,13 +94,13 @@ class MessageItemFactory @Inject constructor(
return buildRedactedItem(attributes, highlight) return buildRedactedItem(attributes, highlight)
} }
val messageContent: MessageContent = val messageContent = event.getLastMessageContent()
event.getLastMessageContent() if (messageContent == null) {
?: //Malformed content, we should echo something on screen val malformedText = stringProvider.getString(R.string.malformed_message)
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message)) return defaultItemFactory.create(malformedText, informationData, highlight, callback)
}
if (messageContent.relatesTo?.type == RelationType.REPLACE if (messageContent.relatesTo?.type == RelationType.REPLACE
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE || event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
) { ) {
// This is an edit event, we should it when debugging as a notice event // This is an edit event, we should it when debugging as a notice event
return noticeItemFactory.create(event, highlight, readMarkerVisible, callback) return noticeItemFactory.create(event, highlight, readMarkerVisible, callback)
@ -110,21 +111,21 @@ class MessageItemFactory @Inject constructor(
// val ev = all.toModel<Event>() // val ev = all.toModel<Event>()
return when (messageContent) { return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
informationData, informationData,
highlight, highlight,
callback, callback,
attributes) attributes)
is MessageTextContent -> buildTextMessageItem(messageContent, is MessageTextContent -> buildTextMessageItem(messageContent,
informationData, informationData,
highlight, highlight,
callback, callback,
attributes) attributes)
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes)
else -> buildNotHandledMessageItem(messageContent, highlight) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback)
} }
} }
@ -166,12 +167,12 @@ class MessageItemFactory @Inject constructor(
})) }))
} }
private fun buildNotHandledMessageItem(messageContent: MessageContent, highlight: Boolean): DefaultItem? { private fun buildNotHandledMessageItem(messageContent: MessageContent,
informationData: MessageInformationData,
highlight: Boolean,
callback: TimelineEventController.Callback?): DefaultItem? {
val text = "${messageContent.type} message events are not yet handled" val text = "${messageContent.type} message events are not yet handled"
return DefaultItem_() return defaultItemFactory.create(text, informationData, highlight, callback)
.leftGuideline(avatarSizeProvider.leftGuideline)
.text(text)
.highlighted(highlight)
} }
private fun buildImageMessageItem(messageContent: MessageImageContent, private fun buildImageMessageItem(messageContent: MessageImageContent,
@ -216,7 +217,7 @@ class MessageItemFactory @Inject constructor(
val thumbnailData = ImageContentRenderer.Data( val thumbnailData = ImageContentRenderer.Data(
filename = messageContent.body, filename = messageContent.body,
url = messageContent.videoInfo?.thumbnailFile?.url url = messageContent.videoInfo?.thumbnailFile?.url
?: messageContent.videoInfo?.thumbnailUrl, ?: messageContent.videoInfo?.thumbnailUrl,
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(), elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
height = messageContent.videoInfo?.height, height = messageContent.videoInfo?.height,
maxHeight = maxHeight, maxHeight = maxHeight,
@ -297,9 +298,9 @@ class MessageItemFactory @Inject constructor(
//nop //nop
} }
}, },
editStart, editStart,
editEnd, editEnd,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE) Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
return spannable return spannable
} }