mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-24 18:36:21 +03:00
Merge branch 'release/1.0.8'
This commit is contained in:
commit
34760a00be
144 changed files with 1987 additions and 1224 deletions
10
.github/ISSUE_TEMPLATE/matrix-sdk.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/matrix-sdk.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: Matrix SDK
|
||||||
|
about: Report issue or ask for a feature regarding the Android Matrix SDK
|
||||||
|
title: "[SDK] "
|
||||||
|
labels: matrix-sdk
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- This issue template should be used by third party application maintainers, to report a bug or to request a feature on the SDK module of the application Element Android-->
|
28
CHANGES.md
28
CHANGES.md
|
@ -1,3 +1,31 @@
|
||||||
|
Changes in Element 1.0.8 (2020-09-25)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Add "show password" in import Megolm keys dialog
|
||||||
|
- Visually disable call buttons in menu and prohibit calling when permissions are insufficient (#2112)
|
||||||
|
- Better management of requested permissions (#2048)
|
||||||
|
- Add a setting to show timestamp for all messages (#2123)
|
||||||
|
- Use cache for user color
|
||||||
|
- Allow using an outdated homeserver, at user's risk (#1972)
|
||||||
|
- Restore small logo on login screens and fix scrolling issue on those screens
|
||||||
|
- PIN Code Improvements: Add more settings: biometrics, grace period, notification content (#1985)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Long message cannot be sent/takes infinite time & blocks other messages (#1397)
|
||||||
|
- Fix crash when wellknown are malformed, or redirect to some HTML content (reported by rageshakes)
|
||||||
|
- User Verification in DM not working
|
||||||
|
- Manual import of Megolm keys does back up the imported keys
|
||||||
|
- Auto scrolling to the latest message when sending (#2094)
|
||||||
|
- Fix incorrect permission check when creating widgets (#2137)
|
||||||
|
- Pin code: user has to enter pin code twice (#2005)
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- Rename `tryThis` to `tryOrNull`
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Add an advanced action to reset an account data entry
|
||||||
|
|
||||||
Changes in Element 1.0.7 (2020-09-17)
|
Changes in Element 1.0.7 (2020-09-17)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
@ -212,7 +212,7 @@ class UnwedgingTest : InstrumentedTest {
|
||||||
mTestHelper.waitWithLatch {
|
mTestHelper.waitWithLatch {
|
||||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||||
// we should get back the key and be able to decrypt
|
// we should get back the key and be able to decrypt
|
||||||
val result = tryThis {
|
val result = tryOrNull {
|
||||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||||
}
|
}
|
||||||
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
||||||
|
|
|
@ -20,7 +20,7 @@ import android.util.Log
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
@ -227,7 +227,7 @@ class WithHeldTests : InstrumentedTest {
|
||||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also {
|
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also {
|
||||||
// try to decrypt and force key request
|
// try to decrypt and force key request
|
||||||
tryThis { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
|
tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
|
||||||
}
|
}
|
||||||
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
|
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
|
||||||
timeLineEvent != null
|
timeLineEvent != null
|
||||||
|
|
|
@ -17,13 +17,11 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.auth.data
|
package org.matrix.android.sdk.api.auth.data
|
||||||
|
|
||||||
// Either a list of supported login types, or an error if the homeserver is outdated
|
|
||||||
sealed class LoginFlowResult {
|
sealed class LoginFlowResult {
|
||||||
data class Success(
|
data class Success(
|
||||||
val supportedLoginTypes: List<String>,
|
val supportedLoginTypes: List<String>,
|
||||||
val isLoginAndRegistrationSupported: Boolean,
|
val isLoginAndRegistrationSupported: Boolean,
|
||||||
val homeServerUrl: String
|
val homeServerUrl: String,
|
||||||
|
val isOutdatedHomeserver: Boolean
|
||||||
) : LoginFlowResult()
|
) : LoginFlowResult()
|
||||||
|
|
||||||
object OutdatedHomeserver : LoginFlowResult()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.extensions
|
||||||
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
inline fun <A> tryThis(message: String? = null, operation: () -> A): A? {
|
inline fun <A> tryOrNull(message: String? = null, operation: () -> A): A? {
|
||||||
return try {
|
return try {
|
||||||
operation()
|
operation()
|
||||||
} catch (any: Throwable) {
|
} catch (any: Throwable) {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.failure
|
package org.matrix.android.sdk.api.failure
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.auth.registration.RegistrationFlowResponse
|
import org.matrix.android.sdk.internal.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -49,7 +49,7 @@ fun Throwable.isInvalidPassword(): Boolean {
|
||||||
*/
|
*/
|
||||||
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
|
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
|
||||||
return if (this is Failure.OtherServerError && this.httpCode == 401) {
|
return if (this is Failure.OtherServerError && this.httpCode == 401) {
|
||||||
tryThis {
|
tryOrNull {
|
||||||
MoshiProvider.providesMoshi()
|
MoshiProvider.providesMoshi()
|
||||||
.adapter(RegistrationFlowResponse::class.java)
|
.adapter(RegistrationFlowResponse::class.java)
|
||||||
.fromJson(this.errorBody)
|
.fromJson(this.errorBody)
|
||||||
|
|
|
@ -273,16 +273,16 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
||||||
return if (versions.isSupportedBySdk()) {
|
// Get the login flow
|
||||||
// Get the login flow
|
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
|
||||||
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
|
apiCall = authAPI.getLoginFlows()
|
||||||
apiCall = authAPI.getLoginFlows()
|
|
||||||
}
|
|
||||||
LoginFlowResult.Success(loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
|
|
||||||
} else {
|
|
||||||
// Not supported
|
|
||||||
LoginFlowResult.OutdatedHomeserver
|
|
||||||
}
|
}
|
||||||
|
return LoginFlowResult.Success(
|
||||||
|
loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
||||||
|
versions.isLoginAndRegistrationSupportedBySdk(),
|
||||||
|
homeServerUrl,
|
||||||
|
!versions.isSupportedBySdk()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRegistrationWizard(): RegistrationWizard {
|
override fun getRegistrationWizard(): RegistrationWizard {
|
||||||
|
|
|
@ -18,10 +18,9 @@
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
@ -32,28 +31,29 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
|
import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class CancelGossipRequestWorker(context: Context,
|
internal class CancelGossipRequestWorker(context: Context,
|
||||||
params: WorkerParameters)
|
params: WorkerParameters)
|
||||||
: CoroutineWorker(context, params) {
|
: SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
val sessionId: String,
|
override val sessionId: String,
|
||||||
val requestId: String,
|
val requestId: String,
|
||||||
val recipients: Map<String, List<String>>
|
val recipients: Map<String, List<String>>,
|
||||||
) {
|
override val lastFailureMessage: String? = null
|
||||||
|
) : SessionWorkerParams {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromRequest(sessionId: String, request: OutgoingGossipingRequest): Params {
|
fun fromRequest(sessionId: String, request: OutgoingGossipingRequest): Params {
|
||||||
return Params(
|
return Params(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
requestId = request.requestId,
|
requestId = request.requestId,
|
||||||
recipients = request.recipients
|
recipients = request.recipients,
|
||||||
|
lastFailureMessage = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,18 +64,11 @@ internal class CancelGossipRequestWorker(context: Context,
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
@Inject lateinit var credentials: Credentials
|
@Inject lateinit var credentials: Credentials
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
|
injector.inject(this)
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
}
|
||||||
?: return Result.success(errorOutputData)
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId)
|
|
||||||
?: return Result.success(errorOutputData).also {
|
|
||||||
// TODO, can this happen? should I update local echo?
|
|
||||||
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
|
|
||||||
}
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
|
|
||||||
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
val localId = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
val toDeviceContent = ShareRequestCancellation(
|
val toDeviceContent = ShareRequestCancellation(
|
||||||
|
@ -107,13 +100,17 @@ internal class CancelGossipRequestWorker(context: Context,
|
||||||
)
|
)
|
||||||
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)
|
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
} catch (exception: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
return if (exception.shouldBeRetried()) {
|
return if (throwable.shouldBeRetried()) {
|
||||||
Result.retry()
|
Result.retry()
|
||||||
} else {
|
} else {
|
||||||
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.FAILED_TO_CANCEL)
|
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.FAILED_TO_CANCEL)
|
||||||
Result.success(errorOutputData)
|
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,16 @@ import androidx.lifecycle.LiveData
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
|
@ -102,12 +108,6 @@ import org.matrix.android.sdk.internal.task.launchToCallback
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.internal.util.fetchCopied
|
import org.matrix.android.sdk.internal.util.fetchCopied
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.cancelChildren
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
@ -345,13 +345,13 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
// Open the store
|
// Open the store
|
||||||
cryptoStore.open()
|
cryptoStore.open()
|
||||||
// this can throw if no network
|
// this can throw if no network
|
||||||
tryThis {
|
tryOrNull {
|
||||||
uploadDeviceKeys()
|
uploadDeviceKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||||
// this can throw if no backup
|
// this can throw if no backup
|
||||||
tryThis {
|
tryOrNull {
|
||||||
keysBackupService.checkAndStartKeysBackup()
|
keysBackupService.checkAndStartKeysBackup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1072,7 +1072,11 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
throw Exception("Error")
|
throw Exception("Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
megolmSessionDataImporter.handle(importedSessions, true, progressListener)
|
megolmSessionDataImporter.handle(
|
||||||
|
megolmSessionsData = importedSessions,
|
||||||
|
fromBackup = false,
|
||||||
|
progressListener = progressListener
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.foldToCallback(callback)
|
}.foldToCallback(callback)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,9 @@
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
@ -34,40 +33,34 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
|
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class SendGossipRequestWorker(context: Context,
|
internal class SendGossipRequestWorker(context: Context,
|
||||||
params: WorkerParameters)
|
params: WorkerParameters)
|
||||||
: CoroutineWorker(context, params) {
|
: SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
val sessionId: String,
|
override val sessionId: String,
|
||||||
val keyShareRequest: OutgoingRoomKeyRequest? = null,
|
val keyShareRequest: OutgoingRoomKeyRequest? = null,
|
||||||
val secretShareRequest: OutgoingSecretRequest? = null
|
val secretShareRequest: OutgoingSecretRequest? = null,
|
||||||
)
|
override val lastFailureMessage: String? = null
|
||||||
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
||||||
@Inject lateinit var cryptoStore: IMXCryptoStore
|
@Inject lateinit var cryptoStore: IMXCryptoStore
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
@Inject lateinit var credentials: Credentials
|
@Inject lateinit var credentials: Credentials
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
|
injector.inject(this)
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
}
|
||||||
?: return Result.success(errorOutputData)
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId)
|
|
||||||
?: return Result.success(errorOutputData).also {
|
|
||||||
// TODO, can this happen? should I update local echo?
|
|
||||||
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
|
|
||||||
}
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
|
|
||||||
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
val localId = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
val eventType: String
|
val eventType: String
|
||||||
|
@ -121,7 +114,7 @@ internal class SendGossipRequestWorker(context: Context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
return Result.success(errorOutputData).also {
|
return buildErrorResult(params, "Unknown empty gossiping request").also {
|
||||||
Timber.e("Unknown empty gossiping request: $params")
|
Timber.e("Unknown empty gossiping request: $params")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,13 +130,17 @@ internal class SendGossipRequestWorker(context: Context,
|
||||||
)
|
)
|
||||||
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT)
|
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
} catch (exception: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
return if (exception.shouldBeRetried()) {
|
return if (throwable.shouldBeRetried()) {
|
||||||
Result.retry()
|
Result.retry()
|
||||||
} else {
|
} else {
|
||||||
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.FAILED_TO_SEND)
|
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.FAILED_TO_SEND)
|
||||||
Result.success(errorOutputData)
|
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,9 @@
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
@ -34,22 +33,23 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class SendGossipWorker(context: Context,
|
internal class SendGossipWorker(context: Context,
|
||||||
params: WorkerParameters)
|
params: WorkerParameters)
|
||||||
: CoroutineWorker(context, params) {
|
: SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
val sessionId: String,
|
override val sessionId: String,
|
||||||
val secretValue: String,
|
val secretValue: String,
|
||||||
val request: IncomingSecretShareRequest
|
val request: IncomingSecretShareRequest,
|
||||||
)
|
override val lastFailureMessage: String? = null
|
||||||
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
||||||
@Inject lateinit var cryptoStore: IMXCryptoStore
|
@Inject lateinit var cryptoStore: IMXCryptoStore
|
||||||
|
@ -58,18 +58,11 @@ internal class SendGossipWorker(context: Context,
|
||||||
@Inject lateinit var messageEncrypter: MessageEncrypter
|
@Inject lateinit var messageEncrypter: MessageEncrypter
|
||||||
@Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
|
@Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
|
injector.inject(this)
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
}
|
||||||
?: return Result.success(errorOutputData)
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId)
|
|
||||||
?: return Result.success(errorOutputData).also {
|
|
||||||
// TODO, can this happen? should I update local echo?
|
|
||||||
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
|
|
||||||
}
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
|
|
||||||
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
val localId = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
val eventType: String = EventType.SEND_SECRET
|
val eventType: String = EventType.SEND_SECRET
|
||||||
|
|
||||||
|
@ -81,7 +74,7 @@ internal class SendGossipWorker(context: Context,
|
||||||
val requestingUserId = params.request.userId ?: ""
|
val requestingUserId = params.request.userId ?: ""
|
||||||
val requestingDeviceId = params.request.deviceId ?: ""
|
val requestingDeviceId = params.request.deviceId ?: ""
|
||||||
val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId)
|
val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId)
|
||||||
?: return Result.success(errorOutputData).also {
|
?: return buildErrorResult(params, "Unknown deviceInfo, cannot send message").also {
|
||||||
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
||||||
Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}")
|
Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}")
|
||||||
}
|
}
|
||||||
|
@ -94,7 +87,7 @@ internal class SendGossipWorker(context: Context,
|
||||||
if (olmSessionResult?.sessionId == null) {
|
if (olmSessionResult?.sessionId == null) {
|
||||||
// no session with this device, probably because there
|
// no session with this device, probably because there
|
||||||
// were no one-time keys.
|
// were no one-time keys.
|
||||||
return Result.success(errorOutputData).also {
|
return buildErrorResult(params, "no session with this device").also {
|
||||||
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
||||||
Timber.e("no session with this device, probably because there were no one-time keys.")
|
Timber.e("no session with this device, probably because there were no one-time keys.")
|
||||||
}
|
}
|
||||||
|
@ -130,13 +123,17 @@ internal class SendGossipWorker(context: Context,
|
||||||
)
|
)
|
||||||
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.ACCEPTED)
|
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.ACCEPTED)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
} catch (exception: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
return if (exception.shouldBeRetried()) {
|
return if (throwable.shouldBeRetried()) {
|
||||||
Result.retry()
|
Result.retry()
|
||||||
} else {
|
} else {
|
||||||
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
||||||
Result.success(errorOutputData)
|
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
|
||||||
* Must be call on the crypto coroutine thread
|
* Must be call on the crypto coroutine thread
|
||||||
*
|
*
|
||||||
* @param megolmSessionsData megolm sessions.
|
* @param megolmSessionsData megolm sessions.
|
||||||
* @param backUpKeys true to back up them to the homeserver.
|
* @param fromBackup true if the imported keys are already backed up on the server.
|
||||||
* @param progressListener the progress listener
|
* @param progressListener the progress listener
|
||||||
* @return import room keys result
|
* @return import room keys result
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,6 +20,10 @@ package org.matrix.android.sdk.internal.crypto.store.db
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.Sort
|
||||||
|
import io.realm.kotlin.where
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
@ -85,10 +89,6 @@ import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.Sort
|
|
||||||
import io.realm.kotlin.where
|
|
||||||
import org.matrix.olm.OlmAccount
|
import org.matrix.olm.OlmAccount
|
||||||
import org.matrix.olm.OlmException
|
import org.matrix.olm.OlmException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -541,7 +541,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
deviceId = it.deviceId
|
deviceId = it.deviceId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
monarchy.writeAsync { realm ->
|
doRealmTransactionAsync(realmConfiguration) { realm ->
|
||||||
realm.where<MyDeviceLastSeenInfoEntity>().findAll().deleteAllFromRealm()
|
realm.where<MyDeviceLastSeenInfoEntity>().findAll().deleteAllFromRealm()
|
||||||
entities.forEach {
|
entities.forEach {
|
||||||
realm.insertOrUpdate(it)
|
realm.insertOrUpdate(it)
|
||||||
|
@ -1191,7 +1191,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
.findAll()
|
.findAll()
|
||||||
.mapNotNull { entity ->
|
.mapNotNull { entity ->
|
||||||
when (entity.type) {
|
when (entity.type) {
|
||||||
GossipRequestType.KEY -> {
|
GossipRequestType.KEY -> {
|
||||||
IncomingRoomKeyRequest(
|
IncomingRoomKeyRequest(
|
||||||
userId = entity.otherUserId,
|
userId = entity.otherUserId,
|
||||||
deviceId = entity.otherDeviceId,
|
deviceId = entity.otherDeviceId,
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.store.db
|
||||||
|
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||||
|
@ -398,7 +398,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
|
?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
|
||||||
?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
|
?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
|
||||||
?.transform { deviceInfoEntity ->
|
?.transform { deviceInfoEntity ->
|
||||||
tryThis {
|
tryOrNull {
|
||||||
deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
|
deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.store.db.model
|
package org.matrix.android.sdk.internal.crypto.store.db.model
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.crypto.GossipRequestType
|
import org.matrix.android.sdk.internal.crypto.GossipRequestType
|
||||||
import org.matrix.android.sdk.internal.crypto.GossipingRequestState
|
import org.matrix.android.sdk.internal.crypto.GossipingRequestState
|
||||||
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
||||||
|
@ -45,7 +45,7 @@ internal open class IncomingGossipingRequestEntity(@Index var requestId: String?
|
||||||
|
|
||||||
var type: GossipRequestType
|
var type: GossipRequestType
|
||||||
get() {
|
get() {
|
||||||
return tryThis { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
|
return tryOrNull { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
typeStr = value.name
|
typeStr = value.name
|
||||||
|
@ -55,7 +55,7 @@ internal open class IncomingGossipingRequestEntity(@Index var requestId: String?
|
||||||
|
|
||||||
var requestState: GossipingRequestState
|
var requestState: GossipingRequestState
|
||||||
get() {
|
get() {
|
||||||
return tryThis { GossipingRequestState.valueOf(requestStateStr) }
|
return tryOrNull { GossipingRequestState.valueOf(requestStateStr) }
|
||||||
?: GossipingRequestState.NONE
|
?: GossipingRequestState.NONE
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter
|
import com.squareup.moshi.JsonAdapter
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.crypto.GossipRequestType
|
import org.matrix.android.sdk.internal.crypto.GossipRequestType
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequest
|
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState
|
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState
|
||||||
|
@ -47,7 +47,7 @@ internal open class OutgoingGossipingRequestEntity(
|
||||||
|
|
||||||
var type: GossipRequestType
|
var type: GossipRequestType
|
||||||
get() {
|
get() {
|
||||||
return tryThis { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
|
return tryOrNull { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
typeStr = value.name
|
typeStr = value.name
|
||||||
|
@ -57,7 +57,7 @@ internal open class OutgoingGossipingRequestEntity(
|
||||||
|
|
||||||
var requestState: OutgoingGossipingRequestState
|
var requestState: OutgoingGossipingRequestState
|
||||||
get() {
|
get() {
|
||||||
return tryThis { OutgoingGossipingRequestState.valueOf(requestStateStr) }
|
return tryOrNull { OutgoingGossipingRequestState.valueOf(requestStateStr) }
|
||||||
?: OutgoingGossipingRequestState.UNSENT
|
?: OutgoingGossipingRequestState.UNSENT
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
|
@ -17,17 +17,17 @@
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
package org.matrix.android.sdk.internal.crypto.verification
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.Data
|
import androidx.work.Data
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -37,56 +37,56 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
internal class SendVerificationMessageWorker(context: Context,
|
internal class SendVerificationMessageWorker(context: Context,
|
||||||
params: WorkerParameters)
|
params: WorkerParameters)
|
||||||
: CoroutineWorker(context, params) {
|
: SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
val event: Event,
|
val eventId: String,
|
||||||
override val lastFailureMessage: String? = null
|
override val lastFailureMessage: String? = null
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject
|
@Inject lateinit var sendVerificationMessageTask: SendVerificationMessageTask
|
||||||
lateinit var sendVerificationMessageTask: SendVerificationMessageTask
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
|
@Inject lateinit var cryptoService: CryptoService
|
||||||
|
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||||
|
|
||||||
@Inject
|
override fun injectWith(injector: SessionComponent) {
|
||||||
lateinit var cryptoService: CryptoService
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
val errorOutputData = Data.Builder().putBoolean(OUTPUT_KEY_FAILED, true).build()
|
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId) ?: return buildErrorResult(params, "Event not found")
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
val localEventId = localEvent.eventId ?: ""
|
||||||
?: return Result.success(errorOutputData)
|
val roomId = localEvent.roomId ?: ""
|
||||||
|
|
||||||
|
if (cancelSendTracker.isCancelRequestedFor(localEventId, roomId)) {
|
||||||
|
return Result.success()
|
||||||
|
.also {
|
||||||
|
cancelSendTracker.markCancelled(localEventId, roomId)
|
||||||
|
Timber.e("## SendEvent: Event sending has been cancelled $localEventId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId)
|
|
||||||
?: return Result.success(errorOutputData).also {
|
|
||||||
// TODO, can this happen? should I update local echo?
|
|
||||||
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
|
|
||||||
}
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
val localId = params.event.eventId ?: ""
|
|
||||||
return try {
|
return try {
|
||||||
val eventId = sendVerificationMessageTask.execute(
|
val resultEventId = sendVerificationMessageTask.execute(
|
||||||
SendVerificationMessageTask.Params(
|
SendVerificationMessageTask.Params(
|
||||||
event = params.event,
|
event = localEvent,
|
||||||
cryptoService = cryptoService
|
cryptoService = cryptoService
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Result.success(Data.Builder().putString(localId, eventId).build())
|
Result.success(Data.Builder().putString(localEventId, resultEventId).build())
|
||||||
} catch (exception: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
if (exception.shouldBeRetried()) {
|
if (throwable.shouldBeRetried()) {
|
||||||
Result.retry()
|
Result.retry()
|
||||||
} else {
|
} else {
|
||||||
Result.success(errorOutputData)
|
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
private const val OUTPUT_KEY_FAILED = "failed"
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
|
||||||
fun hasFailed(outputData: Data): Boolean {
|
|
||||||
return outputData.getBoolean(OUTPUT_KEY_FAILED, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ import androidx.work.Data
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.Operation
|
import androidx.work.Operation
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.R
|
import org.matrix.android.sdk.R
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
|
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||||
|
@ -51,10 +54,8 @@ import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.util.StringProvider
|
import org.matrix.android.sdk.internal.util.StringProvider
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -87,7 +88,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
|
|
||||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
event = event
|
eventId = event.eventId ?: ""
|
||||||
))
|
))
|
||||||
val enqueueInfo = enqueueSendWork(workerParams)
|
val enqueueInfo = enqueueSendWork(workerParams)
|
||||||
|
|
||||||
|
@ -115,20 +116,30 @@ internal class VerificationTransportRoomMessage(
|
||||||
val observer = object : Observer<List<WorkInfo>> {
|
val observer = object : Observer<List<WorkInfo>> {
|
||||||
override fun onChanged(workInfoList: List<WorkInfo>?) {
|
override fun onChanged(workInfoList: List<WorkInfo>?) {
|
||||||
workInfoList
|
workInfoList
|
||||||
?.filter { it.state == WorkInfo.State.SUCCEEDED }
|
|
||||||
?.firstOrNull { it.id == enqueueInfo.second }
|
?.firstOrNull { it.id == enqueueInfo.second }
|
||||||
?.let { wInfo ->
|
?.let { wInfo ->
|
||||||
if (SendVerificationMessageWorker.hasFailed(wInfo.outputData)) {
|
when (wInfo.state) {
|
||||||
Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
|
WorkInfo.State.FAILED -> {
|
||||||
tx?.cancel(onErrorReason)
|
tx?.cancel(onErrorReason)
|
||||||
} else {
|
workLiveData.removeObserver(this)
|
||||||
if (onDone != null) {
|
}
|
||||||
onDone()
|
WorkInfo.State.SUCCEEDED -> {
|
||||||
} else {
|
if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
|
||||||
tx?.state = nextState
|
Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
|
||||||
|
tx?.cancel(onErrorReason)
|
||||||
|
} else {
|
||||||
|
if (onDone != null) {
|
||||||
|
onDone()
|
||||||
|
} else {
|
||||||
|
tx?.state = nextState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workLiveData.removeObserver(this)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// nop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
workLiveData.removeObserver(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,7 +185,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
|
|
||||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
event = event
|
eventId = event.eventId ?: ""
|
||||||
))
|
))
|
||||||
|
|
||||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
|
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
|
||||||
|
@ -184,7 +195,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
workManagerProvider.workManager
|
workManagerProvider.workManager
|
||||||
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest)
|
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
|
|
||||||
// I cannot just listen to the given work request, because when used in a uniqueWork,
|
// I cannot just listen to the given work request, because when used in a uniqueWork,
|
||||||
|
@ -199,7 +210,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
?.filter { it.state == WorkInfo.State.SUCCEEDED }
|
?.filter { it.state == WorkInfo.State.SUCCEEDED }
|
||||||
?.firstOrNull { it.id == workRequest.id }
|
?.firstOrNull { it.id == workRequest.id }
|
||||||
?.let { wInfo ->
|
?.let { wInfo ->
|
||||||
if (SendVerificationMessageWorker.hasFailed(wInfo.outputData)) {
|
if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
|
||||||
callback(null, null)
|
callback(null, null)
|
||||||
} else {
|
} else {
|
||||||
val eventId = wInfo.outputData.getString(localId)
|
val eventId = wInfo.outputData.getString(localId)
|
||||||
|
@ -229,7 +240,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
)
|
)
|
||||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
event = event
|
eventId = event.eventId ?: ""
|
||||||
))
|
))
|
||||||
enqueueSendWork(workerParams)
|
enqueueSendWork(workerParams)
|
||||||
}
|
}
|
||||||
|
@ -249,7 +260,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
)
|
)
|
||||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
event = event
|
eventId = event.eventId ?: ""
|
||||||
))
|
))
|
||||||
val enqueueInfo = enqueueSendWork(workerParams)
|
val enqueueInfo = enqueueSendWork(workerParams)
|
||||||
|
|
||||||
|
@ -280,7 +291,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
|
||||||
.build()
|
.build()
|
||||||
return workManagerProvider.workManager
|
return workManagerProvider.workManager
|
||||||
.beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND, workRequest)
|
.beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||||
.enqueue() to workRequest.id
|
.enqueue() to workRequest.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,31 +16,52 @@
|
||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.database
|
package org.matrix.android.sdk.internal.database
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T) = withContext(Dispatchers.Default) {
|
internal fun <T> CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: suspend (realm: Realm) -> T) {
|
||||||
Realm.getInstance(config).use { bgRealm ->
|
asyncTransaction(monarchy.realmConfiguration, transaction)
|
||||||
bgRealm.beginTransaction()
|
}
|
||||||
val result: T
|
|
||||||
try {
|
internal fun <T> CoroutineScope.asyncTransaction(realmConfiguration: RealmConfiguration, transaction: suspend (realm: Realm) -> T) {
|
||||||
val start = System.currentTimeMillis()
|
launch {
|
||||||
result = transaction(bgRealm)
|
awaitTransaction(realmConfiguration, transaction)
|
||||||
if (isActive) {
|
}
|
||||||
bgRealm.commitTransaction()
|
}
|
||||||
val end = System.currentTimeMillis()
|
|
||||||
val time = end - start
|
private val realmSemaphore = Semaphore(1)
|
||||||
Timber.v("Execute transaction in $time millis")
|
|
||||||
}
|
suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T): T {
|
||||||
} finally {
|
return realmSemaphore.withPermit {
|
||||||
if (bgRealm.isInTransaction) {
|
withContext(Dispatchers.IO) {
|
||||||
bgRealm.cancelTransaction()
|
Realm.getInstance(config).use { bgRealm ->
|
||||||
}
|
bgRealm.beginTransaction()
|
||||||
}
|
val result: T
|
||||||
result
|
try {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
result = transaction(bgRealm)
|
||||||
|
if (isActive) {
|
||||||
|
bgRealm.commitTransaction()
|
||||||
|
val end = System.currentTimeMillis()
|
||||||
|
val time = end - start
|
||||||
|
Timber.v("Execute transaction in $time millis")
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (bgRealm.isInTransaction) {
|
||||||
|
bgRealm.cancelTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,21 +20,34 @@ package org.matrix.android.sdk.internal.database.mapper
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
|
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||||
|
|
||||||
internal object ContentMapper {
|
internal object ContentMapper {
|
||||||
|
|
||||||
private val moshi = MoshiProvider.providesMoshi()
|
private val moshi = MoshiProvider.providesMoshi()
|
||||||
private val adapter = moshi.adapter<Content>(JSON_DICT_PARAMETERIZED_TYPE)
|
private val castJsonNumberMoshi by lazy {
|
||||||
|
// We are adding the CheckNumberType as we are serializing/deserializing multiple time in a row
|
||||||
|
// and we lost typing information doing so.
|
||||||
|
// We don't want this check to be done on all adapters, so we create a new moshi just for that.
|
||||||
|
MoshiProvider.providesMoshi()
|
||||||
|
.newBuilder()
|
||||||
|
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
fun map(content: String?): Content? {
|
fun map(content: String?, castJsonNumbers: Boolean = false): Content? {
|
||||||
return content?.let {
|
return content?.let {
|
||||||
adapter.fromJson(it)
|
if (castJsonNumbers) {
|
||||||
|
castJsonNumberMoshi
|
||||||
|
} else {
|
||||||
|
moshi
|
||||||
|
}.adapter<Content>(JSON_DICT_PARAMETERIZED_TYPE).fromJson(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(content: Content?): String? {
|
fun map(content: Content?): String? {
|
||||||
return content?.let {
|
return content?.let {
|
||||||
adapter.toJson(it)
|
moshi.adapter<Content>(JSON_DICT_PARAMETERIZED_TYPE).toJson(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ internal object EventMapper {
|
||||||
return eventEntity
|
return eventEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(eventEntity: EventEntity): Event {
|
fun map(eventEntity: EventEntity, castJsonNumbers: Boolean = false): Event {
|
||||||
val ud = eventEntity.unsignedData
|
val ud = eventEntity.unsignedData
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
?.let {
|
?.let {
|
||||||
|
@ -69,8 +69,8 @@ internal object EventMapper {
|
||||||
return Event(
|
return Event(
|
||||||
type = eventEntity.type,
|
type = eventEntity.type,
|
||||||
eventId = eventEntity.eventId,
|
eventId = eventEntity.eventId,
|
||||||
content = ContentMapper.map(eventEntity.content),
|
content = ContentMapper.map(eventEntity.content, castJsonNumbers),
|
||||||
prevContent = ContentMapper.map(eventEntity.prevContent),
|
prevContent = ContentMapper.map(eventEntity.prevContent, castJsonNumbers),
|
||||||
originServerTs = eventEntity.originServerTs,
|
originServerTs = eventEntity.originServerTs,
|
||||||
senderId = eventEntity.sender,
|
senderId = eventEntity.sender,
|
||||||
stateKey = eventEntity.stateKey,
|
stateKey = eventEntity.stateKey,
|
||||||
|
@ -96,8 +96,8 @@ internal object EventMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun EventEntity.asDomain(): Event {
|
internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
|
||||||
return EventMapper.map(this)
|
return EventMapper.map(this, castJsonNumbers)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long?): EventEntity {
|
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long?): EventEntity {
|
||||||
|
|
|
@ -24,7 +24,7 @@ import okio.BufferedSink
|
||||||
import okio.ForwardingSink
|
import okio.ForwardingSink
|
||||||
import okio.Sink
|
import okio.Sink
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
internal class ProgressRequestBody(private val delegate: RequestBody,
|
internal class ProgressRequestBody(private val delegate: RequestBody,
|
||||||
|
@ -40,7 +40,7 @@ internal class ProgressRequestBody(private val delegate: RequestBody,
|
||||||
|
|
||||||
override fun isDuplex() = delegate.isDuplex()
|
override fun isDuplex() = delegate.isDuplex()
|
||||||
|
|
||||||
val length = tryThis { delegate.contentLength() } ?: -1
|
val length = tryOrNull { delegate.contentLength() } ?: -1
|
||||||
|
|
||||||
override fun contentLength() = length
|
override fun contentLength() = length
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ interface CheckNumberType {
|
||||||
val numberAsString = reader.nextString()
|
val numberAsString = reader.nextString()
|
||||||
val decimal = BigDecimal(numberAsString)
|
val decimal = BigDecimal(numberAsString)
|
||||||
if (decimal.scale() <= 0) {
|
if (decimal.scale() <= 0) {
|
||||||
decimal.intValueExact()
|
decimal.longValueExact()
|
||||||
} else {
|
} else {
|
||||||
decimal.toDouble()
|
decimal.toDouble()
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import android.webkit.MimeTypeMap
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
import org.matrix.android.sdk.api.session.file.FileService
|
import org.matrix.android.sdk.api.session.file.FileService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
@ -174,7 +174,7 @@ internal class DefaultFileService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toNotify?.forEach { otherCallbacks ->
|
toNotify?.forEach { otherCallbacks ->
|
||||||
tryThis { otherCallbacks.onFailure(it) }
|
tryOrNull { otherCallbacks.onFailure(it) }
|
||||||
}
|
}
|
||||||
}, { file ->
|
}, { file ->
|
||||||
callback.onSuccess(file)
|
callback.onSuccess(file)
|
||||||
|
@ -186,7 +186,7 @@ internal class DefaultFileService @Inject constructor(
|
||||||
}
|
}
|
||||||
Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ")
|
Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ")
|
||||||
toNotify?.forEach { otherCallbacks ->
|
toNotify?.forEach { otherCallbacks ->
|
||||||
tryThis { otherCallbacks.onSuccess(file) }
|
tryOrNull { otherCallbacks.onSuccess(file) }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}.toCancelable()
|
}.toCancelable()
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.session.call
|
||||||
|
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
import org.matrix.android.sdk.api.session.call.CallsListener
|
import org.matrix.android.sdk.api.session.call.CallsListener
|
||||||
|
@ -210,7 +210,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||||
|
|
||||||
private fun onCallHangup(hangup: CallHangupContent) {
|
private fun onCallHangup(hangup: CallHangupContent) {
|
||||||
callListeners.toList().forEach {
|
callListeners.toList().forEach {
|
||||||
tryThis {
|
tryOrNull {
|
||||||
it.onCallHangupReceived(hangup)
|
it.onCallHangupReceived(hangup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +218,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||||
|
|
||||||
private fun onCallAnswer(answer: CallAnswerContent) {
|
private fun onCallAnswer(answer: CallAnswerContent) {
|
||||||
callListeners.toList().forEach {
|
callListeners.toList().forEach {
|
||||||
tryThis {
|
tryOrNull {
|
||||||
it.onCallAnswerReceived(answer)
|
it.onCallAnswerReceived(answer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,7 +226,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||||
|
|
||||||
private fun onCallManageByOtherSession(callId: String) {
|
private fun onCallManageByOtherSession(callId: String) {
|
||||||
callListeners.toList().forEach {
|
callListeners.toList().forEach {
|
||||||
tryThis {
|
tryOrNull {
|
||||||
it.onCallManagedByOtherSession(callId)
|
it.onCallManagedByOtherSession(callId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||||
if (incomingCall.otherUserId == userId) return
|
if (incomingCall.otherUserId == userId) return
|
||||||
|
|
||||||
callListeners.toList().forEach {
|
callListeners.toList().forEach {
|
||||||
tryThis {
|
tryOrNull {
|
||||||
it.onCallInviteReceived(incomingCall, invite)
|
it.onCallInviteReceived(incomingCall, invite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||||
|
|
||||||
private fun onCallIceCandidate(incomingCall: MxCall, candidates: CallCandidatesContent) {
|
private fun onCallIceCandidate(incomingCall: MxCall, candidates: CallCandidatesContent) {
|
||||||
callListeners.toList().forEach {
|
callListeners.toList().forEach {
|
||||||
tryThis {
|
tryOrNull {
|
||||||
it.onCallIceCandidateReceived(incomingCall, candidates)
|
it.onCallIceCandidateReceived(incomingCall, candidates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okio.BufferedSink
|
import okio.BufferedSink
|
||||||
import okio.source
|
import okio.source
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
import org.matrix.android.sdk.internal.di.Authenticated
|
import org.matrix.android.sdk.internal.di.Authenticated
|
||||||
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
||||||
|
@ -96,7 +96,7 @@ internal class FileUploader @Inject constructor(@Authenticated
|
||||||
inputStream.copyTo(it)
|
inputStream.copyTo(it)
|
||||||
}
|
}
|
||||||
return uploadFile(workingFile, filename, mimeType, progressListener).also {
|
return uploadFile(workingFile, filename, mimeType, progressListener).also {
|
||||||
tryThis { workingFile.delete() }
|
tryOrNull { workingFile.delete() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,10 @@ package org.matrix.android.sdk.internal.session.content
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||||
|
@ -34,13 +32,18 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||||
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
||||||
import org.matrix.android.sdk.internal.session.DefaultFileService
|
import org.matrix.android.sdk.internal.session.DefaultFileService
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoIdentifiers
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -56,12 +59,13 @@ private data class NewImageAttributes(
|
||||||
* Possible previous worker: None
|
* Possible previous worker: None
|
||||||
* Possible next worker : Always [MultipleEventSendingDispatcherWorker]
|
* Possible next worker : Always [MultipleEventSendingDispatcherWorker]
|
||||||
*/
|
*/
|
||||||
internal class UploadContentWorker(val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
internal class UploadContentWorker(val context: Context, params: WorkerParameters)
|
||||||
|
: SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
val events: List<Event>,
|
val localEchoIds: List<LocalEchoIdentifiers>,
|
||||||
val attachment: ContentAttachmentData,
|
val attachment: ContentAttachmentData,
|
||||||
val isEncrypted: Boolean,
|
val isEncrypted: Boolean,
|
||||||
val compressBeforeSending: Boolean,
|
val compressBeforeSending: Boolean,
|
||||||
|
@ -73,20 +77,14 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
@Inject lateinit var fileService: DefaultFileService
|
@Inject lateinit var fileService: DefaultFileService
|
||||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||||
@Inject lateinit var imageCompressor: ImageCompressor
|
@Inject lateinit var imageCompressor: ImageCompressor
|
||||||
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
injector.inject(this)
|
||||||
?: return Result.success()
|
}
|
||||||
.also { Timber.e("Unable to parse work parameters") }
|
|
||||||
|
|
||||||
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
Timber.v("Starting upload media work with params $params")
|
Timber.v("Starting upload media work with params $params")
|
||||||
|
|
||||||
if (params.lastFailureMessage != null) {
|
|
||||||
// Transmit the error
|
|
||||||
return Result.success(inputData)
|
|
||||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just defensive code to ensure that we never have an uncaught exception that could break the queue
|
// Just defensive code to ensure that we never have an uncaught exception that could break the queue
|
||||||
return try {
|
return try {
|
||||||
internalDoWork(params)
|
internalDoWork(params)
|
||||||
|
@ -96,11 +94,12 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun internalDoWork(params: Params): Result {
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
sessionComponent.inject(this)
|
}
|
||||||
|
|
||||||
val allCancelled = params.events.all { cancelSendTracker.isCancelRequestedFor(it.eventId, it.roomId) }
|
private suspend fun internalDoWork(params: Params): Result {
|
||||||
|
val allCancelled = params.localEchoIds.all { cancelSendTracker.isCancelRequestedFor(it.eventId, it.roomId) }
|
||||||
if (allCancelled) {
|
if (allCancelled) {
|
||||||
// there is no point in uploading the image!
|
// there is no point in uploading the image!
|
||||||
return Result.success(inputData)
|
return Result.success(inputData)
|
||||||
|
@ -214,18 +213,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## FileService: ERROR")
|
Timber.e(e, "## FileService: ERROR")
|
||||||
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
|
return handleFailure(params, e)
|
||||||
return Result.success(
|
|
||||||
WorkerParamsFactory.toData(
|
|
||||||
params.copy(
|
|
||||||
lastFailureMessage = e.localizedMessage
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} finally {
|
} finally {
|
||||||
// Delete all temporary files
|
// Delete all temporary files
|
||||||
filesToDelete.forEach {
|
filesToDelete.forEach {
|
||||||
tryThis { it.delete() }
|
tryOrNull { it.delete() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,46 +281,48 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSuccess(params: Params,
|
private suspend fun handleSuccess(params: Params,
|
||||||
attachmentUrl: String,
|
attachmentUrl: String,
|
||||||
encryptedFileInfo: EncryptedFileInfo?,
|
encryptedFileInfo: EncryptedFileInfo?,
|
||||||
thumbnailUrl: String?,
|
thumbnailUrl: String?,
|
||||||
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
||||||
newImageAttributes: NewImageAttributes?): Result {
|
newImageAttributes: NewImageAttributes?): Result {
|
||||||
notifyTracker(params) { contentUploadStateTracker.setSuccess(it) }
|
notifyTracker(params) { contentUploadStateTracker.setSuccess(it) }
|
||||||
|
params.localEchoIds.forEach {
|
||||||
|
updateEvent(it.eventId, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes)
|
||||||
|
}
|
||||||
|
|
||||||
val updatedEvents = params.events
|
val sendParams = MultipleEventSendingDispatcherWorker.Params(
|
||||||
.map {
|
sessionId = params.sessionId,
|
||||||
updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes)
|
localEchoIds = params.localEchoIds,
|
||||||
}
|
isEncrypted = params.isEncrypted
|
||||||
|
)
|
||||||
val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isEncrypted)
|
|
||||||
return Result.success(WorkerParamsFactory.toData(sendParams)).also {
|
return Result.success(WorkerParamsFactory.toData(sendParams)).also {
|
||||||
Timber.v("## handleSuccess $attachmentUrl, work is stopped $isStopped")
|
Timber.v("## handleSuccess $attachmentUrl, work is stopped $isStopped")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateEvent(event: Event,
|
private suspend fun updateEvent(eventId: String,
|
||||||
url: String,
|
url: String,
|
||||||
encryptedFileInfo: EncryptedFileInfo?,
|
encryptedFileInfo: EncryptedFileInfo?,
|
||||||
thumbnailUrl: String? = null,
|
thumbnailUrl: String? = null,
|
||||||
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
||||||
newImageAttributes: NewImageAttributes?): Event {
|
newImageAttributes: NewImageAttributes?) {
|
||||||
val messageContent: MessageContent = event.content.toModel() ?: return event
|
localEchoRepository.updateEcho(eventId) { _, event ->
|
||||||
val updatedContent = when (messageContent) {
|
val messageContent: MessageContent? = event.asDomain().content.toModel()
|
||||||
is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newImageAttributes)
|
val updatedContent = when (messageContent) {
|
||||||
is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
|
is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newImageAttributes)
|
||||||
is MessageFileContent -> messageContent.update(url, encryptedFileInfo)
|
is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
|
||||||
is MessageAudioContent -> messageContent.update(url, encryptedFileInfo)
|
is MessageFileContent -> messageContent.update(url, encryptedFileInfo)
|
||||||
else -> messageContent
|
is MessageAudioContent -> messageContent.update(url, encryptedFileInfo)
|
||||||
|
else -> messageContent
|
||||||
|
}
|
||||||
|
event.content = ContentMapper.map(updatedContent.toContent())
|
||||||
}
|
}
|
||||||
return event.copy(content = updatedContent.toContent())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyTracker(params: Params, function: (String) -> Unit) {
|
private fun notifyTracker(params: Params, function: (String) -> Unit) {
|
||||||
params.events
|
params.localEchoIds.forEach { function.invoke(it.eventId) }
|
||||||
.mapNotNull { it.eventId }
|
|
||||||
.forEach { eventId -> function.invoke(eventId) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MessageImageContent.update(url: String,
|
private fun MessageImageContent.update(url: String,
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.session.download
|
||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -76,7 +76,7 @@ internal class DefaultContentDownloadStateTracker @Inject constructor() : Progre
|
||||||
Timber.v("## DL Progress Error code:$errorCode")
|
Timber.v("## DL Progress Error code:$errorCode")
|
||||||
updateState(url, ContentDownloadStateTracker.State.Failure(errorCode))
|
updateState(url, ContentDownloadStateTracker.State.Failure(errorCode))
|
||||||
listeners[url]?.forEach {
|
listeners[url]?.forEach {
|
||||||
tryThis { it.onDownloadStateUpdate(ContentDownloadStateTracker.State.Failure(errorCode)) }
|
tryOrNull { it.onDownloadStateUpdate(ContentDownloadStateTracker.State.Failure(errorCode)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ internal class DefaultContentDownloadStateTracker @Inject constructor() : Progre
|
||||||
private fun updateState(url: String, state: ContentDownloadStateTracker.State) {
|
private fun updateState(url: String, state: ContentDownloadStateTracker.State) {
|
||||||
states[url] = state
|
states[url] = state
|
||||||
listeners[url]?.forEach {
|
listeners[url]?.forEach {
|
||||||
tryThis { it.onDownloadStateUpdate(state) }
|
tryOrNull { it.onDownloadStateUpdate(state) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,20 +18,19 @@
|
||||||
package org.matrix.android.sdk.internal.session.group
|
package org.matrix.android.sdk.internal.session.group
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possible previous worker: None
|
* Possible previous worker: None
|
||||||
* Possible next worker : None
|
* Possible next worker : None
|
||||||
*/
|
*/
|
||||||
internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
internal class GetGroupDataWorker(context: Context, params: WorkerParameters)
|
||||||
|
: SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
|
@ -41,13 +40,11 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
|
||||||
|
|
||||||
@Inject lateinit var getGroupDataTask: GetGroupDataTask
|
@Inject lateinit var getGroupDataTask: GetGroupDataTask
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
injector.inject(this)
|
||||||
?: return Result.failure()
|
}
|
||||||
.also { Timber.e("Unable to parse work parameters") }
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
sessionComponent.inject(this)
|
|
||||||
return runCatching {
|
return runCatching {
|
||||||
getGroupDataTask.execute(GetGroupDataTask.Params.FetchAllActive)
|
getGroupDataTask.execute(GetGroupDataTask.Params.FetchAllActive)
|
||||||
}.fold(
|
}.fold(
|
||||||
|
@ -55,4 +52,8 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
|
||||||
{ Result.retry() }
|
{ Result.retry() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import androidx.lifecycle.LifecycleRegistry
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
@ -113,7 +113,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
// Url has changed, we have to reset our store, update internal configuration and notify listeners
|
// Url has changed, we have to reset our store, update internal configuration and notify listeners
|
||||||
identityStore.setUrl(baseUrl)
|
identityStore.setUrl(baseUrl)
|
||||||
updateIdentityAPI(baseUrl)
|
updateIdentityAPI(baseUrl)
|
||||||
listeners.toList().forEach { tryThis { it.onIdentityServerChange() } }
|
listeners.toList().forEach { tryOrNull { it.onIdentityServerChange() } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
private suspend fun updateAccountData(url: String?) {
|
private suspend fun updateAccountData(url: String?) {
|
||||||
// Also notify the listener
|
// Also notify the listener
|
||||||
withContext(coroutineDispatchers.main) {
|
withContext(coroutineDispatchers.main) {
|
||||||
listeners.toList().forEach { tryThis { it.onIdentityServerChange() } }
|
listeners.toList().forEach { tryOrNull { it.onIdentityServerChange() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams(
|
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams(
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val p
|
||||||
|
|
||||||
Timber.d("Get ${accountThreePidsResponse.threePids?.size} threePids")
|
Timber.d("Get ${accountThreePidsResponse.threePids?.size} threePids")
|
||||||
// Store the list in DB
|
// Store the list in DB
|
||||||
monarchy.writeAsync { realm ->
|
monarchy.awaitTransaction { realm ->
|
||||||
realm.where(UserThreePidEntity::class.java).findAll().deleteAllFromRealm()
|
realm.where(UserThreePidEntity::class.java).findAll().deleteAllFromRealm()
|
||||||
accountThreePidsResponse.threePids?.forEach {
|
accountThreePidsResponse.threePids?.forEach {
|
||||||
val entity = UserThreePidEntity(
|
val entity = UserThreePidEntity(
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
package org.matrix.android.sdk.internal.session.pushers
|
package org.matrix.android.sdk.internal.session.pushers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.session.pushers.PusherState
|
import org.matrix.android.sdk.api.session.pushers.PusherState
|
||||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||||
|
@ -28,16 +28,14 @@ import org.matrix.android.sdk.internal.database.model.PusherEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
|
internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
|
||||||
: CoroutineWorker(context, params) {
|
: SessionSafeCoroutineWorker<AddHttpPusherWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
|
@ -50,14 +48,11 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
|
||||||
@Inject @SessionDatabase lateinit var monarchy: Monarchy
|
@Inject @SessionDatabase lateinit var monarchy: Monarchy
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
injector.inject(this)
|
||||||
?: return Result.failure()
|
}
|
||||||
.also { Timber.e("Unable to parse work parameters") }
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
|
|
||||||
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
val pusher = params.pusher
|
val pusher = params.pusher
|
||||||
|
|
||||||
if (pusher.pushKey.isBlank()) {
|
if (pusher.pushKey.isBlank()) {
|
||||||
|
@ -82,6 +77,10 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun setPusher(pusher: JsonPusher) {
|
private suspend fun setPusher(pusher: JsonPusher) {
|
||||||
executeRequest<Unit>(eventBus) {
|
executeRequest<Unit>(eventBus) {
|
||||||
apiCall = pushersAPI.setPusher(pusher)
|
apiCall = pushersAPI.setPusher(pusher)
|
||||||
|
|
|
@ -202,13 +202,13 @@ internal class DefaultRelationService @AssistedInject constructor(
|
||||||
|
|
||||||
private fun createEncryptEventWork(event: Event, keepKeys: List<String>?): OneTimeWorkRequest {
|
private fun createEncryptEventWork(event: Event, keepKeys: List<String>?): OneTimeWorkRequest {
|
||||||
// Same parameter
|
// Same parameter
|
||||||
val params = EncryptEventWorker.Params(sessionId, event, keepKeys)
|
val params = EncryptEventWorker.Params(sessionId, event.eventId!!, keepKeys)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||||
return timeLineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
|
return timeLineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
|
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = event.eventId!!)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
return timeLineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
return timeLineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.relation
|
package org.matrix.android.sdk.internal.session.room.relation
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
@ -27,45 +26,38 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
// TODO This is not used. Delete?
|
// TODO This is not used. Delete?
|
||||||
internal class SendRelationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
internal class SendRelationWorker(context: Context, params: WorkerParameters)
|
||||||
|
: SessionSafeCoroutineWorker<SendRelationWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val event: Event,
|
val eventId: String,
|
||||||
val relationType: String? = null,
|
val relationType: String? = null,
|
||||||
override val lastFailureMessage: String? = null
|
override val lastFailureMessage: String? = null
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject lateinit var roomAPI: RoomAPI
|
@Inject lateinit var roomAPI: RoomAPI
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
injector.inject(this)
|
||||||
?: return Result.failure()
|
}
|
||||||
.also { Timber.e("Unable to parse work parameters") }
|
|
||||||
|
|
||||||
if (params.lastFailureMessage != null) {
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
// Transmit the error
|
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId)
|
||||||
return Result.success(inputData)
|
if (localEvent?.eventId == null) {
|
||||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
|
||||||
}
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
|
|
||||||
val localEvent = params.event
|
|
||||||
if (localEvent.eventId == null) {
|
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
}
|
}
|
||||||
val relationContent = localEvent.content.toModel<ReactionContent>()
|
val relationContent = localEvent.content.toModel<ReactionContent>()
|
||||||
|
@ -88,6 +80,10 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) {
|
private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) {
|
||||||
executeRequest<SendResponse>(eventBus) {
|
executeRequest<SendResponse>(eventBus) {
|
||||||
apiCall = roomAPI.sendRelation(
|
apiCall = roomAPI.sendRelation(
|
||||||
|
|
|
@ -336,7 +336,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
|
|
||||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
// Same parameter
|
// Same parameter
|
||||||
return EncryptEventWorker.Params(sessionId, event)
|
return EncryptEventWorker.Params(sessionId, event.eventId ?: "")
|
||||||
.let { WorkerParamsFactory.toData(it) }
|
.let { WorkerParamsFactory.toData(it) }
|
||||||
.let {
|
.let {
|
||||||
workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||||
|
@ -360,7 +360,10 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
attachment: ContentAttachmentData,
|
attachment: ContentAttachmentData,
|
||||||
isRoomEncrypted: Boolean,
|
isRoomEncrypted: Boolean,
|
||||||
compressBeforeSending: Boolean): OneTimeWorkRequest {
|
compressBeforeSending: Boolean): OneTimeWorkRequest {
|
||||||
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, allLocalEchos, attachment, isRoomEncrypted, compressBeforeSending)
|
val localEchoIds = allLocalEchos.map {
|
||||||
|
LocalEchoIdentifiers(it.roomId!!, it.eventId!!)
|
||||||
|
}
|
||||||
|
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, localEchoIds, attachment, isRoomEncrypted, compressBeforeSending)
|
||||||
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||||
|
|
||||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
||||||
|
|
|
@ -18,21 +18,23 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.send
|
package org.matrix.android.sdk.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -41,12 +43,12 @@ import javax.inject.Inject
|
||||||
* Possible next worker : Always [SendEventWorker]
|
* Possible next worker : Always [SendEventWorker]
|
||||||
*/
|
*/
|
||||||
internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
: CoroutineWorker(context, params) {
|
: SessionSafeCoroutineWorker<EncryptEventWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
val event: Event,
|
val eventId: String,
|
||||||
/** Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to) */
|
/** Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to) */
|
||||||
val keepKeys: List<String>? = null,
|
val keepKeys: List<String>? = null,
|
||||||
override val lastFailureMessage: String? = null
|
override val lastFailureMessage: String? = null
|
||||||
|
@ -56,24 +58,15 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
Timber.v("Start Encrypt work")
|
injector.inject(this)
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
}
|
||||||
?: return Result.success()
|
|
||||||
.also { Timber.e("Unable to parse work parameters") }
|
|
||||||
|
|
||||||
Timber.v("## SendEvent: Start Encrypt work for event ${params.event.eventId}")
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
if (params.lastFailureMessage != null) {
|
Timber.v("## SendEvent: Start Encrypt work for event ${params.eventId}")
|
||||||
// Transmit the error
|
|
||||||
return Result.success(inputData)
|
|
||||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
|
||||||
}
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId)
|
||||||
sessionComponent.inject(this)
|
if (localEvent?.eventId == null) {
|
||||||
|
|
||||||
val localEvent = params.event
|
|
||||||
if (localEvent.eventId == null) {
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,15 +99,10 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
modifiedContent[toKeep] = it
|
modifiedContent[toKeep] = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val safeResult = result.copy(eventContent = modifiedContent)
|
|
||||||
val encryptedEvent = localEvent.copy(
|
|
||||||
type = safeResult.eventType,
|
|
||||||
content = safeResult.eventContent
|
|
||||||
)
|
|
||||||
// Better handling of local echo, to avoid decrypting transition on remote echo
|
// Better handling of local echo, to avoid decrypting transition on remote echo
|
||||||
// Should I only do it for text messages?
|
// Should I only do it for text messages?
|
||||||
if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||||
val decryptionLocalEcho = MXEventDecryptionResult(
|
MXEventDecryptionResult(
|
||||||
clearEvent = Event(
|
clearEvent = Event(
|
||||||
type = localEvent.type,
|
type = localEvent.type,
|
||||||
content = localEvent.content,
|
content = localEvent.content,
|
||||||
|
@ -124,10 +112,18 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||||
claimedEd25519Key = crypto.getMyDevice().fingerprint()
|
claimedEd25519Key = crypto.getMyDevice().fingerprint()
|
||||||
)
|
)
|
||||||
localEchoRepository.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho)
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
|
||||||
|
localEcho.type = EventType.ENCRYPTED
|
||||||
|
localEcho.content = ContentMapper.map(modifiedContent)
|
||||||
|
decryptionLocalEcho?.also {
|
||||||
|
localEcho.setDecryptionResult(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val nextWorkerParams = SendEventWorker.Params(sessionId = params.sessionId, event = encryptedEvent)
|
val nextWorkerParams = SendEventWorker.Params(sessionId = params.sessionId, eventId = params.eventId)
|
||||||
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
|
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
|
||||||
} else {
|
} else {
|
||||||
val sendState = when (error) {
|
val sendState = when (error) {
|
||||||
|
@ -138,10 +134,14 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||||
// always return success, or the chain will be stuck for ever!
|
// always return success, or the chain will be stuck for ever!
|
||||||
val nextWorkerParams = SendEventWorker.Params(
|
val nextWorkerParams = SendEventWorker.Params(
|
||||||
sessionId = params.sessionId,
|
sessionId = params.sessionId,
|
||||||
event = localEvent,
|
eventId = localEvent.eventId,
|
||||||
lastFailureMessage = error?.localizedMessage ?: "Error"
|
lastFailureMessage = error?.localizedMessage ?: "Error"
|
||||||
)
|
)
|
||||||
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
|
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 org.matrix.android.sdk.internal.session.room.send
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used as a holder to pass necessary data to some workers params.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class LocalEchoIdentifiers(val roomId: String, val eventId: String)
|
|
@ -18,8 +18,8 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.send
|
package org.matrix.android.sdk.internal.session.room.send
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.Realm
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
@ -27,11 +27,11 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||||
|
import org.matrix.android.sdk.internal.database.asyncTransaction
|
||||||
import org.matrix.android.sdk.internal.database.helper.nextId
|
import org.matrix.android.sdk.internal.database.helper.nextId
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
|
||||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
|
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
|
||||||
|
@ -44,11 +44,13 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline
|
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline
|
||||||
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
private val realmSessionProvider: RealmSessionProvider,
|
private val realmSessionProvider: RealmSessionProvider,
|
||||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||||
private val eventBus: EventBus,
|
private val eventBus: EventBus,
|
||||||
|
@ -76,12 +78,12 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
||||||
}
|
}
|
||||||
val timelineEvent = timelineEventMapper.map(timelineEventEntity)
|
val timelineEvent = timelineEventMapper.map(timelineEventEntity)
|
||||||
eventBus.post(DefaultTimeline.OnLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent))
|
eventBus.post(DefaultTimeline.OnLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent))
|
||||||
monarchy.writeAsync { realm ->
|
taskExecutor.executorScope.asyncTransaction(monarchy) { realm ->
|
||||||
val eventInsertEntity = EventInsertEntity(event.eventId, event.type).apply {
|
val eventInsertEntity = EventInsertEntity(event.eventId, event.type).apply {
|
||||||
this.insertType = EventInsertType.LOCAL_ECHO
|
this.insertType = EventInsertType.LOCAL_ECHO
|
||||||
}
|
}
|
||||||
realm.insert(eventInsertEntity)
|
realm.insert(eventInsertEntity)
|
||||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync
|
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@asyncTransaction
|
||||||
roomEntity.sendingTimelineEvents.add(0, timelineEventEntity)
|
roomEntity.sendingTimelineEvents.add(0, timelineEventEntity)
|
||||||
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||||
}
|
}
|
||||||
|
@ -89,30 +91,41 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
||||||
|
|
||||||
fun updateSendState(eventId: String, sendState: SendState) {
|
fun updateSendState(eventId: String, sendState: SendState) {
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
|
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
|
||||||
monarchy.writeAsync { realm ->
|
updateEchoAsync(eventId) { realm, sendingEventEntity ->
|
||||||
|
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
||||||
|
// If already synced, do not put as sent
|
||||||
|
} else {
|
||||||
|
sendingEventEntity.sendState = sendState
|
||||||
|
}
|
||||||
|
roomSummaryUpdater.updateSendingInformation(realm, sendingEventEntity.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateEcho(eventId: String, block: (realm: Realm, eventEntity: EventEntity) -> Unit) {
|
||||||
|
monarchy.awaitTransaction { realm ->
|
||||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||||
if (sendingEventEntity != null) {
|
if (sendingEventEntity != null) {
|
||||||
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
block(realm, sendingEventEntity)
|
||||||
// If already synced, do not put as sent
|
|
||||||
} else {
|
|
||||||
sendingEventEntity.sendState = sendState
|
|
||||||
}
|
|
||||||
roomSummaryUpdater.updateSendingInformation(realm, sendingEventEntity.roomId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) {
|
fun updateEchoAsync(eventId: String, block: (realm: Realm, eventEntity: EventEntity) -> Unit) {
|
||||||
monarchy.writeAsync { realm ->
|
taskExecutor.executorScope.asyncTransaction(monarchy) { realm ->
|
||||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||||
if (sendingEventEntity != null) {
|
if (sendingEventEntity != null) {
|
||||||
sendingEventEntity.type = EventType.ENCRYPTED
|
block(realm, sendingEventEntity)
|
||||||
sendingEventEntity.content = ContentMapper.map(encryptedContent)
|
|
||||||
sendingEventEntity.setDecryptionResult(mxEventDecryptionResult)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getUpToDateEcho(eventId: String): Event? {
|
||||||
|
// We are using awaitTransaction here to make sure this executes after other transactions
|
||||||
|
return monarchy.awaitTransaction { realm ->
|
||||||
|
EventEntity.where(realm, eventId).findFirst()?.asDomain(castJsonNumbers = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun deleteFailedEcho(roomId: String, localEcho: TimelineEvent) {
|
suspend fun deleteFailedEcho(roomId: String, localEcho: TimelineEvent) {
|
||||||
deleteFailedEcho(roomId, localEcho.eventId)
|
deleteFailedEcho(roomId, localEcho.eventId)
|
||||||
}
|
}
|
||||||
|
@ -150,7 +163,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
||||||
return getAllEventsWithStates(roomId, SendState.HAS_FAILED_STATES)
|
return getAllEventsWithStates(roomId, SendState.HAS_FAILED_STATES)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllEventsWithStates(roomId: String, states : List<SendState>): List<TimelineEvent> {
|
fun getAllEventsWithStates(roomId: String, states: List<SendState>): List<TimelineEvent> {
|
||||||
return realmSessionProvider.withRealm { realm ->
|
return realmSessionProvider.withRealm { realm ->
|
||||||
TimelineEventEntity
|
TimelineEventEntity
|
||||||
.findAllInRoomWithSendStates(realm, roomId, states)
|
.findAllInRoomWithSendStates(realm, roomId, states)
|
||||||
|
|
|
@ -19,18 +19,17 @@ package org.matrix.android.sdk.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineSendEventWorkCommon
|
import org.matrix.android.sdk.internal.session.room.timeline.TimelineSendEventWorkCommon
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import org.matrix.android.sdk.internal.worker.startChain
|
import org.matrix.android.sdk.internal.worker.startChain
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -43,12 +42,12 @@ import javax.inject.Inject
|
||||||
* Possible next worker : None, but it will post new work to send events, encrypted or not
|
* Possible next worker : None, but it will post new work to send events, encrypted or not
|
||||||
*/
|
*/
|
||||||
internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters)
|
internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters)
|
||||||
: CoroutineWorker(context, params) {
|
: SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
val events: List<Event>,
|
val localEchoIds: List<LocalEchoIdentifiers>,
|
||||||
val isEncrypted: Boolean,
|
val isEncrypted: Boolean,
|
||||||
override val lastFailureMessage: String? = null
|
override val lastFailureMessage: String? = null
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
@ -57,46 +56,48 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
||||||
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
|
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
|
||||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun doOnError(params: Params): Result {
|
||||||
Timber.v("## SendEvent: Start dispatch sending multiple event work")
|
params.localEchoIds.forEach { localEchoIds ->
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
localEchoRepository.updateSendState(localEchoIds.eventId, SendState.UNDELIVERED)
|
||||||
?: return Result.success()
|
|
||||||
.also { Timber.e("Unable to parse work parameters") }
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
|
|
||||||
if (params.lastFailureMessage != null) {
|
|
||||||
params.events.forEach { event ->
|
|
||||||
event.eventId?.let { localEchoRepository.updateSendState(it, SendState.UNDELIVERED) }
|
|
||||||
}
|
|
||||||
// Transmit the error if needed?
|
|
||||||
return Result.success(inputData)
|
|
||||||
.also { Timber.e("## SendEvent: Work cancelled due to input error from parent ${params.lastFailureMessage}") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return super.doOnError(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun injectWith(injector: SessionComponent) {
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
|
Timber.v("## SendEvent: Start dispatch sending multiple event work")
|
||||||
// Create a work for every event
|
// Create a work for every event
|
||||||
params.events.forEach { event ->
|
params.localEchoIds.forEach { localEchoIds ->
|
||||||
|
val roomId = localEchoIds.roomId
|
||||||
|
val eventId = localEchoIds.eventId
|
||||||
if (params.isEncrypted) {
|
if (params.isEncrypted) {
|
||||||
localEchoRepository.updateSendState(event.eventId ?: "", SendState.ENCRYPTING)
|
localEchoRepository.updateSendState(eventId, SendState.ENCRYPTING)
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule encrypt and send event ${event.eventId}")
|
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule encrypt and send event $eventId")
|
||||||
val encryptWork = createEncryptEventWork(params.sessionId, event, true)
|
val encryptWork = createEncryptEventWork(params.sessionId, eventId, true)
|
||||||
// Note that event will be replaced by the result of the previous work
|
// Note that event will be replaced by the result of the previous work
|
||||||
val sendWork = createSendEventWork(params.sessionId, event, false)
|
val sendWork = createSendEventWork(params.sessionId, eventId, false)
|
||||||
timelineSendEventWorkCommon.postSequentialWorks(event.roomId!!, encryptWork, sendWork)
|
timelineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, sendWork)
|
||||||
} else {
|
} else {
|
||||||
localEchoRepository.updateSendState(event.eventId ?: "", SendState.SENDING)
|
localEchoRepository.updateSendState(eventId, SendState.SENDING)
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event ${event.eventId}")
|
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId")
|
||||||
val sendWork = createSendEventWork(params.sessionId, event, true)
|
val sendWork = createSendEventWork(params.sessionId, eventId, true)
|
||||||
timelineSendEventWorkCommon.postWork(event.roomId!!, sendWork)
|
timelineSendEventWorkCommon.postWork(roomId, sendWork)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEncryptEventWork(sessionId: String, event: Event, startChain: Boolean): OneTimeWorkRequest {
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
val params = EncryptEventWorker.Params(sessionId, event)
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEncryptEventWork(sessionId: String, eventId: String, startChain: Boolean): OneTimeWorkRequest {
|
||||||
|
val params = EncryptEventWorker.Params(sessionId, eventId)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||||
|
|
||||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||||
|
@ -107,8 +108,8 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSendEventWork(sessionId: String, event: Event, startChain: Boolean): OneTimeWorkRequest {
|
private fun createSendEventWork(sessionId: String, eventId: String, startChain: Boolean): OneTimeWorkRequest {
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
|
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = eventId)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
|
||||||
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||||
|
|
|
@ -17,24 +17,24 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.send
|
package org.matrix.android.sdk.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possible previous worker: None
|
* Possible previous worker: None
|
||||||
* Possible next worker : None
|
* Possible next worker : None
|
||||||
*/
|
*/
|
||||||
internal class RedactEventWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
internal class RedactEventWorker(context: Context, params: WorkerParameters)
|
||||||
|
: SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
|
@ -49,20 +49,11 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
|
||||||
@Inject lateinit var roomAPI: RoomAPI
|
@Inject lateinit var roomAPI: RoomAPI
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
injector.inject(this)
|
||||||
?: return Result.failure()
|
}
|
||||||
.also { Timber.e("Unable to parse work parameters") }
|
|
||||||
|
|
||||||
if (params.lastFailureMessage != null) {
|
|
||||||
// Transmit the error
|
|
||||||
return Result.success(inputData)
|
|
||||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
|
||||||
}
|
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
|
|
||||||
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
val eventId = params.eventId
|
val eventId = params.eventId
|
||||||
return runCatching {
|
return runCatching {
|
||||||
executeRequest<SendResponse>(eventBus) {
|
executeRequest<SendResponse>(eventBus) {
|
||||||
|
@ -91,4 +82,8 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ internal class RoomEventSender @Inject constructor(
|
||||||
|
|
||||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
// Same parameter
|
// Same parameter
|
||||||
val params = EncryptEventWorker.Params(sessionId, event)
|
val params = EncryptEventWorker.Params(sessionId, event.eventId!!)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||||
|
|
||||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||||
|
@ -68,7 +68,7 @@ internal class RoomEventSender @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
|
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = event.eventId!!)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
|
||||||
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||||
|
|
|
@ -18,19 +18,19 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.send
|
package org.matrix.android.sdk.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -42,35 +42,29 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
internal class SendEventWorker(context: Context,
|
internal class SendEventWorker(context: Context,
|
||||||
params: WorkerParameters)
|
params: WorkerParameters)
|
||||||
: CoroutineWorker(context, params) {
|
: SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
override val lastFailureMessage: String? = null,
|
override val lastFailureMessage: String? = null,
|
||||||
val event: Event? = null,
|
val eventId: String
|
||||||
// Keep for compat at the moment, will be removed later
|
|
||||||
val eventId: String? = null
|
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
@Inject lateinit var roomAPI: RoomAPI
|
@Inject lateinit var roomAPI: RoomAPI
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||||
|
@SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
injector.inject(this)
|
||||||
?: return Result.success()
|
}
|
||||||
.also { Timber.e("## SendEvent: Unable to parse work parameters") }
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
|
|
||||||
val event = params.event
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
|
val event = localEchoRepository.getUpToDateEcho(params.eventId)
|
||||||
if (event?.eventId == null || event.roomId == null) {
|
if (event?.eventId == null || event.roomId == null) {
|
||||||
// Old way of sending
|
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
||||||
if (params.eventId != null) {
|
|
||||||
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
|
||||||
}
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
.also { Timber.e("Work cancelled due to bad input data") }
|
.also { Timber.e("Work cancelled due to bad input data") }
|
||||||
}
|
}
|
||||||
|
@ -106,6 +100,10 @@ internal class SendEventWorker(context: Context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun sendEvent(eventId: String, roomId: String, type: String, content: Content?) {
|
private suspend fun sendEvent(eventId: String, roomId: String, type: String, content: Content?) {
|
||||||
localEchoRepository.updateSendState(eventId, SendState.SENDING)
|
localEchoRepository.updateSendState(eventId, SendState.SENDING)
|
||||||
executeRequest<SendResponse>(eventBus) {
|
executeRequest<SendResponse>(eventBus) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
@ -333,12 +334,22 @@ internal class DefaultTimeline(
|
||||||
|
|
||||||
// Private methods *****************************************************************************
|
// Private methods *****************************************************************************
|
||||||
|
|
||||||
private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent): Boolean {
|
private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean {
|
||||||
return builtEventsIdMap[eventId]?.let { builtIndex ->
|
return tryOrNull {
|
||||||
// Update the relation of existing event
|
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
builtEvents[builtIndex]?.let { te ->
|
// Update the relation of existing event
|
||||||
builtEvents[builtIndex] = builder(te)
|
builtEvents[builtIndex]?.let { te ->
|
||||||
true
|
val rebuiltEvent = builder(te)
|
||||||
|
// If rebuilt event is filtered its returned as null and should be removed.
|
||||||
|
if (rebuiltEvent == null) {
|
||||||
|
builtEventsIdMap.remove(eventId)
|
||||||
|
builtEventsIdMap.entries.filter { it.value > builtIndex }.forEach { it.setValue(it.value - 1) }
|
||||||
|
builtEvents.removeAt(builtIndex)
|
||||||
|
} else {
|
||||||
|
builtEvents[builtIndex] = rebuiltEvent
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
@ -413,14 +424,14 @@ internal class DefaultTimeline(
|
||||||
|
|
||||||
private fun getState(direction: Timeline.Direction): State {
|
private fun getState(direction: Timeline.Direction): State {
|
||||||
return when (direction) {
|
return when (direction) {
|
||||||
Timeline.Direction.FORWARDS -> forwardsState.get()
|
Timeline.Direction.FORWARDS -> forwardsState.get()
|
||||||
Timeline.Direction.BACKWARDS -> backwardsState.get()
|
Timeline.Direction.BACKWARDS -> backwardsState.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateState(direction: Timeline.Direction, update: (State) -> State) {
|
private fun updateState(direction: Timeline.Direction, update: (State) -> State) {
|
||||||
val stateReference = when (direction) {
|
val stateReference = when (direction) {
|
||||||
Timeline.Direction.FORWARDS -> forwardsState
|
Timeline.Direction.FORWARDS -> forwardsState
|
||||||
Timeline.Direction.BACKWARDS -> backwardsState
|
Timeline.Direction.BACKWARDS -> backwardsState
|
||||||
}
|
}
|
||||||
val currentValue = stateReference.get()
|
val currentValue = stateReference.get()
|
||||||
|
@ -489,7 +500,8 @@ internal class DefaultTimeline(
|
||||||
val eventEntity = results[index]
|
val eventEntity = results[index]
|
||||||
eventEntity?.eventId?.let { eventId ->
|
eventEntity?.eventId?.let { eventId ->
|
||||||
postSnapshot = rebuildEvent(eventId) {
|
postSnapshot = rebuildEvent(eventId) {
|
||||||
buildTimelineEvent(eventEntity)
|
val builtEvent = buildTimelineEvent(eventEntity)
|
||||||
|
listOf(builtEvent).filterEventsWithSettings().firstOrNull()
|
||||||
} || postSnapshot
|
} || postSnapshot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -730,10 +742,10 @@ internal class DefaultTimeline(
|
||||||
return object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
return object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
||||||
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
||||||
when (data) {
|
when (data) {
|
||||||
TokenChunkEventPersistor.Result.SUCCESS -> {
|
TokenChunkEventPersistor.Result.SUCCESS -> {
|
||||||
Timber.v("Success fetching $limit items $direction from pagination request")
|
Timber.v("Success fetching $limit items $direction from pagination request")
|
||||||
}
|
}
|
||||||
TokenChunkEventPersistor.Result.REACHED_END -> {
|
TokenChunkEventPersistor.Result.REACHED_END -> {
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
||||||
|
@ -775,9 +787,8 @@ internal class DefaultTimeline(
|
||||||
}
|
}
|
||||||
if (!filterEdits) return@filter false
|
if (!filterEdits) return@filter false
|
||||||
|
|
||||||
val filterRedacted = !settings.filters.filterRedacted || it.root.isRedacted()
|
val filterRedacted = settings.filters.filterRedacted && it.root.isRedacted()
|
||||||
|
!filterRedacted
|
||||||
filterRedacted
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* At the moment we don't get any group data through the sync, so we poll where every hour.
|
* At the moment we don't get any group data through the sync, so we poll where every hour.
|
||||||
You can also force to refetch group data using [Group] API.
|
* You can also force to refetch group data using [Group] API.
|
||||||
*/
|
*/
|
||||||
private fun scheduleGroupDataFetchingIfNeeded(groupsSyncResponse: GroupsSyncResponse) {
|
private fun scheduleGroupDataFetchingIfNeeded(groupsSyncResponse: GroupsSyncResponse) {
|
||||||
val groupIds = ArrayList<String>()
|
val groupIds = ArrayList<String>()
|
||||||
|
|
|
@ -18,18 +18,18 @@ package org.matrix.android.sdk.internal.session.sync.job
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.failure.isTokenError
|
import org.matrix.android.sdk.api.failure.isTokenError
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
|
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.session.sync.SyncTask
|
import org.matrix.android.sdk.internal.session.sync.SyncTask
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -43,7 +43,7 @@ private const val DEFAULT_DELAY_TIMEOUT = 30_000L
|
||||||
*/
|
*/
|
||||||
internal class SyncWorker(context: Context,
|
internal class SyncWorker(context: Context,
|
||||||
workerParameters: WorkerParameters
|
workerParameters: WorkerParameters
|
||||||
) : CoroutineWorker(context, workerParameters) {
|
) : SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, Params::class.java) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
|
@ -59,14 +59,13 @@ internal class SyncWorker(context: Context,
|
||||||
@Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker
|
@Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker
|
||||||
@Inject lateinit var workManagerProvider: WorkManagerProvider
|
@Inject lateinit var workManagerProvider: WorkManagerProvider
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override fun injectWith(injector: SessionComponent) {
|
||||||
Timber.i("Sync work starting")
|
injector.inject(this)
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
}
|
||||||
?: return Result.success()
|
|
||||||
.also { Timber.e("Unable to parse work parameters") }
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
|
Timber.i("Sync work starting")
|
||||||
|
|
||||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
|
||||||
sessionComponent.inject(this)
|
|
||||||
return runCatching {
|
return runCatching {
|
||||||
doSync(params.timeout)
|
doSync(params.timeout)
|
||||||
}.fold(
|
}.fold(
|
||||||
|
@ -91,6 +90,10 @@ internal class SyncWorker(context: Context,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun doSync(timeout: Long) {
|
private suspend fun doSync(timeout: Long) {
|
||||||
val taskParams = SyncTask.Params(timeout * 1000)
|
val taskParams = SyncTask.Params(timeout * 1000)
|
||||||
syncTask.execute(taskParams)
|
syncTask.execute(taskParams)
|
||||||
|
|
|
@ -202,6 +202,6 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
|
||||||
stateKey = QueryStringValue.NoCondition
|
stateKey = QueryStringValue.NoCondition
|
||||||
)
|
)
|
||||||
val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>() ?: return false
|
val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>() ?: return false
|
||||||
return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, null)
|
return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, EventType.STATE_ROOM_WIDGET_LEGACY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
package org.matrix.android.sdk.internal.util
|
package org.matrix.android.sdk.internal.util
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.internal.database.awaitTransaction
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmModel
|
import io.realm.RealmModel
|
||||||
|
import org.matrix.android.sdk.internal.database.awaitTransaction
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
internal suspend fun <T> Monarchy.awaitTransaction(transaction: suspend (realm: Realm) -> T): T {
|
internal suspend fun <T> Monarchy.awaitTransaction(transaction: suspend (realm: Realm) -> T): T {
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 org.matrix.android.sdk.internal.worker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.Data
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This worker should only sends Result.Success when added to a unique queue to avoid breaking the unique queue.
|
||||||
|
* This abstract class handle the cases of problem when parsing parameter, and forward the error if any to
|
||||||
|
* the next workers.
|
||||||
|
*/
|
||||||
|
internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>(
|
||||||
|
context: Context,
|
||||||
|
workerParameters: WorkerParameters,
|
||||||
|
private val paramClass: Class<PARAM>
|
||||||
|
) : CoroutineWorker(context, workerParameters) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class ErrorData(
|
||||||
|
override val sessionId: String,
|
||||||
|
override val lastFailureMessage: String? = null
|
||||||
|
) : SessionWorkerParams
|
||||||
|
|
||||||
|
final override suspend fun doWork(): Result {
|
||||||
|
val params = WorkerParamsFactory.fromData(paramClass, inputData)
|
||||||
|
?: return buildErrorResult(null, "Unable to parse work parameters")
|
||||||
|
.also { Timber.e("Unable to parse work parameters") }
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val sessionComponent = getSessionComponent(params.sessionId)
|
||||||
|
?: return buildErrorResult(params, "No session")
|
||||||
|
|
||||||
|
// Make sure to inject before handling error as you may need some dependencies to process them.
|
||||||
|
injectWith(sessionComponent)
|
||||||
|
if (params.lastFailureMessage != null) {
|
||||||
|
// Forward error to the next workers
|
||||||
|
doOnError(params)
|
||||||
|
} else {
|
||||||
|
doSafeWork(params)
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun injectWith(injector: SessionComponent)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should only return Result.Success for workers added to a unique queue
|
||||||
|
*/
|
||||||
|
abstract suspend fun doSafeWork(params: PARAM): Result
|
||||||
|
|
||||||
|
protected fun buildErrorResult(params: PARAM?, message: String): Result {
|
||||||
|
return Result.success(
|
||||||
|
if (params != null) {
|
||||||
|
WorkerParamsFactory.toData(paramClass, buildErrorParams(params, message))
|
||||||
|
} else {
|
||||||
|
WorkerParamsFactory.toData(ErrorData::class.java, ErrorData(sessionId = "", lastFailureMessage = message))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun buildErrorParams(params: PARAM, message: String): PARAM
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called when the input parameters are correct, but contain an error from the previous worker.
|
||||||
|
*/
|
||||||
|
@CallSuper
|
||||||
|
open fun doOnError(params: PARAM): Result {
|
||||||
|
// Forward the error
|
||||||
|
return Result.success(inputData)
|
||||||
|
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun hasFailed(outputData: Data): Boolean {
|
||||||
|
return WorkerParamsFactory.fromData(ErrorData::class.java, outputData)
|
||||||
|
.let { it?.lastFailureMessage != null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,12 +18,14 @@
|
||||||
package org.matrix.android.sdk.internal.worker
|
package org.matrix.android.sdk.internal.worker
|
||||||
|
|
||||||
import androidx.work.Data
|
import androidx.work.Data
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||||
|
|
||||||
internal object WorkerParamsFactory {
|
internal object WorkerParamsFactory {
|
||||||
|
|
||||||
val moshi by lazy {
|
private val moshi: Moshi by lazy {
|
||||||
// We are adding the CheckNumberType as we are serializing/deserializing multiple time in a row
|
// We are adding the CheckNumberType as we are serializing/deserializing multiple time in a row
|
||||||
// and we lost typing information doing so.
|
// and we lost typing information doing so.
|
||||||
// We don't want this check to be done on all adapters, so we just add it here.
|
// We don't want this check to be done on all adapters, so we just add it here.
|
||||||
|
@ -33,20 +35,24 @@ internal object WorkerParamsFactory {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
const val KEY = "WORKER_PARAMS_JSON"
|
private const val KEY = "WORKER_PARAMS_JSON"
|
||||||
|
|
||||||
inline fun <reified T> toData(params: T): Data {
|
inline fun <reified T> toData(params: T) = toData(T::class.java, params)
|
||||||
val adapter = moshi.adapter(T::class.java)
|
|
||||||
|
fun <T> toData(clazz: Class<T>, params: T): Data {
|
||||||
|
val adapter = moshi.adapter(clazz)
|
||||||
val json = adapter.toJson(params)
|
val json = adapter.toJson(params)
|
||||||
return Data.Builder().putString(KEY, json).build()
|
return Data.Builder().putString(KEY, json).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> fromData(data: Data): T? {
|
inline fun <reified T> fromData(data: Data) = fromData(T::class.java, data)
|
||||||
|
|
||||||
|
fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull("Unable to parse work parameters") {
|
||||||
val json = data.getString(KEY)
|
val json = data.getString(KEY)
|
||||||
return if (json == null) {
|
return if (json == null) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
val adapter = moshi.adapter(T::class.java)
|
val adapter = moshi.adapter(clazz)
|
||||||
adapter.fromJson(json)
|
adapter.fromJson(json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,21 +151,21 @@
|
||||||
<string name="notice_power_level_changed">%1$s alterou o nível de permissão de %2$s.</string>
|
<string name="notice_power_level_changed">%1$s alterou o nível de permissão de %2$s.</string>
|
||||||
<string name="notice_power_level_diff">%1$s de %2$s para %3$s</string>
|
<string name="notice_power_level_diff">%1$s de %2$s para %3$s</string>
|
||||||
|
|
||||||
<string name="initial_sync_start_importing_account">Primeira sincronização:↵
|
<string name="initial_sync_start_importing_account">Primeira sincronização:
|
||||||
\nImportando a conta…</string>
|
\nImportando a conta…</string>
|
||||||
<string name="initial_sync_start_importing_account_crypto">Primeira sincronização:↵
|
<string name="initial_sync_start_importing_account_crypto">Primeira sincronização:
|
||||||
\nImportando as chaves de criptografia</string>
|
\nImportando as chaves de criptografia</string>
|
||||||
<string name="initial_sync_start_importing_account_rooms">Primeira sincronização:↵
|
<string name="initial_sync_start_importing_account_rooms">Primeira sincronização:
|
||||||
\nImportando as salas</string>
|
\nImportando as salas</string>
|
||||||
<string name="initial_sync_start_importing_account_joined_rooms">Primeira sincronização:↵
|
<string name="initial_sync_start_importing_account_joined_rooms">Primeira sincronização:
|
||||||
\nImportando as salas em que você entrou</string>
|
\nImportando as salas em que você entrou</string>
|
||||||
<string name="initial_sync_start_importing_account_invited_rooms">Primeira sincronização:↵
|
<string name="initial_sync_start_importing_account_invited_rooms">Primeira sincronização:
|
||||||
\nImportando as salas em que você foi convidado</string>
|
\nImportando as salas em que você foi convidado</string>
|
||||||
<string name="initial_sync_start_importing_account_left_rooms">Primeira sincronização:↵
|
<string name="initial_sync_start_importing_account_left_rooms">Primeira sincronização:
|
||||||
\nImportando as salas em que você saiu</string>
|
\nImportando as salas em que você saiu</string>
|
||||||
<string name="initial_sync_start_importing_account_groups">Primeira sincronização:↵
|
<string name="initial_sync_start_importing_account_groups">Primeira sincronização:
|
||||||
\nImportando as comunidades</string>
|
\nImportando as comunidades</string>
|
||||||
<string name="initial_sync_start_importing_account_data">Primeira sincronização:↵
|
<string name="initial_sync_start_importing_account_data">Primeira sincronização:
|
||||||
\nImportando os dados da conta</string>
|
\nImportando os dados da conta</string>
|
||||||
|
|
||||||
<string name="event_status_sending_message">Enviando mensagem…</string>
|
<string name="event_status_sending_message">Enviando mensagem…</string>
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Generated file, do not edit -->
|
||||||
|
<string name="verification_emoji_dog">Cachorro</string>
|
||||||
|
<string name="verification_emoji_cat">Gato</string>
|
||||||
|
<string name="verification_emoji_lion">Leão</string>
|
||||||
|
<string name="verification_emoji_horse">Cavalo</string>
|
||||||
|
<string name="verification_emoji_unicorn">Unicórnio</string>
|
||||||
|
<string name="verification_emoji_pig">Porco</string>
|
||||||
|
<string name="verification_emoji_elephant">Elefante</string>
|
||||||
|
<string name="verification_emoji_rabbit">Coelho</string>
|
||||||
|
<string name="verification_emoji_panda">Panda</string>
|
||||||
|
<string name="verification_emoji_rooster">Galo</string>
|
||||||
|
<string name="verification_emoji_penguin">Pinguim</string>
|
||||||
|
<string name="verification_emoji_turtle">Tartaruga</string>
|
||||||
|
<string name="verification_emoji_fish">Peixe</string>
|
||||||
|
<string name="verification_emoji_octopus">Polvo</string>
|
||||||
|
<string name="verification_emoji_butterfly">Borboleta</string>
|
||||||
|
<string name="verification_emoji_flower">Flor</string>
|
||||||
|
<string name="verification_emoji_tree">Árvore</string>
|
||||||
|
<string name="verification_emoji_cactus">Cacto</string>
|
||||||
|
<string name="verification_emoji_mushroom">Cogumelo</string>
|
||||||
|
<string name="verification_emoji_globe">Globo</string>
|
||||||
|
<string name="verification_emoji_moon">Lua</string>
|
||||||
|
<string name="verification_emoji_cloud">Nuvem</string>
|
||||||
|
<string name="verification_emoji_fire">Fogo</string>
|
||||||
|
<string name="verification_emoji_banana">Banana</string>
|
||||||
|
<string name="verification_emoji_apple">Maçã</string>
|
||||||
|
<string name="verification_emoji_strawberry">Morango</string>
|
||||||
|
<string name="verification_emoji_corn">Milho</string>
|
||||||
|
<string name="verification_emoji_pizza">Pizza</string>
|
||||||
|
<string name="verification_emoji_cake">Bolo</string>
|
||||||
|
<string name="verification_emoji_heart">Coração</string>
|
||||||
|
<string name="verification_emoji_smiley">Sorriso</string>
|
||||||
|
<string name="verification_emoji_robot">Robô</string>
|
||||||
|
<string name="verification_emoji_hat">Chapéu</string>
|
||||||
|
<string name="verification_emoji_glasses">Óculos</string>
|
||||||
|
<string name="verification_emoji_spanner">Chave inglesa</string>
|
||||||
|
<string name="verification_emoji_santa">Papai-noel</string>
|
||||||
|
<string name="verification_emoji_thumbs_up">Joinha</string>
|
||||||
|
<string name="verification_emoji_umbrella">Guarda-chuva</string>
|
||||||
|
<string name="verification_emoji_hourglass">Ampulheta</string>
|
||||||
|
<string name="verification_emoji_clock">Relógio</string>
|
||||||
|
<string name="verification_emoji_gift">Presente</string>
|
||||||
|
<string name="verification_emoji_light_bulb">Lâmpada</string>
|
||||||
|
<string name="verification_emoji_book">Livro</string>
|
||||||
|
<string name="verification_emoji_pencil">Lápis</string>
|
||||||
|
<string name="verification_emoji_paperclip">Clipe de papel</string>
|
||||||
|
<string name="verification_emoji_scissors">Tesoura</string>
|
||||||
|
<string name="verification_emoji_lock">Cadeado</string>
|
||||||
|
<string name="verification_emoji_key">Chave</string>
|
||||||
|
<string name="verification_emoji_hammer">Martelo</string>
|
||||||
|
<string name="verification_emoji_telephone">Telefone</string>
|
||||||
|
<string name="verification_emoji_flag">Bandeira</string>
|
||||||
|
<string name="verification_emoji_train">Trem</string>
|
||||||
|
<string name="verification_emoji_bicycle">Bicicleta</string>
|
||||||
|
<string name="verification_emoji_aeroplane">Avião</string>
|
||||||
|
<string name="verification_emoji_rocket">Foguete</string>
|
||||||
|
<string name="verification_emoji_trophy">Troféu</string>
|
||||||
|
<string name="verification_emoji_ball">Bola</string>
|
||||||
|
<string name="verification_emoji_guitar">Guitarra</string>
|
||||||
|
<string name="verification_emoji_trumpet">Trombeta</string>
|
||||||
|
<string name="verification_emoji_bell">Sino</string>
|
||||||
|
<string name="verification_emoji_anchor">Âncora</string>
|
||||||
|
<string name="verification_emoji_headphones">Fones de ouvido</string>
|
||||||
|
<string name="verification_emoji_folder">Pasta</string>
|
||||||
|
<string name="verification_emoji_pin">Alfinete</string>
|
||||||
|
</resources>
|
|
@ -27,8 +27,8 @@
|
||||||
<string name="notice_room_ban_by_you">Du bannade %1$s</string>
|
<string name="notice_room_ban_by_you">Du bannade %1$s</string>
|
||||||
<string name="notice_room_withdraw">%1$s drog tillbaka inbjudan för %2$s</string>
|
<string name="notice_room_withdraw">%1$s drog tillbaka inbjudan för %2$s</string>
|
||||||
<string name="notice_room_withdraw_by_you">Du drog tillbaka inbjudan för %1$s</string>
|
<string name="notice_room_withdraw_by_you">Du drog tillbaka inbjudan för %1$s</string>
|
||||||
<string name="notice_avatar_url_changed">%1$s ändrade sin avatar</string>
|
<string name="notice_avatar_url_changed">%1$s bytte sin avatar</string>
|
||||||
<string name="notice_avatar_url_changed_by_you">Du ändrade din avatar</string>
|
<string name="notice_avatar_url_changed_by_you">Du bytte din avatar</string>
|
||||||
<string name="notice_display_name_set">%1$s satte sitt visningsnamn till %2$s</string>
|
<string name="notice_display_name_set">%1$s satte sitt visningsnamn till %2$s</string>
|
||||||
<string name="notice_display_name_set_by_you">Du satte ditt visningsnamn till %1$s</string>
|
<string name="notice_display_name_set_by_you">Du satte ditt visningsnamn till %1$s</string>
|
||||||
<string name="notice_display_name_changed_from">%1$s bytte sitt visningsnamn från %2$s till %3$s</string>
|
<string name="notice_display_name_changed_from">%1$s bytte sitt visningsnamn från %2$s till %3$s</string>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<string name="verification_emoji_banana">Banan</string>
|
<string name="verification_emoji_banana">Banan</string>
|
||||||
<string name="verification_emoji_apple">Äpple</string>
|
<string name="verification_emoji_apple">Äpple</string>
|
||||||
<string name="verification_emoji_strawberry">Jordgubbe</string>
|
<string name="verification_emoji_strawberry">Jordgubbe</string>
|
||||||
<string name="verification_emoji_corn">Majskolv</string>
|
<string name="verification_emoji_corn">Majs</string>
|
||||||
<string name="verification_emoji_pizza">Pizza</string>
|
<string name="verification_emoji_pizza">Pizza</string>
|
||||||
<string name="verification_emoji_cake">Tårta</string>
|
<string name="verification_emoji_cake">Tårta</string>
|
||||||
<string name="verification_emoji_heart">Hjärta</string>
|
<string name="verification_emoji_heart">Hjärta</string>
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
<string name="verification_emoji_umbrella">Paraply</string>
|
<string name="verification_emoji_umbrella">Paraply</string>
|
||||||
<string name="verification_emoji_hourglass">Timglas</string>
|
<string name="verification_emoji_hourglass">Timglas</string>
|
||||||
<string name="verification_emoji_clock">Klocka</string>
|
<string name="verification_emoji_clock">Klocka</string>
|
||||||
<string name="verification_emoji_gift">Paket</string>
|
<string name="verification_emoji_gift">Present</string>
|
||||||
<string name="verification_emoji_light_bulb">Lampa</string>
|
<string name="verification_emoji_light_bulb">Lampa</string>
|
||||||
<string name="verification_emoji_book">Bok</string>
|
<string name="verification_emoji_book">Bok</string>
|
||||||
<string name="verification_emoji_pencil">Penna</string>
|
<string name="verification_emoji_pencil">Penna</string>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
<string name="verification_emoji_hammer">Hammare</string>
|
<string name="verification_emoji_hammer">Hammare</string>
|
||||||
<string name="verification_emoji_telephone">Telefon</string>
|
<string name="verification_emoji_telephone">Telefon</string>
|
||||||
<string name="verification_emoji_flag">Flagga</string>
|
<string name="verification_emoji_flag">Flagga</string>
|
||||||
<string name="verification_emoji_train">Ånglok</string>
|
<string name="verification_emoji_train">Tåg</string>
|
||||||
<string name="verification_emoji_bicycle">Cykel</string>
|
<string name="verification_emoji_bicycle">Cykel</string>
|
||||||
<string name="verification_emoji_aeroplane">Flygplan</string>
|
<string name="verification_emoji_aeroplane">Flygplan</string>
|
||||||
<string name="verification_emoji_rocket">Raket</string>
|
<string name="verification_emoji_rocket">Raket</string>
|
||||||
|
|
|
@ -7,4 +7,62 @@
|
||||||
<string name="verification_emoji_horse">马</string>
|
<string name="verification_emoji_horse">马</string>
|
||||||
<string name="verification_emoji_unicorn">独角兽</string>
|
<string name="verification_emoji_unicorn">独角兽</string>
|
||||||
<string name="verification_emoji_pig">猪</string>
|
<string name="verification_emoji_pig">猪</string>
|
||||||
|
<string name="verification_emoji_elephant">大象</string>
|
||||||
|
<string name="verification_emoji_rabbit">兔子</string>
|
||||||
|
<string name="verification_emoji_panda">熊猫</string>
|
||||||
|
<string name="verification_emoji_rooster">公鸡</string>
|
||||||
|
<string name="verification_emoji_penguin">企鹅</string>
|
||||||
|
<string name="verification_emoji_turtle">乌龟</string>
|
||||||
|
<string name="verification_emoji_fish">鱼</string>
|
||||||
|
<string name="verification_emoji_octopus">章鱼</string>
|
||||||
|
<string name="verification_emoji_butterfly">蝴蝶</string>
|
||||||
|
<string name="verification_emoji_flower">花</string>
|
||||||
|
<string name="verification_emoji_tree">树</string>
|
||||||
|
<string name="verification_emoji_cactus">仙人掌</string>
|
||||||
|
<string name="verification_emoji_mushroom">蘑菇</string>
|
||||||
|
<string name="verification_emoji_globe">地球</string>
|
||||||
|
<string name="verification_emoji_moon">月亮</string>
|
||||||
|
<string name="verification_emoji_cloud">云</string>
|
||||||
|
<string name="verification_emoji_fire">火</string>
|
||||||
|
<string name="verification_emoji_banana">香蕉</string>
|
||||||
|
<string name="verification_emoji_apple">苹果</string>
|
||||||
|
<string name="verification_emoji_strawberry">草莓</string>
|
||||||
|
<string name="verification_emoji_corn">玉米</string>
|
||||||
|
<string name="verification_emoji_pizza">披萨</string>
|
||||||
|
<string name="verification_emoji_cake">蛋糕</string>
|
||||||
|
<string name="verification_emoji_heart">心</string>
|
||||||
|
<string name="verification_emoji_smiley">笑脸</string>
|
||||||
|
<string name="verification_emoji_robot">机器人</string>
|
||||||
|
<string name="verification_emoji_hat">帽子</string>
|
||||||
|
<string name="verification_emoji_glasses">眼镜</string>
|
||||||
|
<string name="verification_emoji_spanner">扳手</string>
|
||||||
|
<string name="verification_emoji_santa">圣诞老人</string>
|
||||||
|
<string name="verification_emoji_thumbs_up">赞</string>
|
||||||
|
<string name="verification_emoji_umbrella">伞</string>
|
||||||
|
<string name="verification_emoji_hourglass">沙漏</string>
|
||||||
|
<string name="verification_emoji_clock">时钟</string>
|
||||||
|
<string name="verification_emoji_gift">礼物</string>
|
||||||
|
<string name="verification_emoji_light_bulb">灯泡</string>
|
||||||
|
<string name="verification_emoji_book">书</string>
|
||||||
|
<string name="verification_emoji_pencil">铅笔</string>
|
||||||
|
<string name="verification_emoji_paperclip">回形针</string>
|
||||||
|
<string name="verification_emoji_scissors">剪刀</string>
|
||||||
|
<string name="verification_emoji_lock">锁</string>
|
||||||
|
<string name="verification_emoji_key">钥匙</string>
|
||||||
|
<string name="verification_emoji_hammer">锤子</string>
|
||||||
|
<string name="verification_emoji_telephone">电话</string>
|
||||||
|
<string name="verification_emoji_flag">旗帜</string>
|
||||||
|
<string name="verification_emoji_train">火车</string>
|
||||||
|
<string name="verification_emoji_bicycle">自行车</string>
|
||||||
|
<string name="verification_emoji_aeroplane">飞机</string>
|
||||||
|
<string name="verification_emoji_rocket">火箭</string>
|
||||||
|
<string name="verification_emoji_trophy">奖杯</string>
|
||||||
|
<string name="verification_emoji_ball">球</string>
|
||||||
|
<string name="verification_emoji_guitar">吉他</string>
|
||||||
|
<string name="verification_emoji_trumpet">喇叭</string>
|
||||||
|
<string name="verification_emoji_bell">铃铛</string>
|
||||||
|
<string name="verification_emoji_anchor">锚</string>
|
||||||
|
<string name="verification_emoji_headphones">耳机</string>
|
||||||
|
<string name="verification_emoji_folder">文件夹</string>
|
||||||
|
<string name="verification_emoji_pin">图钉</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -17,7 +17,7 @@ androidExtensions {
|
||||||
// Note: 2 digits max for each value
|
// Note: 2 digits max for each value
|
||||||
ext.versionMajor = 1
|
ext.versionMajor = 1
|
||||||
ext.versionMinor = 0
|
ext.versionMinor = 0
|
||||||
ext.versionPatch = 7
|
ext.versionPatch = 8
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
def cmd = 'git show -s --format=%ct'
|
def cmd = 'git show -s --format=%ct'
|
||||||
|
@ -352,7 +352,7 @@ dependencies {
|
||||||
implementation 'me.saket:better-link-movement-method:2.2.0'
|
implementation 'me.saket:better-link-movement-method:2.2.0'
|
||||||
implementation 'com.google.android:flexbox:1.1.1'
|
implementation 'com.google.android:flexbox:1.1.1'
|
||||||
implementation "androidx.autofill:autofill:$autofill_version"
|
implementation "androidx.autofill:autofill:$autofill_version"
|
||||||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta9'
|
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta10'
|
||||||
|
|
||||||
// Custom Tab
|
// Custom Tab
|
||||||
implementation 'androidx.browser:browser:1.2.0'
|
implementation 'androidx.browser:browser:1.2.0'
|
||||||
|
|
|
@ -89,6 +89,7 @@ import im.vector.app.features.settings.VectorSettingsHelpAboutFragment
|
||||||
import im.vector.app.features.settings.VectorSettingsLabsFragment
|
import im.vector.app.features.settings.VectorSettingsLabsFragment
|
||||||
import im.vector.app.features.settings.VectorSettingsNotificationPreferenceFragment
|
import im.vector.app.features.settings.VectorSettingsNotificationPreferenceFragment
|
||||||
import im.vector.app.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
import im.vector.app.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
||||||
|
import im.vector.app.features.settings.VectorSettingsPinFragment
|
||||||
import im.vector.app.features.settings.VectorSettingsPreferencesFragment
|
import im.vector.app.features.settings.VectorSettingsPreferencesFragment
|
||||||
import im.vector.app.features.settings.VectorSettingsSecurityPrivacyFragment
|
import im.vector.app.features.settings.VectorSettingsSecurityPrivacyFragment
|
||||||
import im.vector.app.features.settings.account.deactivation.DeactivateAccountFragment
|
import im.vector.app.features.settings.account.deactivation.DeactivateAccountFragment
|
||||||
|
@ -284,6 +285,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(VectorSettingsLabsFragment::class)
|
@FragmentKey(VectorSettingsLabsFragment::class)
|
||||||
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
|
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(VectorSettingsPinFragment::class)
|
||||||
|
fun bindVectorSettingsPinFragment(fragment: VectorSettingsPinFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(PushRulesFragment::class)
|
@FragmentKey(PushRulesFragment::class)
|
||||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.app.features.crypto.verification.IncomingVerificationRequestHan
|
||||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.HomeRoomListDataSource
|
import im.vector.app.features.home.HomeRoomListDataSource
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
import im.vector.app.features.html.VectorHtmlCompressor
|
import im.vector.app.features.html.VectorHtmlCompressor
|
||||||
import im.vector.app.features.login.ReAuthHelper
|
import im.vector.app.features.login.ReAuthHelper
|
||||||
|
@ -71,6 +72,8 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun matrix(): Matrix
|
fun matrix(): Matrix
|
||||||
|
|
||||||
|
fun matrixItemColorProvider(): MatrixItemColorProvider
|
||||||
|
|
||||||
fun sessionListener(): SessionListener
|
fun sessionListener(): SessionListener
|
||||||
|
|
||||||
fun currentSession(): Session
|
fun currentSession(): Session
|
||||||
|
|
|
@ -17,12 +17,12 @@
|
||||||
package im.vector.app.core.extensions
|
package im.vector.app.core.extensions
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentTransaction
|
import androidx.fragment.app.FragmentTransaction
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
|
||||||
inline fun androidx.fragment.app.FragmentManager.commitTransactionNow(func: FragmentTransaction.() -> FragmentTransaction) {
|
inline fun androidx.fragment.app.FragmentManager.commitTransactionNow(func: FragmentTransaction.() -> FragmentTransaction) {
|
||||||
// Could throw and make the app crash
|
// Could throw and make the app crash
|
||||||
// e.g sharedActionViewModel.observe()
|
// e.g sharedActionViewModel.observe()
|
||||||
tryThis("Failed to commitTransactionNow") {
|
tryOrNull("Failed to commitTransactionNow") {
|
||||||
beginTransaction().func().commitNow()
|
beginTransaction().func().commitNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,14 @@ package im.vector.app.core.extensions
|
||||||
|
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||||
import org.matrix.android.sdk.api.extensions.ensurePrefix
|
import org.matrix.android.sdk.api.extensions.ensurePrefix
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
|
|
||||||
fun ThreePid.getFormattedValue(): String {
|
fun ThreePid.getFormattedValue(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is ThreePid.Email -> email
|
is ThreePid.Email -> email
|
||||||
is ThreePid.Msisdn -> {
|
is ThreePid.Msisdn -> {
|
||||||
tryThis(message = "Unable to parse the phone number") {
|
tryOrNull(message = "Unable to parse the phone number") {
|
||||||
PhoneNumberUtil.getInstance().parse(msisdn.ensurePrefix("+"), null)
|
PhoneNumberUtil.getInstance().parse(msisdn.ensurePrefix("+"), null)
|
||||||
}
|
}
|
||||||
?.let {
|
?.let {
|
||||||
|
|
|
@ -84,7 +84,7 @@ import im.vector.app.receivers.DebugReceiver
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.GlobalError
|
import org.matrix.android.sdk.api.failure.GlobalError
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
@ -318,11 +318,17 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
|
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
|
||||||
when (resultCode) {
|
when (resultCode) {
|
||||||
Activity.RESULT_OK -> {
|
Activity.RESULT_OK -> {
|
||||||
|
Timber.v("Pin ok, unlock app")
|
||||||
pinLocker.unlock()
|
pinLocker.unlock()
|
||||||
|
|
||||||
|
// Cancel any new started PinActivity, after a screen rotation for instance
|
||||||
|
finishActivity(PinActivity.PIN_REQUEST_CODE)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
pinLocker.block()
|
if (pinLocker.getLiveState().value != PinLocker.State.UNLOCKED) {
|
||||||
moveTaskToBack(true)
|
// Remove the task, to be sure that PIN code will be requested when resumed
|
||||||
|
finishAndRemoveTask()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,7 +368,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
super.onPostResume()
|
super.onPostResume()
|
||||||
synchronized(postResumeScheduledActions) {
|
synchronized(postResumeScheduledActions) {
|
||||||
postResumeScheduledActions.forEach {
|
postResumeScheduledActions.forEach {
|
||||||
tryThis { it.invoke() }
|
tryOrNull { it.invoke() }
|
||||||
}
|
}
|
||||||
postResumeScheduledActions.clear()
|
postResumeScheduledActions.clear()
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,9 @@ abstract class GenericItemWithValue : VectorEpoxyModel<GenericItemWithValue.Hold
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var itemClickAction: View.OnClickListener? = null
|
var itemClickAction: View.OnClickListener? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var itemLongClickAction: View.OnLongClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.titleText.setTextOrHide(title)
|
holder.titleText.setTextOrHide(title)
|
||||||
|
@ -76,6 +79,7 @@ abstract class GenericItemWithValue : VectorEpoxyModel<GenericItemWithValue.Hold
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.view.setOnClickListener(itemClickAction?.let { DebouncedClickListener(it) })
|
holder.view.setOnClickListener(itemClickAction?.let { DebouncedClickListener(it) })
|
||||||
|
holder.view.setOnLongClickListener(itemLongClickAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
|
|
@ -40,7 +40,7 @@ import androidx.fragment.app.Fragment
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -530,7 +530,7 @@ fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: Strin
|
||||||
return null
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
// Close resources
|
// Close resources
|
||||||
tryThis { inputStream?.close() }
|
tryOrNull { inputStream?.close() }
|
||||||
tryThis { outputStream?.close() }
|
tryOrNull { outputStream?.close() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,6 @@ import androidx.fragment.app.Fragment
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
private const val LOG_TAG = "PermissionUtils"
|
|
||||||
|
|
||||||
// Android M permission request code management
|
// Android M permission request code management
|
||||||
private const val PERMISSIONS_GRANTED = true
|
private const val PERMISSIONS_GRANTED = true
|
||||||
private const val PERMISSIONS_DENIED = !PERMISSIONS_GRANTED
|
private const val PERMISSIONS_DENIED = !PERMISSIONS_GRANTED
|
||||||
|
@ -42,16 +40,18 @@ const val PERMISSION_CAMERA = 0x1
|
||||||
private const val PERMISSION_WRITE_EXTERNAL_STORAGE = 0x1 shl 1
|
private const val PERMISSION_WRITE_EXTERNAL_STORAGE = 0x1 shl 1
|
||||||
private const val PERMISSION_RECORD_AUDIO = 0x1 shl 2
|
private const val PERMISSION_RECORD_AUDIO = 0x1 shl 2
|
||||||
private const val PERMISSION_READ_CONTACTS = 0x1 shl 3
|
private const val PERMISSION_READ_CONTACTS = 0x1 shl 3
|
||||||
|
private const val PERMISSION_READ_EXTERNAL_STORAGE = 0x1 shl 4
|
||||||
|
|
||||||
// Permissions sets
|
// Permissions sets
|
||||||
const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
|
const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
|
||||||
const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||||
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA or PERMISSION_WRITE_EXTERNAL_STORAGE
|
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA
|
||||||
const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
|
const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
|
||||||
const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
|
const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
|
||||||
const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
|
const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
|
||||||
const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||||
const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
|
const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
|
||||||
|
const val PERMISSIONS_FOR_READING_FILES = PERMISSION_READ_EXTERNAL_STORAGE
|
||||||
const val PERMISSIONS_FOR_PICKING_CONTACT = PERMISSION_READ_CONTACTS
|
const val PERMISSIONS_FOR_PICKING_CONTACT = PERMISSION_READ_CONTACTS
|
||||||
|
|
||||||
const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
|
const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
|
||||||
|
@ -67,7 +67,6 @@ const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574
|
||||||
const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
|
const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
|
||||||
const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576
|
const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576
|
||||||
const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577
|
const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577
|
||||||
const val PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT = 578
|
|
||||||
const val PERMISSION_REQUEST_CODE_READ_CONTACTS = 579
|
const val PERMISSION_REQUEST_CODE_READ_CONTACTS = 579
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,6 +78,7 @@ fun logPermissionStatuses(context: Context) {
|
||||||
Manifest.permission.CAMERA,
|
Manifest.permission.CAMERA,
|
||||||
Manifest.permission.RECORD_AUDIO,
|
Manifest.permission.RECORD_AUDIO,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
Manifest.permission.READ_CONTACTS)
|
Manifest.permission.READ_CONTACTS)
|
||||||
|
|
||||||
Timber.v("## logPermissionStatuses() : log the permissions status used by the app")
|
Timber.v("## logPermissionStatuses() : log the permissions status used by the app")
|
||||||
|
@ -145,7 +145,7 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||||
fragment: Fragment?,
|
fragment: Fragment?,
|
||||||
requestCode: Int,
|
requestCode: Int,
|
||||||
@StringRes rationaleMessage: Int
|
@StringRes rationaleMessage: Int
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var isPermissionGranted = false
|
var isPermissionGranted = false
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
|
@ -161,7 +161,8 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||||
&& PERMISSIONS_FOR_MEMBER_DETAILS != permissionsToBeGrantedBitMap
|
&& PERMISSIONS_FOR_MEMBER_DETAILS != permissionsToBeGrantedBitMap
|
||||||
&& PERMISSIONS_FOR_ROOM_AVATAR != permissionsToBeGrantedBitMap
|
&& PERMISSIONS_FOR_ROOM_AVATAR != permissionsToBeGrantedBitMap
|
||||||
&& PERMISSIONS_FOR_VIDEO_RECORDING != permissionsToBeGrantedBitMap
|
&& PERMISSIONS_FOR_VIDEO_RECORDING != permissionsToBeGrantedBitMap
|
||||||
&& PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap) {
|
&& PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap
|
||||||
|
&& PERMISSIONS_FOR_READING_FILES != permissionsToBeGrantedBitMap) {
|
||||||
Timber.w("## checkPermissions(): permissions to be granted are not supported")
|
Timber.w("## checkPermissions(): permissions to be granted are not supported")
|
||||||
isPermissionGranted = false
|
isPermissionGranted = false
|
||||||
} else {
|
} else {
|
||||||
|
@ -188,6 +189,12 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PERMISSION_READ_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_READ_EXTERNAL_STORAGE) {
|
||||||
|
val permissionType = Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||||
|
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||||
|
}
|
||||||
|
|
||||||
// the contact book access is requested for any android platforms
|
// the contact book access is requested for any android platforms
|
||||||
// for android M, we use the system preferences
|
// for android M, we use the system preferences
|
||||||
// for android < M, we use a dedicated settings
|
// for android < M, we use a dedicated settings
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.app.core.utils
|
|
||||||
|
|
||||||
import androidx.annotation.ColorRes
|
|
||||||
import im.vector.app.R
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
@ColorRes
|
|
||||||
fun getColorFromUserId(userId: String?): Int {
|
|
||||||
var hash = 0
|
|
||||||
|
|
||||||
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() }
|
|
||||||
|
|
||||||
return when (abs(hash) % 8) {
|
|
||||||
1 -> R.color.riotx_username_2
|
|
||||||
2 -> R.color.riotx_username_3
|
|
||||||
3 -> R.color.riotx_username_4
|
|
||||||
4 -> R.color.riotx_username_5
|
|
||||||
5 -> R.color.riotx_username_6
|
|
||||||
6 -> R.color.riotx_username_7
|
|
||||||
7 -> R.color.riotx_username_8
|
|
||||||
else -> R.color.riotx_username_1
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -42,7 +42,6 @@ import im.vector.app.core.extensions.getMeasurements
|
||||||
import im.vector.app.core.utils.PERMISSIONS_EMPTY
|
import im.vector.app.core.utils.PERMISSIONS_EMPTY
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
|
import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
|
|
||||||
import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback
|
import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@ -215,10 +214,10 @@ class AttachmentTypeSelectorView(context: Context,
|
||||||
*/
|
*/
|
||||||
enum class Type(val permissionsBit: Int) {
|
enum class Type(val permissionsBit: Int) {
|
||||||
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
|
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
|
||||||
GALLERY(PERMISSIONS_FOR_WRITING_FILES),
|
GALLERY(PERMISSIONS_EMPTY),
|
||||||
FILE(PERMISSIONS_FOR_WRITING_FILES),
|
FILE(PERMISSIONS_EMPTY),
|
||||||
STICKER(PERMISSIONS_EMPTY),
|
STICKER(PERMISSIONS_EMPTY),
|
||||||
AUDIO(PERMISSIONS_FOR_WRITING_FILES),
|
AUDIO(PERMISSIONS_EMPTY),
|
||||||
CONTACT(PERMISSIONS_FOR_PICKING_CONTACT)
|
CONTACT(PERMISSIONS_FOR_PICKING_CONTACT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,17 +42,13 @@ import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.utils.OnSnapPositionChangeListener
|
import im.vector.app.core.utils.OnSnapPositionChangeListener
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
|
|
||||||
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT
|
|
||||||
import im.vector.app.core.utils.SnapOnScrollListener
|
import im.vector.app.core.utils.SnapOnScrollListener
|
||||||
import im.vector.app.core.utils.allGranted
|
|
||||||
import im.vector.app.core.utils.attachSnapHelperWithListener
|
import im.vector.app.core.utils.attachSnapHelperWithListener
|
||||||
import im.vector.app.core.utils.checkPermissions
|
|
||||||
import im.vector.app.features.media.createUCropWithDefaultSettings
|
import im.vector.app.features.media.createUCropWithDefaultSettings
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
|
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -102,7 +98,7 @@ class AttachmentsPreviewFragment @Inject constructor(
|
||||||
handleRemoveAction()
|
handleRemoveAction()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.attachmentsPreviewEditAction -> {
|
R.id.attachmentsPreviewEditAction -> {
|
||||||
handleEditAction()
|
handleEditAction()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -183,22 +179,7 @@ class AttachmentsPreviewFragment @Inject constructor(
|
||||||
viewModel.handle(AttachmentsPreviewAction.RemoveCurrentAttachment)
|
viewModel.handle(AttachmentsPreviewAction.RemoveCurrentAttachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEditAction() {
|
private fun handleEditAction() = withState(viewModel) {
|
||||||
// check permissions
|
|
||||||
if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT)) {
|
|
||||||
doHandleEditAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
|
|
||||||
if (requestCode == PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT && allGranted(grantResults)) {
|
|
||||||
doHandleEditAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doHandleEditAction() = withState(viewModel) {
|
|
||||||
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
|
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
|
||||||
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
|
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
|
||||||
val uri = currentAttachment.queryUri
|
val uri = currentAttachment.queryUri
|
||||||
|
|
|
@ -27,7 +27,7 @@ import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
import io.reactivex.subjects.ReplaySubject
|
import io.reactivex.subjects.ReplaySubject
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
import org.matrix.android.sdk.api.session.call.CallsListener
|
import org.matrix.android.sdk.api.session.call.CallsListener
|
||||||
|
@ -95,7 +95,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||||
|
|
||||||
val callAudioManager = CallAudioManager(context.applicationContext) {
|
val callAudioManager = CallAudioManager(context.applicationContext) {
|
||||||
currentCallsListeners.forEach {
|
currentCallsListeners.forEach {
|
||||||
tryThis { it.onAudioDevicesChange() }
|
tryOrNull { it.onAudioDevicesChange() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
currentCallsListeners.forEach {
|
currentCallsListeners.forEach {
|
||||||
tryThis { it.onCaptureStateChanged() }
|
tryOrNull { it.onCaptureStateChanged() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
currentCallsListeners.forEach {
|
currentCallsListeners.forEach {
|
||||||
tryThis { it.onCurrentCallChange(value?.mxCall) }
|
tryOrNull { it.onCurrentCallChange(value?.mxCall) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -745,7 +745,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
currentCallsListeners.forEach {
|
currentCallsListeners.forEach {
|
||||||
tryThis { it.onCameraChange() }
|
tryOrNull { it.onCameraChange() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -771,7 +771,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||||
// videoCapturer?.stopCapture()
|
// videoCapturer?.stopCapture()
|
||||||
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
||||||
currentCaptureMode = format
|
currentCaptureMode = format
|
||||||
currentCallsListeners.forEach { tryThis { it.onCaptureStateChanged() } }
|
currentCallsListeners.forEach { tryOrNull { it.onCaptureStateChanged() } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ import org.jitsi.meet.sdk.JitsiMeetActivityInterface
|
||||||
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
|
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
|
||||||
import org.jitsi.meet.sdk.JitsiMeetView
|
import org.jitsi.meet.sdk.JitsiMeetView
|
||||||
import org.jitsi.meet.sdk.JitsiMeetViewListener
|
import org.jitsi.meet.sdk.JitsiMeetViewListener
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
|
||||||
.setVideoMuted(!viewState.enableVideo)
|
.setVideoMuted(!viewState.enableVideo)
|
||||||
.setUserInfo(viewState.userInfo)
|
.setUserInfo(viewState.userInfo)
|
||||||
.apply {
|
.apply {
|
||||||
tryThis { URL(viewState.jitsiUrl) }?.let {
|
tryOrNull { URL(viewState.jitsiUrl) }?.let {
|
||||||
setServerURL(it)
|
setServerURL(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.utils.startImportTextFromFileIntent
|
import im.vector.app.core.utils.startImportTextFromFileIntent
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.fragment_ssss_access_from_key.*
|
import kotlinx.android.synthetic.main.fragment_ssss_access_from_key.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -84,7 +84,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
|
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
|
||||||
data?.data?.let { dataURI ->
|
data?.data?.let { dataURI ->
|
||||||
tryThis {
|
tryOrNull {
|
||||||
activity?.contentResolver?.openInputStream(dataURI)
|
activity?.contentResolver?.openInputStream(dataURI)
|
||||||
?.bufferedReader()
|
?.bufferedReader()
|
||||||
?.use { it.readText() }
|
?.use { it.readText() }
|
||||||
|
|
|
@ -37,7 +37,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.utils.colorizeMatchingText
|
import im.vector.app.core.utils.colorizeMatchingText
|
||||||
import im.vector.app.core.utils.startImportTextFromFileIntent
|
import im.vector.app.core.utils.startImportTextFromFileIntent
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
|
import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText
|
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText
|
||||||
|
@ -150,7 +150,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
|
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
|
||||||
data?.data?.let { dataURI ->
|
data?.data?.let { dataURI ->
|
||||||
tryThis {
|
tryOrNull {
|
||||||
activity?.contentResolver?.openInputStream(dataURI)
|
activity?.contentResolver?.openInputStream(dataURI)
|
||||||
?.bufferedReader()
|
?.bufferedReader()
|
||||||
?.use { it.readText() }
|
?.use { it.readText() }
|
||||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.utils.ensureProtocol
|
import im.vector.app.core.utils.ensureProtocol
|
||||||
import im.vector.app.features.discovery.change.SetIdentityServerFragment
|
import im.vector.app.features.discovery.change.SetIdentityServerFragment
|
||||||
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
import im.vector.app.features.terms.ReviewTermsActivity
|
import im.vector.app.features.terms.ReviewTermsActivity
|
||||||
import org.matrix.android.sdk.api.session.identity.SharedState
|
import org.matrix.android.sdk.api.session.identity.SharedState
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
|
@ -178,10 +179,6 @@ class DiscoverySettingsFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToChangeIdentityServerFragment() {
|
private fun navigateToChangeIdentityServerFragment() {
|
||||||
parentFragmentManager.beginTransaction()
|
(vectorBaseActivity as? VectorSettingsActivity)?.navigateTo(SetIdentityServerFragment::class.java)
|
||||||
.setCustomAnimations(R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom, R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom)
|
|
||||||
.replace(R.id.vector_settings_page, SetIdentityServerFragment::class.java, null)
|
|
||||||
.addToBackStack(null)
|
|
||||||
.commit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,11 @@
|
||||||
|
|
||||||
package im.vector.app.features.home
|
package im.vector.app.features.home
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.AnyThread
|
import androidx.annotation.AnyThread
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
@ -33,8 +31,8 @@ import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.core.glide.GlideRequest
|
import im.vector.app.core.glide.GlideRequest
|
||||||
import im.vector.app.core.glide.GlideRequests
|
import im.vector.app.core.glide.GlideRequests
|
||||||
import im.vector.app.core.utils.getColorFromUserId
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -43,7 +41,8 @@ import javax.inject.Inject
|
||||||
* This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable>
|
* This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
|
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val matrixItemColorProvider: MatrixItemColorProvider) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val THUMBNAIL_SIZE = 250
|
private const val THUMBNAIL_SIZE = 250
|
||||||
|
@ -51,21 +50,19 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(matrixItem: MatrixItem, imageView: ImageView) {
|
fun render(matrixItem: MatrixItem, imageView: ImageView) {
|
||||||
render(imageView.context,
|
render(GlideApp.with(imageView),
|
||||||
GlideApp.with(imageView),
|
|
||||||
matrixItem,
|
matrixItem,
|
||||||
DrawableImageViewTarget(imageView))
|
DrawableImageViewTarget(imageView))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear(imageView: ImageView) {
|
fun clear(imageView: ImageView) {
|
||||||
// It can be called after recycler view is destroyed, just silently catch
|
// It can be called after recycler view is destroyed, just silently catch
|
||||||
tryThis { GlideApp.with(imageView).clear(imageView) }
|
tryOrNull { GlideApp.with(imageView).clear(imageView) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
|
fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
|
||||||
render(imageView.context,
|
render(glideRequests,
|
||||||
glideRequests,
|
|
||||||
matrixItem,
|
matrixItem,
|
||||||
DrawableImageViewTarget(imageView))
|
DrawableImageViewTarget(imageView))
|
||||||
}
|
}
|
||||||
|
@ -79,7 +76,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
displayName = mappedContact.displayName
|
displayName = mappedContact.displayName
|
||||||
)
|
)
|
||||||
|
|
||||||
val placeholder = getPlaceholderDrawable(imageView.context, matrixItem)
|
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||||
GlideApp.with(imageView)
|
GlideApp.with(imageView)
|
||||||
.load(mappedContact.photoURI)
|
.load(mappedContact.photoURI)
|
||||||
.apply(RequestOptions.circleCropTransform())
|
.apply(RequestOptions.circleCropTransform())
|
||||||
|
@ -88,11 +85,10 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(context: Context,
|
fun render(glideRequests: GlideRequests,
|
||||||
glideRequests: GlideRequests,
|
|
||||||
matrixItem: MatrixItem,
|
matrixItem: MatrixItem,
|
||||||
target: Target<Drawable>) {
|
target: Target<Drawable>) {
|
||||||
val placeholder = getPlaceholderDrawable(context, matrixItem)
|
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||||
buildGlideRequest(glideRequests, matrixItem.avatarUrl)
|
buildGlideRequest(glideRequests, matrixItem.avatarUrl)
|
||||||
.placeholder(placeholder)
|
.placeholder(placeholder)
|
||||||
.into(target)
|
.into(target)
|
||||||
|
@ -100,7 +96,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
@Throws
|
@Throws
|
||||||
fun shortcutDrawable(context: Context, glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap {
|
fun shortcutDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap {
|
||||||
return glideRequests
|
return glideRequests
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.apply {
|
.apply {
|
||||||
|
@ -108,7 +104,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
if (resolvedUrl != null) {
|
if (resolvedUrl != null) {
|
||||||
load(resolvedUrl)
|
load(resolvedUrl)
|
||||||
} else {
|
} else {
|
||||||
val avatarColor = avatarColor(matrixItem, context)
|
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
|
||||||
load(TextDrawable.builder()
|
load(TextDrawable.builder()
|
||||||
.beginConfig()
|
.beginConfig()
|
||||||
.bold()
|
.bold()
|
||||||
|
@ -130,8 +126,8 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
}
|
}
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
|
fun getPlaceholderDrawable(matrixItem: MatrixItem): Drawable {
|
||||||
val avatarColor = avatarColor(matrixItem, context)
|
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
|
||||||
return TextDrawable.builder()
|
return TextDrawable.builder()
|
||||||
.beginConfig()
|
.beginConfig()
|
||||||
.bold()
|
.bold()
|
||||||
|
@ -152,11 +148,4 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
|
return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
|
||||||
?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
|
?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun avatarColor(matrixItem: MatrixItem, context: Context): Int {
|
|
||||||
return when (matrixItem) {
|
|
||||||
is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id))
|
|
||||||
else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ class ShortcutsHandler @Inject constructor(
|
||||||
.map { room ->
|
.map { room ->
|
||||||
val intent = RoomDetailActivity.shortcutIntent(context, room.roomId)
|
val intent = RoomDetailActivity.shortcutIntent(context, room.roomId)
|
||||||
val bitmap = try {
|
val bitmap = try {
|
||||||
avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem(), iconSize)
|
avatarRenderer.shortcutDrawable(GlideApp.with(context), room.toMatrixItem(), iconSize)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,6 @@ import im.vector.app.core.utils.colorizeMatchingText
|
||||||
import im.vector.app.core.utils.copyToClipboard
|
import im.vector.app.core.utils.copyToClipboard
|
||||||
import im.vector.app.core.utils.createJSonViewerStyleProvider
|
import im.vector.app.core.utils.createJSonViewerStyleProvider
|
||||||
import im.vector.app.core.utils.createUIHandler
|
import im.vector.app.core.utils.createUIHandler
|
||||||
import im.vector.app.core.utils.getColorFromUserId
|
|
||||||
import im.vector.app.core.utils.isValidUrl
|
import im.vector.app.core.utils.isValidUrl
|
||||||
import im.vector.app.core.utils.onPermissionResultAudioIpCall
|
import im.vector.app.core.utils.onPermissionResultAudioIpCall
|
||||||
import im.vector.app.core.utils.onPermissionResultVideoIpCall
|
import im.vector.app.core.utils.onPermissionResultVideoIpCall
|
||||||
|
@ -127,6 +126,7 @@ import im.vector.app.features.home.room.detail.timeline.action.EventSharedAction
|
||||||
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||||
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||||
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
|
||||||
|
@ -217,7 +217,9 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
private val notificationUtils: NotificationUtils,
|
private val notificationUtils: NotificationUtils,
|
||||||
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager) :
|
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
|
||||||
|
private val matrixItemColorProvider: MatrixItemColorProvider
|
||||||
|
) :
|
||||||
VectorBaseFragment(),
|
VectorBaseFragment(),
|
||||||
TimelineEventController.Callback,
|
TimelineEventController.Callback,
|
||||||
VectorInviteView.Callback,
|
VectorInviteView.Callback,
|
||||||
|
@ -610,6 +612,16 @@ class RoomDetailFragment @Inject constructor(
|
||||||
it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId)
|
it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId)
|
||||||
}
|
}
|
||||||
withState(roomDetailViewModel) { state ->
|
withState(roomDetailViewModel) { state ->
|
||||||
|
// Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions
|
||||||
|
val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) {
|
||||||
|
1 -> false
|
||||||
|
2 -> state.isAllowedToStartWebRTCCall
|
||||||
|
else -> state.isAllowedToManageWidgets
|
||||||
|
}
|
||||||
|
setOf(R.id.voice_call, R.id.video_call).forEach {
|
||||||
|
menu.findItem(it).icon?.alpha = if (callButtonsEnabled) 0xFF else 0x40
|
||||||
|
}
|
||||||
|
|
||||||
val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps)
|
val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps)
|
||||||
val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0
|
val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0
|
||||||
if (widgetsCount > 0) {
|
if (widgetsCount > 0) {
|
||||||
|
@ -687,6 +699,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
// webRtcPeerConnectionManager.endCall()
|
// webRtcPeerConnectionManager.endCall()
|
||||||
// safeStartCall(it, isVideoCall)
|
// safeStartCall(it, isVideoCall)
|
||||||
// }
|
// }
|
||||||
|
} else if (!state.isAllowedToStartWebRTCCall) {
|
||||||
|
showDialogWithMessage(getString(R.string.no_permissions_to_start_webrtc_call))
|
||||||
} else {
|
} else {
|
||||||
safeStartCall(isVideoCall)
|
safeStartCall(isVideoCall)
|
||||||
}
|
}
|
||||||
|
@ -778,7 +792,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
// switch to expanded bar
|
// switch to expanded bar
|
||||||
composerLayout.composerRelatedMessageTitle.apply {
|
composerLayout.composerRelatedMessageTitle.apply {
|
||||||
text = event.senderInfo.disambiguatedDisplayName
|
text = event.senderInfo.disambiguatedDisplayName
|
||||||
setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId)))
|
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
|
||||||
}
|
}
|
||||||
|
|
||||||
val messageContent: MessageContent? = event.getLastMessageContent()
|
val messageContent: MessageContent? = event.getLastMessageContent()
|
||||||
|
|
|
@ -57,7 +57,7 @@ import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.MatrixPatterns
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -181,10 +181,12 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
.subscribe {
|
.subscribe {
|
||||||
val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE)
|
val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE)
|
||||||
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
|
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
|
||||||
|
val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
canSendMessage = canSendMessage,
|
canSendMessage = canSendMessage,
|
||||||
isAllowedToManageWidgets = isAllowedToManageWidgets
|
isAllowedToManageWidgets = isAllowedToManageWidgets,
|
||||||
|
isAllowedToStartWebRTCCall = isAllowedToStartWebRTCCall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,7 +336,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
val roomId: String = room.roomId
|
val roomId: String = room.roomId
|
||||||
val confId = roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.toLowerCase(VectorLocale.applicationLocale)
|
val confId = roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.toLowerCase(VectorLocale.applicationLocale)
|
||||||
|
|
||||||
val preferredJitsiDomain = tryThis {
|
val preferredJitsiDomain = tryOrNull {
|
||||||
rawService.getElementWellknown(session.myUserId)
|
rawService.getElementWellknown(session.myUserId)
|
||||||
?.jitsiServer
|
?.jitsiServer
|
||||||
?.preferredDomain
|
?.preferredDomain
|
||||||
|
@ -988,7 +990,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
&& mxcUrl?.startsWith("content://") ?: false
|
&& mxcUrl?.startsWith("content://") ?: false
|
||||||
val isDownloaded = mxcUrl?.let { session.fileService().isFileInCache(it, action.messageFileContent.mimeType) } ?: false
|
val isDownloaded = mxcUrl?.let { session.fileService().isFileInCache(it, action.messageFileContent.mimeType) } ?: false
|
||||||
if (isLocalSendingFile) {
|
if (isLocalSendingFile) {
|
||||||
tryThis { Uri.parse(mxcUrl) }?.let {
|
tryOrNull { Uri.parse(mxcUrl) }?.let {
|
||||||
_viewEvents.post(RoomDetailViewEvents.OpenFile(
|
_viewEvents.post(RoomDetailViewEvents.OpenFile(
|
||||||
action.messageFileContent.mimeType,
|
action.messageFileContent.mimeType,
|
||||||
it,
|
it,
|
||||||
|
|
|
@ -67,7 +67,8 @@ data class RoomDetailViewState(
|
||||||
val canShowJumpToReadMarker: Boolean = true,
|
val canShowJumpToReadMarker: Boolean = true,
|
||||||
val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown,
|
val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown,
|
||||||
val canSendMessage: Boolean = true,
|
val canSendMessage: Boolean = true,
|
||||||
val isAllowedToManageWidgets: Boolean = false
|
val isAllowedToManageWidgets: Boolean = false,
|
||||||
|
val isAllowedToStartWebRTCCall: Boolean = true
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomDetailArgs) : this(
|
constructor(args: RoomDetailArgs) : this(
|
||||||
|
|
|
@ -19,18 +19,20 @@ package im.vector.app.features.home.room.detail.timeline
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.utils.getColorFromUserId
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MessageColorProvider @Inject constructor(
|
class MessageColorProvider @Inject constructor(
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
|
private val matrixItemColorProvider: MatrixItemColorProvider,
|
||||||
private val vectorPreferences: VectorPreferences) {
|
private val vectorPreferences: VectorPreferences) {
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
fun getMemberNameTextColor(userId: String): Int {
|
fun getMemberNameTextColor(matrixItem: MatrixItem): Int {
|
||||||
return colorProvider.getColor(getColorFromUserId(userId))
|
return matrixItemColorProvider.getColor(matrixItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.app.features.home.room.detail.timeline.helper
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class MatrixItemColorProvider @Inject constructor(
|
||||||
|
private val colorProvider: ColorProvider
|
||||||
|
) {
|
||||||
|
private val cache = mutableMapOf<String, Int>()
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
fun getColor(matrixItem: MatrixItem): Int {
|
||||||
|
return cache.getOrPut(matrixItem.id) {
|
||||||
|
colorProvider.getColor(
|
||||||
|
when (matrixItem) {
|
||||||
|
is MatrixItem.UserItem -> getColorFromUserId(matrixItem.id)
|
||||||
|
else -> getColorFromRoomId(matrixItem.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@ColorRes
|
||||||
|
@VisibleForTesting
|
||||||
|
fun getColorFromUserId(userId: String?): Int {
|
||||||
|
var hash = 0
|
||||||
|
|
||||||
|
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() }
|
||||||
|
|
||||||
|
return when (abs(hash) % 8) {
|
||||||
|
1 -> R.color.riotx_username_2
|
||||||
|
2 -> R.color.riotx_username_3
|
||||||
|
3 -> R.color.riotx_username_4
|
||||||
|
4 -> R.color.riotx_username_5
|
||||||
|
5 -> R.color.riotx_username_6
|
||||||
|
6 -> R.color.riotx_username_7
|
||||||
|
7 -> R.color.riotx_username_8
|
||||||
|
else -> R.color.riotx_username_1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorRes
|
||||||
|
private fun getColorFromRoomId(roomId: String?): Int {
|
||||||
|
return when ((roomId?.toList()?.sumBy { it.toInt() } ?: 0) % 3) {
|
||||||
|
1 -> R.color.riotx_avatar_fill_2
|
||||||
|
2 -> R.color.riotx_avatar_fill_3
|
||||||
|
else -> R.color.riotx_avatar_fill_1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,13 +21,13 @@ package im.vector.app.features.home.room.detail.timeline.helper
|
||||||
import im.vector.app.core.date.DateFormatKind
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.extensions.localDateTime
|
import im.vector.app.core.extensions.localDateTime
|
||||||
import im.vector.app.core.resources.ColorProvider
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
|
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
@ -49,7 +49,7 @@ import javax.inject.Inject
|
||||||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||||
private val roomSummaryHolder: RoomSummaryHolder,
|
private val roomSummaryHolder: RoomSummaryHolder,
|
||||||
private val dateFormatter: VectorDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val colorProvider: ColorProvider) {
|
private val vectorPreferences: VectorPreferences) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
|
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
|
||||||
// Non nullability has been tested before
|
// Non nullability has been tested before
|
||||||
|
@ -81,6 +81,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||||
avatarUrl = event.senderInfo.avatarUrl,
|
avatarUrl = event.senderInfo.avatarUrl,
|
||||||
memberName = event.senderInfo.disambiguatedDisplayName,
|
memberName = event.senderInfo.disambiguatedDisplayName,
|
||||||
showInformation = showInformation,
|
showInformation = showInformation,
|
||||||
|
forceShowTimestamp = vectorPreferences.alwaysShowTimeStamps(),
|
||||||
orderedReactionList = event.annotations?.reactionsSummary
|
orderedReactionList = event.annotations?.reactionsSummary
|
||||||
// ?.filter { isSingleEmoji(it.key) }
|
// ?.filter { isSingleEmoji(it.key) }
|
||||||
?.map {
|
?.map {
|
||||||
|
|
|
@ -21,6 +21,8 @@ import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.utils.DebouncedClickListener
|
import im.vector.app.core.utils.DebouncedClickListener
|
||||||
|
@ -69,8 +71,14 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
||||||
holder.avatarImageView.setOnClickListener(null)
|
holder.avatarImageView.setOnClickListener(null)
|
||||||
holder.memberNameView.setOnClickListener(null)
|
holder.memberNameView.setOnClickListener(null)
|
||||||
holder.avatarImageView.visibility = View.GONE
|
holder.avatarImageView.visibility = View.GONE
|
||||||
holder.memberNameView.visibility = View.GONE
|
if (attributes.informationData.forceShowTimestamp) {
|
||||||
holder.timeView.visibility = View.GONE
|
holder.memberNameView.isInvisible = true
|
||||||
|
holder.timeView.isVisible = true
|
||||||
|
holder.timeView.text = attributes.informationData.time
|
||||||
|
} else {
|
||||||
|
holder.memberNameView.isVisible = false
|
||||||
|
holder.timeView.isVisible = false
|
||||||
|
}
|
||||||
holder.avatarImageView.setOnLongClickListener(null)
|
holder.avatarImageView.setOnLongClickListener(null)
|
||||||
holder.memberNameView.setOnLongClickListener(null)
|
holder.memberNameView.setOnLongClickListener(null)
|
||||||
}
|
}
|
||||||
|
@ -85,7 +93,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
||||||
super.unbind(holder)
|
super.unbind(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Attributes.getMemberNameColor() = messageColorProvider.getMemberNameTextColor(informationData.senderId)
|
private fun Attributes.getMemberNameColor() = messageColorProvider.getMemberNameTextColor(informationData.matrixItem)
|
||||||
|
|
||||||
abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) {
|
abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) {
|
||||||
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||||
|
|
|
@ -32,6 +32,7 @@ data class MessageInformationData(
|
||||||
val avatarUrl: String?,
|
val avatarUrl: String?,
|
||||||
val memberName: CharSequence? = null,
|
val memberName: CharSequence? = null,
|
||||||
val showInformation: Boolean = true,
|
val showInformation: Boolean = true,
|
||||||
|
val forceShowTimestamp: Boolean = false,
|
||||||
/*List of reactions (emoji,count,isSelected)*/
|
/*List of reactions (emoji,count,isSelected)*/
|
||||||
val orderedReactionList: List<ReactionInfoData>? = null,
|
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||||
val pollResponseAggregatedSummary: PollResponseData? = null,
|
val pollResponseAggregatedSummary: PollResponseData? = null,
|
||||||
|
|
|
@ -27,7 +27,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.ClickListener
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.app.core.epoxy.onClick
|
import im.vector.app.core.epoxy.onClick
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ abstract class RoomWidgetItem : EpoxyModelWithHolder<RoomWidgetItem.Holder>() {
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.widgetName.text = widget.name
|
holder.widgetName.text = widget.name
|
||||||
holder.widgetUrl.text = tryThis { URL(widget.computedUrl) }?.host ?: widget.computedUrl
|
holder.widgetUrl.text = tryOrNull { URL(widget.computedUrl) }?.host ?: widget.computedUrl
|
||||||
if (iconRes != null) {
|
if (iconRes != null) {
|
||||||
holder.iconImage.isVisible = true
|
holder.iconImage.isVisible = true
|
||||||
holder.iconImage.setImageResource(iconRes!!)
|
holder.iconImage.setImageResource(iconRes!!)
|
||||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.app.features.raw.wellknown.isE2EByDefault
|
||||||
import im.vector.app.features.userdirectory.KnownUsersFragment
|
import im.vector.app.features.userdirectory.KnownUsersFragment
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||||
|
@ -68,7 +68,7 @@ class HomeServerCapabilitiesViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun initAdminE2eByDefault() {
|
private fun initAdminE2eByDefault() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val adminE2EByDefault = tryThis {
|
val adminE2EByDefault = tryOrNull {
|
||||||
rawService.getElementWellknown(session.myUserId)
|
rawService.getElementWellknown(session.myUserId)
|
||||||
?.isE2EByDefault()
|
?.isE2EByDefault()
|
||||||
?: true
|
?: true
|
||||||
|
|
|
@ -53,7 +53,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
|
||||||
@UiThread
|
@UiThread
|
||||||
fun bind(textView: TextView) {
|
fun bind(textView: TextView) {
|
||||||
tv = WeakReference(textView)
|
tv = WeakReference(textView)
|
||||||
avatarRenderer.render(context, glideRequests, matrixItem, target)
|
avatarRenderer.render(glideRequests, matrixItem, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplacementSpan *****************************************************************************
|
// ReplacementSpan *****************************************************************************
|
||||||
|
@ -99,7 +99,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
|
||||||
val icon = try {
|
val icon = try {
|
||||||
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
|
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
avatarRenderer.getPlaceholderDrawable(context, matrixItem)
|
avatarRenderer.getPlaceholderDrawable(matrixItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
||||||
|
|
|
@ -19,10 +19,12 @@ package im.vector.app.features.login
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
|
@ -42,10 +44,10 @@ import im.vector.app.features.login.terms.LoginTermsFragment
|
||||||
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
|
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
|
||||||
import im.vector.app.features.login.terms.toLocalizedLoginTerms
|
import im.vector.app.features.login.terms.toLocalizedLoginTerms
|
||||||
import im.vector.app.features.pin.UnlockedActivity
|
import im.vector.app.features.pin.UnlockedActivity
|
||||||
|
import kotlinx.android.synthetic.main.activity_login.*
|
||||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import kotlinx.android.synthetic.main.activity_login.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,6 +74,13 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||||
get() = supportFragmentManager.findFragmentById(R.id.loginFragmentContainer)
|
get() = supportFragmentManager.findFragmentById(R.id.loginFragmentContainer)
|
||||||
|
|
||||||
private val commonOption: (FragmentTransaction) -> Unit = { ft ->
|
private val commonOption: (FragmentTransaction) -> Unit = { ft ->
|
||||||
|
// Find the loginLogo on the current Fragment, this should not return null
|
||||||
|
(topFragment?.view as? ViewGroup)
|
||||||
|
// Find findViewById does not work, I do not know why
|
||||||
|
// findViewById<View?>(R.id.loginLogo)
|
||||||
|
?.children
|
||||||
|
?.firstOrNull { it.id == R.id.loginLogo }
|
||||||
|
?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +136,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||||
is LoginViewEvents.OutdatedHomeserver -> {
|
is LoginViewEvents.OutdatedHomeserver -> {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.login_error_outdated_homeserver_title)
|
.setTitle(R.string.login_error_outdated_homeserver_title)
|
||||||
.setMessage(R.string.login_error_outdated_homeserver_content)
|
.setMessage(R.string.login_error_outdated_homeserver_warning_content)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
Unit
|
Unit
|
||||||
|
@ -136,6 +145,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
LoginServerSelectionFragment::class.java,
|
LoginServerSelectionFragment::class.java,
|
||||||
option = { ft ->
|
option = { ft ->
|
||||||
|
findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
// TODO Disabled because it provokes a flickering
|
// TODO Disabled because it provokes a flickering
|
||||||
|
@ -262,7 +272,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
intent?.data
|
intent?.data
|
||||||
?.let { tryThis { it.getQueryParameter("loginToken") } }
|
?.let { tryOrNull { it.getQueryParameter("loginToken") } }
|
||||||
?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) }
|
?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -748,34 +748,21 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
else -> LoginMode.Unsupported
|
else -> LoginMode.Unsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
|
// FIXME We should post a view event here normally?
|
||||||
notSupported()
|
setState {
|
||||||
} else {
|
copy(
|
||||||
// FIXME We should post a view event here normally?
|
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||||
setState {
|
homeServerUrl = data.homeServerUrl,
|
||||||
copy(
|
loginMode = loginMode,
|
||||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||||
homeServerUrl = data.homeServerUrl,
|
)
|
||||||
loginMode = loginMode,
|
}
|
||||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|
||||||
)
|
|| data.isOutdatedHomeserver) {
|
||||||
}
|
// Notify the UI
|
||||||
|
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is LoginFlowResult.OutdatedHomeserver -> {
|
|
||||||
notSupported()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun notSupported() {
|
|
||||||
// Notify the UI
|
|
||||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
|
||||||
|
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,7 +39,7 @@ import im.vector.app.core.utils.isLocalFile
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -109,7 +109,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||||
fun clear(imageView: ImageView) {
|
fun clear(imageView: ImageView) {
|
||||||
// It can be called after recycler view is destroyed, just silently catch
|
// It can be called after recycler view is destroyed, just silently catch
|
||||||
// We'd better keep ref to requestManager, but we don't have it
|
// We'd better keep ref to requestManager, but we don't have it
|
||||||
tryThis {
|
tryOrNull {
|
||||||
GlideApp
|
GlideApp
|
||||||
.with(imageView).clear(imageView)
|
.with(imageView).clear(imageView)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,9 @@ import im.vector.app.BuildConfig
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import me.gujun.android.span.span
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
import me.gujun.android.span.span
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
@ -72,6 +72,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
private val currentSession: Session?
|
private val currentSession: Session?
|
||||||
get() = activeSessionDataSource.currentValue?.orNull()
|
get() = activeSessionDataSource.currentValue?.orNull()
|
||||||
|
|
||||||
|
private var useCompleteNotificationFormat = vectorPreferences.useCompleteNotificationFormat()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Should be called as soon as a new event is ready to be displayed.
|
Should be called as soon as a new event is ready to be displayed.
|
||||||
The notification corresponding to this event will not be displayed until
|
The notification corresponding to this event will not be displayed until
|
||||||
|
@ -243,8 +245,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
roomEvents.add(event)
|
roomEvents.add(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is InviteNotifiableEvent -> invitationEvents.add(event)
|
is InviteNotifiableEvent -> invitationEvents.add(event)
|
||||||
is SimpleNotifiableEvent -> simpleEvents.add(event)
|
is SimpleNotifiableEvent -> simpleEvents.add(event)
|
||||||
else -> Timber.w("Type not handled")
|
else -> Timber.w("Type not handled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,6 +255,16 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
|
|
||||||
var globalLastMessageTimestamp = 0L
|
var globalLastMessageTimestamp = 0L
|
||||||
|
|
||||||
|
val newSettings = vectorPreferences.useCompleteNotificationFormat()
|
||||||
|
if (newSettings != useCompleteNotificationFormat) {
|
||||||
|
// Settings has changed, remove all current notifications
|
||||||
|
notificationUtils.cancelAllNotifications()
|
||||||
|
useCompleteNotificationFormat = newSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
var simpleNotificationRoomCounter = 0
|
||||||
|
var simpleNotificationMessageCounter = 0
|
||||||
|
|
||||||
// events have been grouped by roomId
|
// events have been grouped by roomId
|
||||||
for ((roomId, events) in roomIdToEventMap) {
|
for ((roomId, events) in roomIdToEventMap) {
|
||||||
// Build the notification for the room
|
// Build the notification for the room
|
||||||
|
@ -263,6 +275,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
simpleNotificationRoomCounter++
|
||||||
val roomName = events[0].roomName ?: events[0].senderName ?: ""
|
val roomName = events[0].roomName ?: events[0].senderName ?: ""
|
||||||
|
|
||||||
val roomEventGroupInfo = RoomEventGroupInfo(
|
val roomEventGroupInfo = RoomEventGroupInfo(
|
||||||
|
@ -303,6 +316,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
roomEventGroupInfo.hasSmartReplyError = true
|
roomEventGroupInfo.hasSmartReplyError = true
|
||||||
} else {
|
} else {
|
||||||
if (!event.isRedacted) {
|
if (!event.isRedacted) {
|
||||||
|
simpleNotificationMessageCounter++
|
||||||
style.addMessage(event.body, event.timestamp, senderPerson)
|
style.addMessage(event.body, event.timestamp, senderPerson)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,16 +375,18 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description)
|
stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description)
|
||||||
}
|
}
|
||||||
|
|
||||||
val notification = notificationUtils.buildMessagesListNotification(
|
if (useCompleteNotificationFormat) {
|
||||||
style,
|
val notification = notificationUtils.buildMessagesListNotification(
|
||||||
roomEventGroupInfo,
|
style,
|
||||||
largeBitmap,
|
roomEventGroupInfo,
|
||||||
lastMessageTimestamp,
|
largeBitmap,
|
||||||
myUserDisplayName,
|
lastMessageTimestamp,
|
||||||
tickerText)
|
myUserDisplayName,
|
||||||
|
tickerText)
|
||||||
|
|
||||||
// is there an id for this room?
|
// is there an id for this room?
|
||||||
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
|
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
|
|
||||||
hasNewEvent = true
|
hasNewEvent = true
|
||||||
summaryIsNoisy = summaryIsNoisy || roomEventGroupInfo.shouldBing
|
summaryIsNoisy = summaryIsNoisy || roomEventGroupInfo.shouldBing
|
||||||
|
@ -383,8 +399,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
for (event in invitationEvents) {
|
for (event in invitationEvents) {
|
||||||
// We build a invitation notification
|
// We build a invitation notification
|
||||||
if (firstTime || !event.hasBeenDisplayed) {
|
if (firstTime || !event.hasBeenDisplayed) {
|
||||||
val notification = notificationUtils.buildRoomInvitationNotification(event, session.myUserId)
|
if (useCompleteNotificationFormat) {
|
||||||
notificationUtils.showNotificationMessage(event.roomId, ROOM_INVITATION_NOTIFICATION_ID, notification)
|
val notification = notificationUtils.buildRoomInvitationNotification(event, session.myUserId)
|
||||||
|
notificationUtils.showNotificationMessage(event.roomId, ROOM_INVITATION_NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
event.hasBeenDisplayed = true // we can consider it as displayed
|
event.hasBeenDisplayed = true // we can consider it as displayed
|
||||||
hasNewEvent = true
|
hasNewEvent = true
|
||||||
summaryIsNoisy = summaryIsNoisy || event.noisy
|
summaryIsNoisy = summaryIsNoisy || event.noisy
|
||||||
|
@ -396,8 +414,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
for (event in simpleEvents) {
|
for (event in simpleEvents) {
|
||||||
// We build a simple notification
|
// We build a simple notification
|
||||||
if (firstTime || !event.hasBeenDisplayed) {
|
if (firstTime || !event.hasBeenDisplayed) {
|
||||||
val notification = notificationUtils.buildSimpleEventNotification(event, session.myUserId)
|
if (useCompleteNotificationFormat) {
|
||||||
notificationUtils.showNotificationMessage(event.eventId, ROOM_EVENT_NOTIFICATION_ID, notification)
|
val notification = notificationUtils.buildSimpleEventNotification(event, session.myUserId)
|
||||||
|
notificationUtils.showNotificationMessage(event.eventId, ROOM_EVENT_NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
event.hasBeenDisplayed = true // we can consider it as displayed
|
event.hasBeenDisplayed = true // we can consider it as displayed
|
||||||
hasNewEvent = true
|
hasNewEvent = true
|
||||||
summaryIsNoisy = summaryIsNoisy || event.noisy
|
summaryIsNoisy = summaryIsNoisy || event.noisy
|
||||||
|
@ -421,19 +441,76 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
if (eventList.isEmpty() || eventList.all { it.isRedacted }) {
|
if (eventList.isEmpty() || eventList.all { it.isRedacted }) {
|
||||||
notificationUtils.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID)
|
notificationUtils.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID)
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME roomIdToEventMap.size is not correct, this is the number of rooms
|
||||||
val nbEvents = roomIdToEventMap.size + simpleEvents.size
|
val nbEvents = roomIdToEventMap.size + simpleEvents.size
|
||||||
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
|
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
|
||||||
summaryInboxStyle.setBigContentTitle(sumTitle)
|
summaryInboxStyle.setBigContentTitle(sumTitle)
|
||||||
// TODO get latest event?
|
// TODO get latest event?
|
||||||
.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents))
|
.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents))
|
||||||
|
|
||||||
val notification = notificationUtils.buildSummaryListNotification(
|
if (useCompleteNotificationFormat) {
|
||||||
summaryInboxStyle,
|
val notification = notificationUtils.buildSummaryListNotification(
|
||||||
sumTitle,
|
summaryInboxStyle,
|
||||||
noisy = hasNewEvent && summaryIsNoisy,
|
sumTitle,
|
||||||
lastMessageTimestamp = globalLastMessageTimestamp)
|
noisy = hasNewEvent && summaryIsNoisy,
|
||||||
|
lastMessageTimestamp = globalLastMessageTimestamp)
|
||||||
|
|
||||||
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
|
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
|
||||||
|
} else {
|
||||||
|
// Add the simple events as message (?)
|
||||||
|
simpleNotificationMessageCounter += simpleEvents.size
|
||||||
|
val numberOfInvitations = invitationEvents.size
|
||||||
|
|
||||||
|
val privacyTitle = if (numberOfInvitations > 0) {
|
||||||
|
val invitationsStr = stringProvider.getQuantityString(R.plurals.notification_invitations, numberOfInvitations, numberOfInvitations)
|
||||||
|
if (simpleNotificationMessageCounter > 0) {
|
||||||
|
// Invitation and message
|
||||||
|
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
|
||||||
|
simpleNotificationMessageCounter, simpleNotificationMessageCounter)
|
||||||
|
if (simpleNotificationRoomCounter > 1) {
|
||||||
|
// In several rooms
|
||||||
|
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
|
||||||
|
simpleNotificationRoomCounter, simpleNotificationRoomCounter)
|
||||||
|
stringProvider.getString(
|
||||||
|
R.string.notification_unread_notified_messages_in_room_and_invitation,
|
||||||
|
messageStr,
|
||||||
|
roomStr,
|
||||||
|
invitationsStr
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// In one room
|
||||||
|
stringProvider.getString(
|
||||||
|
R.string.notification_unread_notified_messages_and_invitation,
|
||||||
|
messageStr,
|
||||||
|
invitationsStr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only invitation
|
||||||
|
invitationsStr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No invitation, only messages
|
||||||
|
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
|
||||||
|
simpleNotificationMessageCounter, simpleNotificationMessageCounter)
|
||||||
|
if (simpleNotificationRoomCounter > 1) {
|
||||||
|
// In several rooms
|
||||||
|
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
|
||||||
|
simpleNotificationRoomCounter, simpleNotificationRoomCounter)
|
||||||
|
stringProvider.getString(R.string.notification_unread_notified_messages_in_room, messageStr, roomStr)
|
||||||
|
} else {
|
||||||
|
// In one room
|
||||||
|
messageStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val notification = notificationUtils.buildSummaryListNotification(
|
||||||
|
style = null,
|
||||||
|
compatSummary = privacyTitle,
|
||||||
|
noisy = hasNewEvent && summaryIsNoisy,
|
||||||
|
lastMessageTimestamp = globalLastMessageTimestamp)
|
||||||
|
|
||||||
|
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
|
|
||||||
if (hasNewEvent && summaryIsNoisy) {
|
if (hasNewEvent && summaryIsNoisy) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -772,7 +772,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
/**
|
/**
|
||||||
* Build the summary notification
|
* Build the summary notification
|
||||||
*/
|
*/
|
||||||
fun buildSummaryListNotification(style: NotificationCompat.InboxStyle,
|
fun buildSummaryListNotification(style: NotificationCompat.InboxStyle?,
|
||||||
compatSummary: String,
|
compatSummary: String,
|
||||||
noisy: Boolean,
|
noisy: Boolean,
|
||||||
lastMessageTimestamp: Long): Notification {
|
lastMessageTimestamp: Long): Notification {
|
||||||
|
|
|
@ -28,7 +28,6 @@ import im.vector.app.core.platform.VectorBaseActivity
|
||||||
class PinActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
|
class PinActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val PIN_REQUEST_CODE = 17890
|
const val PIN_REQUEST_CODE = 17890
|
||||||
|
|
||||||
fun newIntent(context: Context, args: PinArgs): Intent {
|
fun newIntent(context: Context, args: PinArgs): Intent {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.utils.toast
|
import im.vector.app.core.utils.toast
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
import im.vector.app.features.MainActivityArgs
|
import im.vector.app.features.MainActivityArgs
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -42,7 +43,8 @@ data class PinArgs(
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
class PinFragment @Inject constructor(
|
class PinFragment @Inject constructor(
|
||||||
private val pinCodeStore: PinCodeStore
|
private val pinCodeStore: PinCodeStore,
|
||||||
|
private val vectorPreferences: VectorPreferences
|
||||||
) : VectorBaseFragment() {
|
) : VectorBaseFragment() {
|
||||||
|
|
||||||
private val fragmentArgs: PinArgs by args()
|
private val fragmentArgs: PinArgs by args()
|
||||||
|
@ -53,54 +55,10 @@ class PinFragment @Inject constructor(
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
when (fragmentArgs.pinMode) {
|
when (fragmentArgs.pinMode) {
|
||||||
PinMode.CREATE -> showCreateFragment()
|
PinMode.CREATE -> showCreateFragment()
|
||||||
PinMode.DELETE -> showDeleteFragment()
|
|
||||||
PinMode.AUTH -> showAuthFragment()
|
PinMode.AUTH -> showAuthFragment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDeleteFragment() {
|
|
||||||
val encodedPin = pinCodeStore.getEncodedPin() ?: return
|
|
||||||
val authFragment = PFLockScreenFragment()
|
|
||||||
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
|
||||||
.setUseBiometric(pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0)
|
|
||||||
.setTitle(getString(R.string.auth_pin_confirm_to_disable_title))
|
|
||||||
.setClearCodeOnError(true)
|
|
||||||
.setMode(PFFLockScreenConfiguration.MODE_AUTH)
|
|
||||||
authFragment.setConfiguration(builder.build())
|
|
||||||
authFragment.setEncodedPinCode(encodedPin)
|
|
||||||
authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener {
|
|
||||||
override fun onPinLoginFailed() {
|
|
||||||
onWrongPin()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBiometricAuthSuccessful() {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
pinCodeStore.deleteEncodedPin()
|
|
||||||
vectorBaseActivity.setResult(Activity.RESULT_OK)
|
|
||||||
vectorBaseActivity.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBiometricAuthLoginFailed() {
|
|
||||||
val remainingAttempts = pinCodeStore.onWrongBiometrics()
|
|
||||||
if (remainingAttempts <= 0) {
|
|
||||||
// Disable Biometrics
|
|
||||||
builder.setUseBiometric(false)
|
|
||||||
authFragment.setConfiguration(builder.build())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCodeInputSuccessful() {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
pinCodeStore.deleteEncodedPin()
|
|
||||||
vectorBaseActivity.setResult(Activity.RESULT_OK)
|
|
||||||
vectorBaseActivity.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
replaceFragment(R.id.pinFragmentContainer, authFragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showCreateFragment() {
|
private fun showCreateFragment() {
|
||||||
val createFragment = PFLockScreenFragment()
|
val createFragment = PFLockScreenFragment()
|
||||||
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
||||||
|
@ -131,9 +89,8 @@ class PinFragment @Inject constructor(
|
||||||
val authFragment = PFLockScreenFragment()
|
val authFragment = PFLockScreenFragment()
|
||||||
val canUseBiometrics = pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0
|
val canUseBiometrics = pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0
|
||||||
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
||||||
.setUseBiometric(true)
|
|
||||||
.setAutoShowBiometric(true)
|
.setAutoShowBiometric(true)
|
||||||
.setUseBiometric(canUseBiometrics)
|
.setUseBiometric(vectorPreferences.useBiometricsToUnlock() && canUseBiometrics)
|
||||||
.setAutoShowBiometric(canUseBiometrics)
|
.setAutoShowBiometric(canUseBiometrics)
|
||||||
.setTitle(getString(R.string.auth_pin_title))
|
.setTitle(getString(R.string.auth_pin_title))
|
||||||
.setLeftButton(getString(R.string.auth_pin_forgot))
|
.setLeftButton(getString(R.string.auth_pin_forgot))
|
||||||
|
|
|
@ -22,12 +22,14 @@ import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
// 2 minutes, when enabled
|
||||||
private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L
|
private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,24 +37,22 @@ private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L
|
||||||
* It automatically locks when entering background/foreground with a grace period.
|
* It automatically locks when entering background/foreground with a grace period.
|
||||||
* You can force to unlock with unlock method, use it whenever the pin code has been validated.
|
* You can force to unlock with unlock method, use it whenever the pin code has been validated.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : LifecycleObserver {
|
class PinLocker @Inject constructor(
|
||||||
|
private val pinCodeStore: PinCodeStore,
|
||||||
|
private val vectorPreferences: VectorPreferences
|
||||||
|
) : LifecycleObserver {
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
// App is locked, can be unlock
|
// App is locked, can be unlock
|
||||||
LOCKED,
|
LOCKED,
|
||||||
|
|
||||||
// App is blocked and can't be unlocked as long as the app is in foreground
|
// App is unlocked, the app can be used
|
||||||
BLOCKED,
|
|
||||||
|
|
||||||
// is unlocked, the app can be used
|
|
||||||
UNLOCKED
|
UNLOCKED
|
||||||
}
|
}
|
||||||
|
|
||||||
private val liveState = MutableLiveData<State>()
|
private val liveState = MutableLiveData<State>()
|
||||||
|
|
||||||
private var isBlocked = false
|
|
||||||
private var shouldBeLocked = true
|
private var shouldBeLocked = true
|
||||||
private var entersBackgroundTs = 0L
|
private var entersBackgroundTs = 0L
|
||||||
|
|
||||||
|
@ -62,13 +62,13 @@ class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : Li
|
||||||
|
|
||||||
private fun computeState() {
|
private fun computeState() {
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
val state = if (isBlocked) {
|
val state = if (shouldBeLocked && pinCodeStore.hasEncodedPin()) {
|
||||||
State.BLOCKED
|
|
||||||
} else if (shouldBeLocked && pinCodeStore.hasEncodedPin()) {
|
|
||||||
State.LOCKED
|
State.LOCKED
|
||||||
} else {
|
} else {
|
||||||
State.UNLOCKED
|
State.UNLOCKED
|
||||||
}
|
}
|
||||||
|
.also { Timber.v("New state: $it") }
|
||||||
|
|
||||||
if (liveState.value != state) {
|
if (liveState.value != state) {
|
||||||
liveState.postValue(state)
|
liveState.postValue(state)
|
||||||
}
|
}
|
||||||
|
@ -81,23 +81,25 @@ class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : Li
|
||||||
computeState()
|
computeState()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun block() {
|
|
||||||
Timber.v("Block app")
|
|
||||||
isBlocked = true
|
|
||||||
computeState()
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
fun entersForeground() {
|
fun entersForeground() {
|
||||||
val timeElapsedSinceBackground = SystemClock.elapsedRealtime() - entersBackgroundTs
|
val timeElapsedSinceBackground = SystemClock.elapsedRealtime() - entersBackgroundTs
|
||||||
shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= PERIOD_OF_GRACE_IN_MS
|
shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= getGracePeriod()
|
||||||
Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background")
|
Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background shouldBeLocked: $shouldBeLocked")
|
||||||
computeState()
|
computeState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||||
fun entersBackground() {
|
fun entersBackground() {
|
||||||
isBlocked = false
|
Timber.v("App enters background")
|
||||||
entersBackgroundTs = SystemClock.elapsedRealtime()
|
entersBackgroundTs = SystemClock.elapsedRealtime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getGracePeriod(): Long {
|
||||||
|
return if (vectorPreferences.useGracePeriod()) {
|
||||||
|
PERIOD_OF_GRACE_IN_MS
|
||||||
|
} else {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,5 @@ package im.vector.app.features.pin
|
||||||
|
|
||||||
enum class PinMode {
|
enum class PinMode {
|
||||||
CREATE,
|
CREATE,
|
||||||
DELETE,
|
|
||||||
AUTH
|
AUTH
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,13 @@
|
||||||
|
|
||||||
package im.vector.app.features.raw.wellknown
|
package im.vector.app.features.raw.wellknown
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
|
|
||||||
suspend fun RawService.getElementWellknown(userId: String): ElementWellKnown? {
|
suspend fun RawService.getElementWellknown(userId: String): ElementWellKnown? {
|
||||||
return awaitCallback<String> { getWellknown(userId, it) }
|
return tryOrNull { awaitCallback<String> { getWellknown(userId, it) } }
|
||||||
.let { ElementWellKnownMapper.from(it) }
|
?.let { ElementWellKnownMapper.from(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ElementWellKnown.isE2EByDefault() = elementE2E?.e2eDefault ?: riotE2E?.e2eDefault ?: true
|
fun ElementWellKnown.isE2EByDefault() = elementE2E?.e2eDefault ?: riotE2E?.e2eDefault ?: true
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features.raw.wellknown
|
package im.vector.app.features.raw.wellknown
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
|
|
||||||
object ElementWellKnownMapper {
|
object ElementWellKnownMapper {
|
||||||
|
@ -24,6 +25,6 @@ object ElementWellKnownMapper {
|
||||||
val adapter: JsonAdapter<ElementWellKnown> = MoshiProvider.providesMoshi().adapter(ElementWellKnown::class.java)
|
val adapter: JsonAdapter<ElementWellKnown> = MoshiProvider.providesMoshi().adapter(ElementWellKnown::class.java)
|
||||||
|
|
||||||
fun from(value: String): ElementWellKnown? {
|
fun from(value: String): ElementWellKnown? {
|
||||||
return adapter.fromJson(value)
|
return tryOrNull("Unable to parse well-known data") { adapter.fromJson(value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ import im.vector.app.features.roomdirectory.RoomDirectoryActivity
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||||
|
@ -59,7 +59,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
||||||
|
|
||||||
private fun initAdminE2eByDefault() {
|
private fun initAdminE2eByDefault() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
adminE2EByDefault = tryThis {
|
adminE2EByDefault = tryOrNull {
|
||||||
rawService.getElementWellknown(session.myUserId)
|
rawService.getElementWellknown(session.myUserId)
|
||||||
?.isE2EByDefault()
|
?.isE2EByDefault()
|
||||||
?: true
|
?: true
|
||||||
|
|
|
@ -28,7 +28,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.di.DefaultSharedPreferences
|
import im.vector.app.core.di.DefaultSharedPreferences
|
||||||
import im.vector.app.features.homeserver.ServerUrlsRepository
|
import im.vector.app.features.homeserver.ServerUrlsRepository
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -166,6 +166,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
// Security
|
// Security
|
||||||
const val SETTINGS_SECURITY_USE_FLAG_SECURE = "SETTINGS_SECURITY_USE_FLAG_SECURE"
|
const val SETTINGS_SECURITY_USE_FLAG_SECURE = "SETTINGS_SECURITY_USE_FLAG_SECURE"
|
||||||
const val SETTINGS_SECURITY_USE_PIN_CODE_FLAG = "SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
|
const val SETTINGS_SECURITY_USE_PIN_CODE_FLAG = "SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
|
||||||
|
private const val SETTINGS_SECURITY_USE_BIOMETRICS_FLAG = "SETTINGS_SECURITY_USE_BIOMETRICS_FLAG"
|
||||||
|
private const val SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG = "SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG"
|
||||||
|
const val SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG = "SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG"
|
||||||
|
|
||||||
// other
|
// other
|
||||||
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
|
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
|
||||||
|
@ -424,7 +427,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUnknownDeviceDismissedList(): List<String> {
|
fun getUnknownDeviceDismissedList(): List<String> {
|
||||||
return tryThis {
|
return tryOrNull {
|
||||||
defaultPrefs.getStringSet(SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST, null)?.toList()
|
defaultPrefs.getStringSet(SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST, null)?.toList()
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
}
|
}
|
||||||
|
@ -839,14 +842,31 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user enable protecting app access with pin code
|
* The user enable protecting app access with pin code.
|
||||||
|
* Currently we use the pin code store to know if the pin is enabled, so this is not used
|
||||||
*/
|
*/
|
||||||
fun useFlagPinCode(): Boolean {
|
fun useFlagPinCode(): Boolean {
|
||||||
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_PIN_CODE_FLAG, false)
|
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_PIN_CODE_FLAG, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun useBiometricsToUnlock(): Boolean {
|
||||||
|
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_BIOMETRICS_FLAG, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun useGracePeriod(): Boolean {
|
||||||
|
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if Pin code is disabled, or if user set the settings to see full notification content
|
||||||
|
*/
|
||||||
|
fun useCompleteNotificationFormat(): Boolean {
|
||||||
|
return !useFlagPinCode()
|
||||||
|
|| defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG, true)
|
||||||
|
}
|
||||||
|
|
||||||
fun backgroundSyncTimeOut(): Int {
|
fun backgroundSyncTimeOut(): Int {
|
||||||
return tryThis {
|
return tryOrNull {
|
||||||
// The xml pref is saved as a string so use getString and parse
|
// The xml pref is saved as a string so use getString and parse
|
||||||
defaultPrefs.getString(SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, null)?.toInt()
|
defaultPrefs.getString(SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, null)?.toInt()
|
||||||
} ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
} ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
||||||
|
@ -860,7 +880,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun backgroundSyncDelay(): Int {
|
fun backgroundSyncDelay(): Int {
|
||||||
return tryThis {
|
return tryOrNull {
|
||||||
// The xml pref is saved as a string so use getString and parse
|
// The xml pref is saved as a string so use getString and parse
|
||||||
defaultPrefs.getString(SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, null)?.toInt()
|
defaultPrefs.getString(SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, null)?.toInt()
|
||||||
} ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
} ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
||||||
|
|
|
@ -17,6 +17,7 @@ package im.vector.app.features.settings
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
@ -134,6 +135,14 @@ class VectorSettingsActivity : VectorBaseActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T: Fragment> navigateTo(fragmentClass: Class<T>) {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.right_in, R.anim.fade_out, R.anim.fade_in, R.anim.right_out)
|
||||||
|
.replace(R.id.vector_settings_page, fragmentClass, null)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getIntent(context: Context, directAccess: Int) = Intent(context, VectorSettingsActivity::class.java)
|
fun getIntent(context: Context, directAccess: Int) = Intent(context, VectorSettingsActivity::class.java)
|
||||||
.apply { putExtra(EXTRA_DIRECT_ACCESS, directAccess) }
|
.apply { putExtra(EXTRA_DIRECT_ACCESS, directAccess) }
|
||||||
|
|
|
@ -37,7 +37,7 @@ import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
import im.vector.app.push.fcm.FcmHelper
|
import im.vector.app.push.fcm.FcmHelper
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryThis
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleIds
|
import org.matrix.android.sdk.api.pushrules.RuleIds
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleKind
|
import org.matrix.android.sdk.api.pushrules.RuleKind
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -88,7 +88,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut())
|
it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut())
|
||||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
if (newValue is String) {
|
if (newValue is String) {
|
||||||
val syncTimeout = tryThis { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
val syncTimeout = tryOrNull { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
||||||
vectorPreferences.setBackgroundSyncTimeout(maxOf(0, syncTimeout))
|
vectorPreferences.setBackgroundSyncTimeout(maxOf(0, syncTimeout))
|
||||||
refreshBackgroundSyncPrefs()
|
refreshBackgroundSyncPrefs()
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
it.summary = secondsToText(vectorPreferences.backgroundSyncDelay())
|
it.summary = secondsToText(vectorPreferences.backgroundSyncDelay())
|
||||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
if (newValue is String) {
|
if (newValue is String) {
|
||||||
val syncDelay = tryThis { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
val syncDelay = tryOrNull { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
||||||
vectorPreferences.setBackgroundSyncDelay(maxOf(0, syncDelay))
|
vectorPreferences.setBackgroundSyncDelay(maxOf(0, syncDelay))
|
||||||
refreshBackgroundSyncPrefs()
|
refreshBackgroundSyncPrefs()
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue