diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index 0f978413..90f52474 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult @@ -107,4 +108,8 @@ interface CryptoService { fun clearCryptoCache(callback: MatrixCallback) + fun addNewSessionListener(newSessionListener: NewSessionListener) + + fun removeSessionListener(listener: NewSessionListener) + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 3341d87e..14b9e334 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -34,6 +34,7 @@ data class TimelineEvent( val isUniqueDisplayName: Boolean, val senderAvatar: String?, val sendState: SendState, + val hasClearEventFlag: Boolean = false, val annotations: EventAnnotationsSummary? = null ) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index 01506b71..abab01d0 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -1063,6 +1063,13 @@ internal class CryptoManager @Inject constructor( .executeBy(taskExecutor) } + override fun addNewSessionListener(newSessionListener: NewSessionListener) { + roomDecryptorProvider.addNewSessionListener(newSessionListener) + } + + override fun removeSessionListener(listener: NewSessionListener) { + roomDecryptorProvider.removeSessionListener(listener) + } /* ========================================================================================== * DEBUG INFO * ========================================================================================== */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/NewSessionListener.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/NewSessionListener.kt new file mode 100644 index 00000000..7881b1ea --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/NewSessionListener.kt @@ -0,0 +1,21 @@ +/* + * 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.crypto + + +interface NewSessionListener { + fun onNewSession(roomId: String?, senderKey: String, sessionId: String) +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/RoomDecryptorProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/RoomDecryptorProvider.kt index 7d424f56..3dd7b932 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/RoomDecryptorProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/RoomDecryptorProvider.kt @@ -34,6 +34,16 @@ internal class RoomDecryptorProvider @Inject constructor( // A map from algorithm to MXDecrypting instance, for each room private val roomDecryptors: MutableMap> = HashMap() + private val newSessionListeners = ArrayList() + + fun addNewSessionListener(listener: NewSessionListener) { + if (!newSessionListeners.contains(listener)) newSessionListeners.add(listener) + } + + fun removeSessionListener(listener: NewSessionListener) { + newSessionListeners.remove(listener) + } + /** * Get a decryptor for a given room and algorithm. * If we already have a decryptor for the given room and algorithm, return @@ -64,7 +74,19 @@ internal class RoomDecryptorProvider @Inject constructor( val decryptingClass = MXCryptoAlgorithms.hasDecryptorClassForAlgorithm(algorithm) if (decryptingClass) { val alg = when (algorithm) { - MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create() + MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply { + this.newSessionListener = object : NewSessionListener { + override fun onNewSession(rid: String?, senderKey: String, sessionId: String) { + newSessionListeners.forEach { + try { + it.onNewSession(roomId, senderKey, sessionId) + } catch (e: Throwable) { + + } + } + } + } + } else -> olmDecryptionFactory.create() } if (roomId != null && !TextUtils.isEmpty(roomId)) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 086d4af5..69992df6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -55,6 +55,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials, private val coroutineDispatchers: MatrixCoroutineDispatchers) : IMXDecrypting { + var newSessionListener: NewSessionListener? = null + /** * Events which we couldn't decrypt due to unknown sessions / indexes: map from * senderKey|sessionId to timelines to list of MatrixEvents. @@ -203,7 +205,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials, if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { Timber.v("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId + " sessionKey " + roomKeyContent.sessionKey) // from " + event); - val forwardedRoomKeyContent = event.getClearContent().toModel() ?: return + val forwardedRoomKeyContent = event.getClearContent().toModel() + ?: return forwardingCurve25519KeyChain = if (forwardedRoomKeyContent.forwardingCurve25519KeyChain == null) { ArrayList() } else { @@ -275,43 +278,8 @@ internal class MXMegolmDecryption(private val credentials: Credentials, * @param sessionId the session id */ override fun onNewSession(senderKey: String, sessionId: String) { - //TODO see how to handle this Timber.v("ON NEW SESSION $sessionId - $senderKey") - /*val k = "$senderKey|$sessionId" - - val pending = pendingEvents[k] - - if (null != pending) { - // Have another go at decrypting events sent with this session. - pendingEvents.remove(k) - - val timelineIds = pending.keys - - for (timelineId in timelineIds) { - val events = pending[timelineId] - - for (event in events!!) { - var result: MXEventDecryptionResult? = null - - try { - result = decryptEvent(event, timelineId) - } catch (e: MXDecryptionException) { - Timber.e(e, "## onNewSession() : Still can't decrypt " + event.eventId + ". Error") - event.setCryptoError(e.cryptoError) - } - - if (null != result) { - val fResut = result - CryptoAsyncHelper.getUiHandler().post { - event.setClearData(fResut) - //mSession!!.onEventDecrypted(event) - } - Timber.v("## onNewSession() : successful re-decryption of " + event.eventId) - } - } - } - } - */ + newSessionListener?.onNewSession(null, senderKey, sessionId) } override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index d03d0721..1b19db2b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.store import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper @@ -376,4 +377,8 @@ internal interface IMXCryptoStore { * @return an IncomingRoomKeyRequest if it exists, else null */ fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? + + fun addNewSessionListener(listener: NewSessionListener) + + fun removeSessionListener(listener: NewSessionListener) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 1cbd21a1..fa944465 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.store.db import android.text.TextUtils import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper @@ -57,6 +58,17 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals // Cache for InboundGroupSession, to release them properly private val inboundGroupSessionToRelease = HashMap() + + private val newSessionListeners = ArrayList() + + override fun addNewSessionListener(listener: NewSessionListener) { + if (!newSessionListeners.contains(listener)) newSessionListeners.add(listener) + } + + override fun removeSessionListener(listener: NewSessionListener) { + newSessionListeners.remove(listener) + } + /* ========================================================================================== * Other data * ========================================================================================== */ @@ -718,4 +730,5 @@ internal class RealmCryptoStore(private val enableFileEncryption: Boolean = fals } .toMutableList() } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 51e5ba0e..d49471fb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -64,7 +64,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, fun create(roomId: String): Room { val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService) - val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask) + val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, cryptoService, paginationTask) val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 42d4a22c..72c07527 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -20,11 +20,15 @@ import android.os.Handler import android.os.HandlerThread import android.os.Looper import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.api.util.addTo +import im.vector.matrix.android.internal.crypto.NewSessionListener +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.query.findIncludingEvent @@ -56,6 +60,7 @@ internal class DefaultTimeline( private val contextOfEventTask: GetContextOfEventTask, private val timelineEventFactory: CacheableTimelineEventFactory, private val paginationTask: PaginationTask, + private val cryptoService: CryptoService, private val allowedTypes: List? ) : Timeline { @@ -159,6 +164,33 @@ internal class DefaultTimeline( postSnapshot() } + private val newSessionListener = object : NewSessionListener { + override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { + if (roomId == this@DefaultTimeline.roomId) { + Timber.v("New session id detected for this room") + backgroundHandler.get()?.post { + val realm = backgroundRealm.get() + var hasChange = false + builtEvents.forEachIndexed { index, timelineEvent -> + if (timelineEvent.isEncrypted()) { + val eventContent = timelineEvent.root.content.toModel() + if (eventContent?.sessionId == sessionId + && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { + //we need to rebuild this event + EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { + builtEvents[index] = timelineEventFactory.create(it, realm) + hasChange = true + } + } + } + } + if (hasChange) postSnapshot() + } + } + } + + } + // Public methods ****************************************************************************** override fun paginate(direction: Timeline.Direction, count: Int) { @@ -184,6 +216,7 @@ internal class DefaultTimeline( val handler = Handler(handlerThread.looper) this.backgroundHandlerThread.set(handlerThread) this.backgroundHandler.set(handler) + cryptoService.addNewSessionListener(newSessionListener) handler.post { val realm = Realm.getInstance(realmConfiguration) backgroundRealm.set(realm) @@ -211,6 +244,7 @@ internal class DefaultTimeline( override fun dispose() { if (isStarted.compareAndSet(true, false)) { + cryptoService.removeSessionListener(newSessionListener) Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") backgroundHandler.get()?.post { cancelableBag.cancel() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 40aa4677..1d3462e2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.timeline import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineService @@ -35,11 +36,20 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St private val taskExecutor: TaskExecutor, private val timelineEventFactory: CacheableTimelineEventFactory, private val contextOfEventTask: GetContextOfEventTask, + private val cryptoService: CryptoService, private val paginationTask: PaginationTask ) : TimelineService { override fun createTimeline(eventId: String?, allowedTypes: List?): Timeline { - return DefaultTimeline(roomId, eventId, monarchy.realmConfiguration, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask, allowedTypes) + return DefaultTimeline(roomId, + eventId, + monarchy.realmConfiguration, + taskExecutor, + contextOfEventTask, + timelineEventFactory, + paginationTask, + cryptoService, + allowedTypes) } override fun getTimeLineEvent(eventId: String): TimelineEvent? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt index 4fb237c3..e9a10902 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventFactory.kt @@ -69,6 +69,7 @@ internal class SimpleTimelineEventFactory @Inject constructor(private val roomMe isUniqueDisplayName, senderRoomMember?.avatarUrl, eventEntity.sendState, + event.mClearEvent != null, relations ) } @@ -120,6 +121,7 @@ internal class InMemoryTimelineEventFactory @Inject constructor(private val room senderData.isUniqueDisplayName, senderData.senderAvatar, eventEntity.sendState, + event.mClearEvent != null, relations ) } @@ -138,7 +140,7 @@ internal class InMemoryTimelineEventFactory @Inject constructor(private val room } event.setClearData(result) } catch (failure: Throwable) { - Timber.e(failure, "Encrypted event: decryption failed") + Timber.e("Encrypted event: decryption failed ${failure.localizedMessage}") if (failure is MXDecryptionException) { event.setCryptoError(failure.cryptoError) } else {