Merge branch 'release/0.18.1'

This commit is contained in:
Valere 2020-03-17 11:46:55 +01:00
commit 367f793929
15 changed files with 132 additions and 20 deletions

View File

@ -1,3 +1,16 @@
Changes in RiotX 0.18.1 (2020-03-17)
===================================================
Improvements 🙌:
- Implementation of /join command
Bugfix 🐛:
- Message transitions in encrypted rooms are jarring #518
- Images that failed to send are waiting to be sent forever #1145
- Fix / Crashed when trying to send a gif from the Gboard #1136
- Fix / Cannot click on key backup banner when new keys are available
Changes in RiotX 0.18.0 (2020-03-11) Changes in RiotX 0.18.0 (2020-03-11)
=================================================== ===================================================

View File

@ -193,6 +193,7 @@ internal class DefaultSession @Inject constructor(
stopAnyBackgroundSync() stopAnyBackgroundSync()
liveEntityObservers.forEach { it.cancelProcess() } liveEntityObservers.forEach { it.cancelProcess() }
cacheService.get().clearCache(callback) cacheService.get().clearCache(callback)
workManagerProvider.cancelAllWorks()
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)

View File

@ -77,6 +77,16 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
return Result.success(inputData) return Result.success(inputData)
} }
// Just defensive code to ensure that we never have an uncaught exception that could break the queue
return try {
internalDoWork(params)
} catch (failure: Throwable) {
Timber.e(failure)
handleFailure(params, failure)
}
}
private suspend fun internalDoWork(params: Params): Result {
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this) sessionComponent.inject(this)

View File

@ -23,7 +23,10 @@ import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.crypto.CryptoService 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.events.model.Event
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.util.awaitCallback import im.vector.matrix.android.internal.util.awaitCallback
import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.SessionWorkerParams
@ -96,6 +99,22 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
type = safeResult.eventType, type = safeResult.eventType,
content = safeResult.eventContent content = safeResult.eventContent
) )
// Better handling of local echo, to avoid decrypting transition on remote echo
// Should I only do it for text messages?
if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
val decryptionLocalEcho = MXEventDecryptionResult(
clearEvent = Event(
type = localEvent.type,
content = localEvent.content,
roomId = localEvent.roomId
).toContent(),
forwardingCurve25519KeyChain = emptyList(),
senderCurve25519Key = result.eventContent["sender_key"] as? String,
claimedEd25519Key = crypto.getMyDevice().fingerprint()
)
localEchoUpdater.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho)
}
val nextWorkerParams = SendEventWorker.Params(params.sessionId, encryptedEvent) val nextWorkerParams = SendEventWorker.Params(params.sessionId, encryptedEvent)
return Result.success(WorkerParamsFactory.toData(nextWorkerParams)) return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
} else { } else {

View File

@ -17,7 +17,11 @@
package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
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.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import timber.log.Timber import timber.log.Timber
@ -38,4 +42,15 @@ internal class LocalEchoUpdater @Inject constructor(private val monarchy: Monarc
} }
} }
} }
fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) {
monarchy.writeAsync { realm ->
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
if (sendingEventEntity != null) {
sendingEventEntity.type = EventType.ENCRYPTED
sendingEventEntity.content = ContentMapper.map(encryptedContent)
sendingEventEntity.setDecryptionResult(mxEventDecryptionResult)
}
}
}
} }

View File

@ -23,6 +23,7 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Event 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.WorkManagerProvider import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.SessionWorkerParams
@ -49,6 +50,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
@Inject lateinit var workManagerProvider: WorkManagerProvider @Inject lateinit var workManagerProvider: WorkManagerProvider
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon @Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
Timber.v("Start dispatch sending multiple event work") Timber.v("Start dispatch sending multiple event work")
@ -57,14 +59,17 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
Timber.e("Work cancelled due to input error from parent") Timber.e("Work cancelled due to input error from parent")
} }
if (params.lastFailureMessage != null) {
// Transmit the error
return Result.success(inputData)
}
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this) sessionComponent.inject(this)
if (params.lastFailureMessage != null) {
params.events.forEach { event ->
event.eventId?.let { localEchoUpdater.updateSendState(it, SendState.UNDELIVERED) }
}
// Transmit the error if needed?
return Result.success(inputData)
}
// Create a work for every event // Create a work for every event
params.events.forEach { event -> params.events.forEach { event ->
if (params.isEncrypted) { if (params.isEncrypted) {

View File

@ -25,6 +25,8 @@ import im.vector.matrix.android.api.session.room.model.RoomMemberContent
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.helper.addOrUpdate import im.vector.matrix.android.internal.database.helper.addOrUpdate
import im.vector.matrix.android.internal.database.helper.addTimelineEvent import im.vector.matrix.android.internal.database.helper.addTimelineEvent
import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.ContentMapper
@ -38,6 +40,7 @@ import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoo
import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.getOrNull import im.vector.matrix.android.internal.database.query.getOrNull
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
import im.vector.matrix.android.internal.session.mapWithProgress import im.vector.matrix.android.internal.session.mapWithProgress
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
@ -260,6 +263,14 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
if (sendingEventEntity != null) { if (sendingEventEntity != null) {
Timber.v("Remove local echo for tx:$it") Timber.v("Remove local echo for tx:$it")
roomEntity.sendingTimelineEvents.remove(sendingEventEntity) roomEntity.sendingTimelineEvents.remove(sendingEventEntity)
if (event.isEncrypted() && event.content?.get("algorithm") as? String == MXCRYPTO_ALGORITHM_MEGOLM) {
// updated with echo decryption, to avoid seeing it decrypt again
val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java)
sendingEventEntity.root?.decryptionResultJson?.let { json ->
eventEntity.decryptionResultJson = json
event.mxDecryptionResult = adapter.fromJson(json)
}
}
} else { } else {
Timber.v("Can't find corresponding local echo for tx:$it") Timber.v("Can't find corresponding local echo for tx:$it")
} }

View File

@ -16,7 +16,7 @@ androidExtensions {
ext.versionMajor = 0 ext.versionMajor = 0
ext.versionMinor = 18 ext.versionMinor = 18
ext.versionPatch = 0 ext.versionPatch = 1
static def getGitTimestamp() { static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct' def cmd = 'git show -s --format=%ct'

View File

@ -123,6 +123,7 @@ class KeysBackupBanner @JvmOverloads constructor(
is State.Setup -> { is State.Setup -> {
delegate?.setupKeysBackup() delegate?.setupKeysBackup()
} }
is State.Update,
is State.Recover -> { is State.Recover -> {
delegate?.recoverKeysBackup() delegate?.recoverKeysBackup()
} }

View File

@ -177,19 +177,28 @@ class AttachmentsHelper private constructor(private val context: Context,
fun handleShareIntent(intent: Intent): Boolean { fun handleShareIntent(intent: Intent): Boolean {
val type = intent.resolveType(context) ?: return false val type = intent.resolveType(context) ?: return false
if (type.startsWith("image")) { if (type.startsWith("image")) {
imagePicker.submit(IntentUtils.getPickerIntentForSharing(intent)) imagePicker.submit(safeShareIntent(intent))
} else if (type.startsWith("video")) { } else if (type.startsWith("video")) {
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent)) videoPicker.submit(safeShareIntent(intent))
} else if (type.startsWith("audio")) { } else if (type.startsWith("audio")) {
videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent)) videoPicker.submit(safeShareIntent(intent))
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) { } else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) {
filePicker.submit(IntentUtils.getPickerIntentForSharing(intent)) filePicker.submit(safeShareIntent(intent))
} else { } else {
return false return false
} }
return true return true
} }
private fun safeShareIntent(intent: Intent): Intent {
// Work around for getPickerIntentForSharing doing NPE in android 10
return try {
IntentUtils.getPickerIntentForSharing(intent)
} catch (failure: Throwable) {
intent
}
}
private fun getPickerManagerForRequestCode(requestCode: Int): PickerManager? { private fun getPickerManagerForRequestCode(requestCode: Int): PickerManager? {
return when (requestCode) { return when (requestCode) {
PICK_IMAGE_DEVICE -> imagePicker PICK_IMAGE_DEVICE -> imagePicker

View File

@ -31,10 +31,12 @@ import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.ui.list.GenericItem import im.vector.riotx.core.ui.list.GenericItem
import im.vector.riotx.core.ui.list.genericItem import im.vector.riotx.core.ui.list.genericItem
import im.vector.riotx.features.settings.VectorPreferences
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
class KeysBackupSettingsRecyclerViewController @Inject constructor(private val stringProvider: StringProvider, class KeysBackupSettingsRecyclerViewController @Inject constructor(private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences,
private val session: Session) : TypedEpoxyController<KeysBackupSettingViewState>() { private val session: Session) : TypedEpoxyController<KeysBackupSettingViewState>() {
var listener: Listener? = null var listener: Listener? = null
@ -149,8 +151,10 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(private val s
description(keyVersionResult?.algorithm ?: "") description(keyVersionResult?.algorithm ?: "")
} }
if (vectorPreferences.developerMode()) {
buildKeysBackupTrust(data.keysBackupVersionTrust) buildKeysBackupTrust(data.keysBackupVersionTrust)
} }
}
// Footer // Footer
keysBackupSettingFooterItem { keysBackupSettingFooterItem {

View File

@ -297,11 +297,18 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it)
is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it)
is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it)
is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it)
is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it)
}.exhaustive }.exhaustive
} }
} }
private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSuccess) {
updateComposerText("")
lockSendButton = false
navigator.openRoom(vectorBaseActivity, action.roomId)
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
if (savedInstanceState == null) { if (savedInstanceState == null) {

View File

@ -50,6 +50,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
abstract class SendMessageResult : RoomDetailViewEvents() abstract class SendMessageResult : RoomDetailViewEvents()
object MessageSent : SendMessageResult() object MessageSent : SendMessageResult()
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult()
class SlashCommandUnknown(val command: String) : SendMessageResult() class SlashCommandUnknown(val command: String) : SendMessageResult()
data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult()

View File

@ -379,8 +379,8 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
} }
is ParsedCommand.JoinRoom -> { is ParsedCommand.JoinRoom -> {
// TODO handleJoinToAnotherRoomSlashCommand(slashCommandResult)
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) popDraft()
} }
is ParsedCommand.PartRoom -> { is ParsedCommand.PartRoom -> {
// TODO // TODO
@ -512,6 +512,22 @@ class RoomDetailViewModel @AssistedInject constructor(
room.deleteDraft(NoOpMatrixCallback()) room.deleteDraft(NoOpMatrixCallback())
} }
private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
session.joinRoom(command.roomAlias, command.reason, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
session.getRoomSummary(command.roomAlias)
?.roomId
?.let {
_viewEvents.post(RoomDetailViewEvents.JoinRoomCommandSuccess(it))
}
}
override fun onFailure(failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
}
})
}
private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray() val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
return buildString { return buildString {

View File

@ -1461,7 +1461,7 @@ Why choose Riot.im?
<string name="keys_backup_banner_recover_line1">Never lose encrypted messages</string> <string name="keys_backup_banner_recover_line1">Never lose encrypted messages</string>
<string name="keys_backup_banner_recover_line2">Use Key Backup</string> <string name="keys_backup_banner_recover_line2">Use Key Backup</string>
<string name="keys_backup_banner_update_line1">New encrypted messages keys</string> <string name="keys_backup_banner_update_line1">New secure message keys</string>
<string name="keys_backup_banner_update_line2">Manage in Key Backup</string> <string name="keys_backup_banner_update_line2">Manage in Key Backup</string>
<string name="keys_backup_banner_in_progress">Backing up keys…</string> <string name="keys_backup_banner_in_progress">Backing up keys…</string>