diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendState.kt index e79a2db3..75e3c0f6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendState.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendState.kt @@ -26,10 +26,19 @@ enum class SendState { SENDING, // the event has been sent SENT, - SYNCED; + // the event has been received from server + SYNCED, + // The event failed to be sent + UNDELIVERED, + // the event failed to be sent because some unknown devices have been found while encrypting it + FAILED_UNKNOWN_DEVICES; fun isSent(): Boolean { return this == SENT || this == SYNCED } + fun hasFailed(): Boolean { + return this == UNDELIVERED || this == FAILED_UNKNOWN_DEVICES + } + } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt index 0309af8e..2217a796 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt @@ -47,7 +47,7 @@ internal class DeviceListManager(private val cryptoStore: IMXCryptoStore, val status = deviceTrackingStatuses[userId]!! if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) { // if a download was in progress when we got shut down, it isn't any more. - deviceTrackingStatuses.put(userId, TRACKING_STATUS_PENDING_DOWNLOAD) + deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD isUpdated = true } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 33776bf6..0c09c9e2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -21,6 +21,8 @@ package im.vector.matrix.android.internal.crypto.algorithms.megolm import android.text.TextUtils import arrow.core.Try import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.DeviceListManager @@ -88,7 +90,7 @@ internal class MXMegolmEncryption( keysClaimedMap["ed25519"] = olmDevice.deviceEd25519Key!! olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!, - ArrayList(), keysClaimedMap, false) + ArrayList(), keysClaimedMap, false) keysBackup.maybeBackupKeys() @@ -103,10 +105,10 @@ internal class MXMegolmEncryption( private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap): Try { var session = outboundSession if (session == null - // Need to make a brand new session? - || session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) - // Determine if we have shared with anyone we shouldn't have - || session.sharedWithTooManyDevices(devicesInRoom)) { + // Need to make a brand new session? + || session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) + // Determine if we have shared with anyone we shouldn't have + || session.sharedWithTooManyDevices(devicesInRoom)) { session = prepareNewSessionInRoom() outboundSession = session } @@ -192,7 +194,7 @@ internal class MXMegolmEncryption( return ensureOlmSessionsForDevicesAction.handle(devicesByUser) .flatMap { Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after " - + (System.currentTimeMillis() - t0) + " ms") + + (System.currentTimeMillis() - t0) + " ms") val contentMap = MXUsersDevicesMap() var haveTargets = false val userIds = it.userIds @@ -227,7 +229,7 @@ internal class MXMegolmEncryption( sendToDeviceTask.execute(sendToDeviceParams) .map { Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after " - + (System.currentTimeMillis() - t0) + " ms") + + (System.currentTimeMillis() - t0) + " ms") // Add the devices we have shared with to session.sharedWithDevices. // we deliberately iterate over devicesByUser (ie, the devices we @@ -291,24 +293,23 @@ internal class MXMegolmEncryption( // an m.new_device. return deviceListManager .downloadKeys(userIds, false) - .map { + .flatMap { val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices() - || cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId) + || cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId) val devicesInRoom = MXUsersDevicesMap() val unknownDevices = MXUsersDevicesMap() for (userId in it.userIds) { - val deviceIds = it.getUserDeviceIds(userId) - - for (deviceId in deviceIds!!) { - val deviceInfo = it.getObject(deviceId, userId) - if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo!!.isUnknown) { + val deviceIds = it.getUserDeviceIds(userId) ?: continue + for (deviceId in deviceIds) { + val deviceInfo = it.getObject(deviceId, userId) ?: continue + if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) { // The device is not yet known by the user unknownDevices.setObject(deviceInfo, userId, deviceId) continue } - if (deviceInfo!!.isBlocked) { + if (deviceInfo.isBlocked) { // Remove any blocked devices continue } @@ -324,7 +325,13 @@ internal class MXMegolmEncryption( devicesInRoom.setObject(deviceInfo, userId, deviceId) } } - devicesInRoom + if (unknownDevices.isEmpty) { + Try.just(devicesInRoom) + } else { + val cryptoError = MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE, + MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices) + Try.Failure(Failure.CryptoError(cryptoError)) + } } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXUsersDevicesMap.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXUsersDevicesMap.java index 49d46e6c..ff125141 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXUsersDevicesMap.java +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXUsersDevicesMap.java @@ -175,6 +175,10 @@ public class MXUsersDevicesMap implements Serializable { } } + public boolean isEmpty(){ + return mMap.isEmpty(); + } + @Override public String toString() { if (null != mMap) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/repository/WarnOnUnknownDeviceRepository.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/repository/WarnOnUnknownDeviceRepository.kt index aabc2e18..ab79fe31 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/repository/WarnOnUnknownDeviceRepository.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/repository/WarnOnUnknownDeviceRepository.kt @@ -20,14 +20,14 @@ internal class WarnOnUnknownDeviceRepository { // TODO: set it back to true by default. Need UI // Warn the user if some new devices are detected while encrypting a message. - private var mWarnOnUnknownDevices = false + private var warnOnUnknownDevices = false /** * Tells if the encryption must fail if some unknown devices are detected. * * @return true to warn when some unknown devices are detected. */ - fun warnOnUnknownDevices() = mWarnOnUnknownDevices + fun warnOnUnknownDevices() = warnOnUnknownDevices /** * Update the warn status when some unknown devices are detected. @@ -35,7 +35,7 @@ internal class WarnOnUnknownDeviceRepository { * @param warn true to warn when some unknown devices are detected. */ fun setWarnOnUnknownDevices(warn: Boolean) { - mWarnOnUnknownDevices = warn + warnOnUnknownDevices = warn } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 0ae5bd0e..671a947f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.session.room.relation.DefaultUpdateQuic import im.vector.matrix.android.internal.session.room.relation.FindReactionEventForUndoTask import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory +import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater 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.timeline.* @@ -76,6 +77,10 @@ class RoomModule { LocalEchoEventFactory(get(), get()) } + scope(DefaultSession.SCOPE) { + LocalEchoUpdater(get()) + } + scope(DefaultSession.SCOPE) { RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt index 12185a07..4d0d644e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt @@ -21,8 +21,10 @@ import androidx.work.Worker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.util.WorkerParamsFactory @@ -40,16 +42,18 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) ) private val crypto by inject() + private val localEchoUpdater by inject() override fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.success() + ?: return Result.success() val localEvent = params.event if (localEvent.eventId == null) { return Result.success() } + localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING) // TODO Better async handling val latch = CountDownLatch(1) @@ -76,19 +80,21 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) latch.await() val safeResult = result - // TODO Update local echo - return if (error != null) { - Result.success() // TODO Pass error!!) - } else if (safeResult != null) { + if (safeResult != null) { val encryptedEvent = localEvent.copy( type = safeResult.mEventType, content = safeResult.mEventContent ) val nextWorkerParams = SendEventWorker.Params(params.roomId, encryptedEvent) - Result.success(WorkerParamsFactory.toData(nextWorkerParams)) - - } else { - Result.success() + return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) } + val safeError = error + val sendState = when (safeError) { + is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES + else -> SendState.UNDELIVERED + } + localEchoUpdater.updateSendState(localEvent.eventId, sendState) + //always return success, or the chain will be stuck for ever! + return Result.success() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt new file mode 100644 index 00000000..7dc69536 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt @@ -0,0 +1,38 @@ +/* + * + * * 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.send + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.util.tryTransactionAsync + +internal class LocalEchoUpdater(private val monarchy: Monarchy) { + + fun updateSendState(eventId: String, sendState: SendState) { + monarchy.tryTransactionAsync { realm -> + val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() + if (sendingEventEntity != null) { + sendingEventEntity.sendState = sendState + } + } + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt index 078dc588..32239b53 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt @@ -22,6 +22,7 @@ import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI @@ -39,6 +40,7 @@ internal class SendEventWorker(context: Context, params: WorkerParameters) ) private val roomAPI by inject() + private val localEchoUpdater by inject() override fun doWork(): Result { @@ -50,6 +52,7 @@ internal class SendEventWorker(context: Context, params: WorkerParameters) return Result.success() } + localEchoUpdater.updateSendState(event.eventId, SendState.SENDING) val result = executeRequest { apiCall = roomAPI.send( event.eventId, @@ -62,7 +65,7 @@ internal class SendEventWorker(context: Context, params: WorkerParameters) when (it) { is Failure.NetworkConnection -> Result.retry() else -> { - //TODO mark as failed to send? + localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED) //always return success, or the chain will be stuck for ever! Result.success() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineSendEventWorkCommon.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineSendEventWorkCommon.kt index 2bfafdbd..b47fad21 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineSendEventWorkCommon.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineSendEventWorkCommon.kt @@ -56,7 +56,6 @@ internal object TimelineSendEventWorkCommon { WorkManager.getInstance() .beginUniqueWork(buildWorkIdentifier(roomId), ExistingWorkPolicy.APPEND, workRequest) .enqueue() - } inline fun createWork(data: Data): OneTimeWorkRequest { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index 7749e152..8a323968 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -56,12 +56,12 @@ abstract class AbsMessageItem : BaseEventItem() { var reactionPillCallback: TimelineEventController.ReactionPillCallback? = null @EpoxyAttribute - var avatarCallback: TimelineEventController.AvatarCallback?= null + var avatarCallback: TimelineEventController.AvatarCallback? = null - private val _avatarClickListener = DebouncedClickListener(View.OnClickListener { + private val _avatarClickListener = DebouncedClickListener(View.OnClickListener { avatarCallback?.onAvatarClicked(informationData) }) - private val _memberNameClickListener = DebouncedClickListener(View.OnClickListener { + private val _memberNameClickListener = DebouncedClickListener(View.OnClickListener { avatarCallback?.onMemberNameClicked(informationData) })