mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Merge pull request #180 from vector-im/feature/fix_timeline
Request can now be canceled properly
This commit is contained in:
commit
7e9275831b
18 changed files with 224 additions and 158 deletions
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.api.failure
|
package im.vector.matrix.android.api.failure
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +36,8 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
||||||
|
|
||||||
|
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
||||||
|
|
||||||
data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString()))
|
data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString()))
|
||||||
|
|
||||||
abstract class FeatureFailure : Failure()
|
abstract class FeatureFailure : Failure()
|
||||||
|
|
|
@ -36,7 +36,9 @@ interface CryptoService {
|
||||||
|
|
||||||
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun deleteDevice(deviceId: String, accountPassword: String, callback: MatrixCallback<Unit>)
|
fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass
|
||||||
* An interactive authentication flow.
|
* An interactive authentication flow.
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class InteractiveAuthenticationFlow(
|
data class InteractiveAuthenticationFlow(
|
||||||
|
|
||||||
@Json(name = "type")
|
@Json(name = "type")
|
||||||
val type: String? = null,
|
val type: String? = null,
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.data
|
package im.vector.matrix.android.internal.auth.data
|
||||||
|
|
||||||
internal object LoginFlowTypes {
|
object LoginFlowTypes {
|
||||||
const val PASSWORD = "m.login.password"
|
const val PASSWORD = "m.login.password"
|
||||||
const val OAUTH2 = "m.login.oauth2"
|
const val OAUTH2 = "m.login.oauth2"
|
||||||
const val EMAIL_CODE = "m.login.email.code"
|
const val EMAIL_CODE = "m.login.email.code"
|
||||||
|
|
|
@ -22,7 +22,7 @@ import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
|
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class RegistrationFlowResponse(
|
data class RegistrationFlowResponse(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of flows.
|
* The list of flows.
|
||||||
|
|
|
@ -55,6 +55,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.*
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
||||||
|
@ -124,6 +125,7 @@ internal class CryptoManager(
|
||||||
private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
|
private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
|
||||||
private val olmEncryptionFactory: MXOlmEncryptionFactory,
|
private val olmEncryptionFactory: MXOlmEncryptionFactory,
|
||||||
private val deleteDeviceTask: DeleteDeviceTask,
|
private val deleteDeviceTask: DeleteDeviceTask,
|
||||||
|
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
||||||
// Tasks
|
// Tasks
|
||||||
private val getDevicesTask: GetDevicesTask,
|
private val getDevicesTask: GetDevicesTask,
|
||||||
private val setDeviceNameTask: SetDeviceNameTask,
|
private val setDeviceNameTask: SetDeviceNameTask,
|
||||||
|
@ -163,9 +165,16 @@ internal class CryptoManager(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDevice(deviceId: String, accountPassword: String, callback: MatrixCallback<Unit>) {
|
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||||
deleteDeviceTask
|
deleteDeviceTask
|
||||||
.configureWith(DeleteDeviceTask.Params(deviceId, accountPassword))
|
.configureWith(DeleteDeviceTask.Params(deviceId))
|
||||||
|
.dispatchTo(callback)
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
|
||||||
|
deleteDeviceWithUserPasswordTask
|
||||||
|
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password))
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,6 +193,7 @@ internal class CryptoModule {
|
||||||
megolmEncryptionFactory = get(),
|
megolmEncryptionFactory = get(),
|
||||||
olmEncryptionFactory = get(),
|
olmEncryptionFactory = get(),
|
||||||
deleteDeviceTask = get(),
|
deleteDeviceTask = get(),
|
||||||
|
deleteDeviceWithUserPasswordTask = get(),
|
||||||
// Tasks
|
// Tasks
|
||||||
getDevicesTask = get(),
|
getDevicesTask = get(),
|
||||||
setDeviceNameTask = get(),
|
setDeviceNameTask = get(),
|
||||||
|
@ -227,7 +228,10 @@ internal class CryptoModule {
|
||||||
DefaultClaimOneTimeKeysForUsersDevice(get()) as ClaimOneTimeKeysForUsersDeviceTask
|
DefaultClaimOneTimeKeysForUsersDevice(get()) as ClaimOneTimeKeysForUsersDeviceTask
|
||||||
}
|
}
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
DefaultDeleteDeviceTask(get(), get()) as DeleteDeviceTask
|
DefaultDeleteDeviceTask(get()) as DeleteDeviceTask
|
||||||
|
}
|
||||||
|
scope(DefaultSession.SCOPE) {
|
||||||
|
DefaultDeleteDeviceWithUserPasswordTask(get(), get()) as DeleteDeviceWithUserPasswordTask
|
||||||
}
|
}
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
DefaultDownloadKeysForUsers(get()) as DownloadKeysForUsersTask
|
DefaultDownloadKeysForUsers(get()) as DownloadKeysForUsersTask
|
||||||
|
|
|
@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
|
||||||
* This class provides the authentication data to delete a device
|
* This class provides the authentication data to delete a device
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class DeleteDeviceAuth(
|
internal data class DeleteDeviceAuth(
|
||||||
|
|
||||||
// device device session id
|
// device device session id
|
||||||
@Json(name = "session")
|
@Json(name = "session")
|
||||||
|
|
|
@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
|
||||||
* This class provides the parameter to delete a device
|
* This class provides the parameter to delete a device
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class DeleteDeviceParams(
|
internal data class DeleteDeviceParams(
|
||||||
@Json(name = "auth")
|
@Json(name = "auth")
|
||||||
var deleteDeviceAuth: DeleteDeviceAuth? = null
|
var deleteDeviceAuth: DeleteDeviceAuth? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,27 +19,21 @@ package im.vector.matrix.android.internal.crypto.tasks
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import arrow.core.failure
|
import arrow.core.failure
|
||||||
import arrow.core.recoverWith
|
import arrow.core.recoverWith
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
|
||||||
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
|
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val deviceId: String,
|
val deviceId: String
|
||||||
val accountPassword: String
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultDeleteDeviceTask(private val cryptoApi: CryptoApi,
|
internal class DefaultDeleteDeviceTask(private val cryptoApi: CryptoApi)
|
||||||
private val credentials: Credentials)
|
|
||||||
: DeleteDeviceTask {
|
: DeleteDeviceTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeleteDeviceTask.Params): Try<Unit> {
|
override suspend fun execute(params: DeleteDeviceTask.Params): Try<Unit> {
|
||||||
|
@ -47,28 +41,18 @@ internal class DefaultDeleteDeviceTask(private val cryptoApi: CryptoApi,
|
||||||
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
|
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
|
||||||
}.recoverWith { throwable ->
|
}.recoverWith { throwable ->
|
||||||
if (throwable is Failure.OtherServerError && throwable.httpCode == 401) {
|
if (throwable is Failure.OtherServerError && throwable.httpCode == 401) {
|
||||||
// Replay the request with passing the credentials
|
|
||||||
|
|
||||||
// Parse to get a RegistrationFlowResponse
|
// Parse to get a RegistrationFlowResponse
|
||||||
val registrationFlowResponseAdapter = MoshiProvider.providesMoshi().adapter(RegistrationFlowResponse::class.java)
|
|
||||||
val registrationFlowResponse = try {
|
val registrationFlowResponse = try {
|
||||||
registrationFlowResponseAdapter.fromJson(throwable.errorBody)
|
MoshiProvider.providesMoshi()
|
||||||
|
.adapter(RegistrationFlowResponse::class.java)
|
||||||
|
.fromJson(throwable.errorBody)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the server response can be casted
|
// check if the server response can be casted
|
||||||
if (registrationFlowResponse?.flows?.isNotEmpty() == true) {
|
if (registrationFlowResponse != null) {
|
||||||
val stages = ArrayList<String>()
|
Failure.RegistrationFlowError(registrationFlowResponse).failure()
|
||||||
|
|
||||||
// Get all stages
|
|
||||||
registrationFlowResponse.flows?.forEach {
|
|
||||||
stages.addAll(it.stages ?: emptyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.v("## deleteDevice() : supported stages $stages")
|
|
||||||
|
|
||||||
deleteDeviceRecursive(registrationFlowResponse.session, params, stages)
|
|
||||||
} else {
|
} else {
|
||||||
throwable.failure()
|
throwable.failure()
|
||||||
}
|
}
|
||||||
|
@ -79,43 +63,4 @@ internal class DefaultDeleteDeviceTask(private val cryptoApi: CryptoApi,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteDeviceRecursive(authSession: String?,
|
|
||||||
params: DeleteDeviceTask.Params,
|
|
||||||
remainingStages: MutableList<String>): Try<Unit> {
|
|
||||||
// Pick the first stage
|
|
||||||
val stage = remainingStages.first()
|
|
||||||
|
|
||||||
val newParams = DeleteDeviceParams()
|
|
||||||
.apply {
|
|
||||||
deleteDeviceAuth = DeleteDeviceAuth()
|
|
||||||
.apply {
|
|
||||||
type = stage
|
|
||||||
session = authSession
|
|
||||||
user = credentials.userId
|
|
||||||
password = params.accountPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return executeRequest<Unit> {
|
|
||||||
apiCall = cryptoApi.deleteDevice(params.deviceId, newParams)
|
|
||||||
}.recoverWith { throwable ->
|
|
||||||
if (throwable is Failure.ServerError
|
|
||||||
&& throwable.httpCode == 401
|
|
||||||
&& (throwable.error.code == MatrixError.FORBIDDEN || throwable.error.code == MatrixError.UNKNOWN)) {
|
|
||||||
if (remainingStages.size > 1) {
|
|
||||||
// Try next stage
|
|
||||||
val otherStages = remainingStages.subList(1, remainingStages.size)
|
|
||||||
|
|
||||||
deleteDeviceRecursive(authSession, params, otherStages)
|
|
||||||
} else {
|
|
||||||
// No more stage remaining
|
|
||||||
throwable.failure()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Other error
|
|
||||||
throwable.failure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.tasks
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
|
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
|
||||||
|
internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserPasswordTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val deviceId: String,
|
||||||
|
val authSession: String?,
|
||||||
|
val password: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultDeleteDeviceWithUserPasswordTask(private val cryptoApi: CryptoApi,
|
||||||
|
private val credentials: Credentials)
|
||||||
|
: DeleteDeviceWithUserPasswordTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params): Try<Unit> {
|
||||||
|
return executeRequest {
|
||||||
|
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()
|
||||||
|
.apply {
|
||||||
|
deleteDeviceAuth = DeleteDeviceAuth()
|
||||||
|
.apply {
|
||||||
|
type = LoginFlowTypes.PASSWORD
|
||||||
|
session = params.authSession
|
||||||
|
user = credentials.userId
|
||||||
|
password = params.password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,34 +28,45 @@ import com.squareup.moshi.Moshi
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
internal inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute()
|
internal suspend inline fun <DATA> executeRequest(block: Request<DATA>.() -> Unit) = Request<DATA>().apply(block).execute()
|
||||||
|
|
||||||
internal class Request<DATA> {
|
internal class Request<DATA> {
|
||||||
|
|
||||||
private val moshi: Moshi = MoshiProvider.providesMoshi()
|
private val moshi: Moshi = MoshiProvider.providesMoshi()
|
||||||
lateinit var apiCall: Call<DATA>
|
lateinit var apiCall: Call<DATA>
|
||||||
|
|
||||||
fun execute(): Try<DATA> {
|
suspend fun execute(): Try<DATA> {
|
||||||
return Try {
|
return suspendCancellableCoroutine { continuation ->
|
||||||
val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync()
|
continuation.invokeOnCancellation {
|
||||||
if (response.isSuccessful) {
|
Timber.v("Request is canceled")
|
||||||
response.body() ?: throw IllegalStateException("The request returned a null body")
|
apiCall.cancel()
|
||||||
} else {
|
|
||||||
throw manageFailure(response.errorBody(), response.code())
|
|
||||||
}
|
}
|
||||||
}.recoverWith {
|
val result = Try {
|
||||||
when (it) {
|
val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync()
|
||||||
is IOException -> Failure.NetworkConnection(it)
|
if (response.isSuccessful) {
|
||||||
is Failure.ServerError,
|
response.body()
|
||||||
is Failure.OtherServerError -> it
|
?: throw IllegalStateException("The request returned a null body")
|
||||||
else -> Failure.Unknown(it)
|
} else {
|
||||||
}.failure()
|
throw manageFailure(response.errorBody(), response.code())
|
||||||
|
}
|
||||||
|
}.recoverWith {
|
||||||
|
when (it) {
|
||||||
|
is IOException -> Failure.NetworkConnection(it)
|
||||||
|
is Failure.ServerError,
|
||||||
|
is Failure.OtherServerError -> it
|
||||||
|
else -> Failure.Unknown(it)
|
||||||
|
}.failure()
|
||||||
|
}
|
||||||
|
continuation.resume(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun manageFailure(errorBody: ResponseBody?, httpCode: Int): Throwable {
|
private fun manageFailure(errorBody: ResponseBody?, httpCode: Int): Throwable {
|
||||||
|
|
|
@ -290,8 +290,12 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
|
||||||
cryptoService.setDeviceName(deviceId, deviceName, callback)
|
cryptoService.setDeviceName(deviceId, deviceName, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDevice(deviceId: String, accountPassword: String, callback: MatrixCallback<Unit>) {
|
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||||
cryptoService.deleteDevice(deviceId, accountPassword, callback)
|
cryptoService.deleteDevice(deviceId, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
|
||||||
|
cryptoService.deleteDeviceWithUserPassword(deviceId, authSession, password, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
|
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
|
||||||
|
|
|
@ -21,13 +21,13 @@ import arrow.core.fix
|
||||||
import arrow.instances.`try`.monad.monad
|
import arrow.instances.`try`.monad.monad
|
||||||
import arrow.typeclasses.binding
|
import arrow.typeclasses.binding
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.internal.task.Task
|
|
||||||
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.group.model.GroupRooms
|
import im.vector.matrix.android.internal.session.group.model.GroupRooms
|
||||||
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
|
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
|
||||||
import im.vector.matrix.android.internal.session.group.model.GroupUsers
|
import im.vector.matrix.android.internal.session.group.model.GroupUsers
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.tryTransactionSync
|
import im.vector.matrix.android.internal.util.tryTransactionSync
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
|
|
||||||
|
@ -45,21 +45,17 @@ internal class DefaultGetGroupDataTask(
|
||||||
|
|
||||||
override suspend fun execute(params: GetGroupDataTask.Params): Try<Unit> {
|
override suspend fun execute(params: GetGroupDataTask.Params): Try<Unit> {
|
||||||
val groupId = params.groupId
|
val groupId = params.groupId
|
||||||
|
val groupSummary = executeRequest<GroupSummaryResponse> {
|
||||||
|
apiCall = groupAPI.getSummary(groupId)
|
||||||
|
}
|
||||||
|
val groupRooms = executeRequest<GroupRooms> {
|
||||||
|
apiCall = groupAPI.getRooms(groupId)
|
||||||
|
}
|
||||||
|
val groupUsers = executeRequest<GroupUsers> {
|
||||||
|
apiCall = groupAPI.getUsers(groupId)
|
||||||
|
}
|
||||||
return Try.monad().binding {
|
return Try.monad().binding {
|
||||||
|
insertInDb(groupSummary.bind(), groupRooms.bind(), groupUsers.bind(), groupId).bind()
|
||||||
val groupSummary = executeRequest<GroupSummaryResponse> {
|
|
||||||
apiCall = groupAPI.getSummary(groupId)
|
|
||||||
}.bind()
|
|
||||||
|
|
||||||
val groupRooms = executeRequest<GroupRooms> {
|
|
||||||
apiCall = groupAPI.getRooms(groupId)
|
|
||||||
}.bind()
|
|
||||||
|
|
||||||
val groupUsers = executeRequest<GroupUsers> {
|
|
||||||
apiCall = groupAPI.getUsers(groupId)
|
|
||||||
}.bind()
|
|
||||||
|
|
||||||
insertInDb(groupSummary, groupRooms, groupUsers, groupId).bind()
|
|
||||||
}.fix()
|
}.fix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,12 +67,13 @@ internal class DefaultGetGroupDataTask(
|
||||||
return monarchy
|
return monarchy
|
||||||
.tryTransactionSync { realm ->
|
.tryTransactionSync { realm ->
|
||||||
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
|
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
|
||||||
?: realm.createObject(groupId)
|
?: realm.createObject(groupId)
|
||||||
|
|
||||||
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
|
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
|
||||||
val name = groupSummary.profile?.name
|
val name = groupSummary.profile?.name
|
||||||
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
|
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
|
||||||
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: ""
|
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription
|
||||||
|
?: ""
|
||||||
|
|
||||||
val roomIds = groupRooms.rooms.map { it.roomId }
|
val roomIds = groupRooms.rooms.map { it.roomId }
|
||||||
groupSummaryEntity.roomIds.clear()
|
groupSummaryEntity.roomIds.clear()
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package im.vector.matrix.android.internal.session.room.relation
|
package im.vector.matrix.android.internal.session.room.relation
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
@ -32,7 +33,7 @@ import im.vector.matrix.android.internal.util.WorkerParamsFactory
|
||||||
import org.koin.standalone.inject
|
import org.koin.standalone.inject
|
||||||
|
|
||||||
class SendRelationWorker(context: Context, params: WorkerParameters)
|
class SendRelationWorker(context: Context, params: WorkerParameters)
|
||||||
: Worker(context, params), MatrixKoinComponent {
|
: CoroutineWorker(context, params), MatrixKoinComponent {
|
||||||
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@ -44,7 +45,7 @@ class SendRelationWorker(context: Context, params: WorkerParameters)
|
||||||
|
|
||||||
private val roomAPI by inject<RoomAPI>()
|
private val roomAPI by inject<RoomAPI>()
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||||
?: return Result.failure()
|
?: return Result.failure()
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
@ -27,7 +28,7 @@ import im.vector.matrix.android.internal.util.WorkerParamsFactory
|
||||||
import org.koin.standalone.inject
|
import org.koin.standalone.inject
|
||||||
|
|
||||||
internal class RedactEventWorker(context: Context, params: WorkerParameters)
|
internal class RedactEventWorker(context: Context, params: WorkerParameters)
|
||||||
: Worker(context, params), MatrixKoinComponent {
|
: CoroutineWorker(context, params), MatrixKoinComponent {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
|
@ -39,7 +40,7 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters)
|
||||||
|
|
||||||
private val roomAPI by inject<RoomAPI>()
|
private val roomAPI by inject<RoomAPI>()
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||||
?: return Result.failure()
|
?: return Result.failure()
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
@ -30,7 +31,7 @@ import im.vector.matrix.android.internal.util.WorkerParamsFactory
|
||||||
import org.koin.standalone.inject
|
import org.koin.standalone.inject
|
||||||
|
|
||||||
internal class SendEventWorker(context: Context, params: WorkerParameters)
|
internal class SendEventWorker(context: Context, params: WorkerParameters)
|
||||||
: Worker(context, params), MatrixKoinComponent {
|
: CoroutineWorker(context, params), MatrixKoinComponent {
|
||||||
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@ -42,7 +43,7 @@ internal class SendEventWorker(context: Context, params: WorkerParameters)
|
||||||
private val roomAPI by inject<RoomAPI>()
|
private val roomAPI by inject<RoomAPI>()
|
||||||
private val localEchoUpdater by inject<LocalEchoUpdater>()
|
private val localEchoUpdater by inject<LocalEchoUpdater>()
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
|
|
||||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||||
?: return Result.success()
|
?: return Result.success()
|
||||||
|
|
|
@ -46,7 +46,9 @@ import com.google.android.material.textfield.TextInputLayout
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable
|
import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable
|
||||||
import im.vector.matrix.android.api.extensions.sortByLastSeen
|
import im.vector.matrix.android.api.extensions.sortByLastSeen
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
|
@ -2413,7 +2415,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||||
|
|
||||||
// disable the deletion for our own device
|
// disable the deletion for our own device
|
||||||
if (!TextUtils.equals(mSession.getMyDevice()?.deviceId, aDeviceInfo.deviceId)) {
|
if (!TextUtils.equals(mSession.getMyDevice()?.deviceId, aDeviceInfo.deviceId)) {
|
||||||
builder.setNegativeButton(R.string.delete) { _, _ -> displayDeviceDeletionDialog(aDeviceInfo) }
|
builder.setNegativeButton(R.string.delete) { _, _ -> deleteDevice(aDeviceInfo) }
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setNeutralButton(R.string.cancel, null)
|
builder.setNeutralButton(R.string.cancel, null)
|
||||||
|
@ -2486,11 +2488,17 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||||
/**
|
/**
|
||||||
* Try to delete a device.
|
* Try to delete a device.
|
||||||
*
|
*
|
||||||
* @param deviceId the device id
|
* @param deviceInfo the device to delete
|
||||||
*/
|
*/
|
||||||
private fun deleteDevice(deviceId: String) {
|
private fun deleteDevice(deviceInfo: DeviceInfo) {
|
||||||
|
val deviceId = deviceInfo.deviceId
|
||||||
|
if (deviceId == null) {
|
||||||
|
Timber.e("## displayDeviceDeletionDialog(): sanity check failure")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
mSession.deleteDevice(deviceId, mAccountPassword, object : MatrixCallback<Unit> {
|
mSession.deleteDevice(deviceId, object : MatrixCallback<Unit> {
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
hideLoadingView()
|
hideLoadingView()
|
||||||
// force settings update
|
// force settings update
|
||||||
|
@ -2498,59 +2506,85 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
mAccountPassword = ""
|
var isPasswordRequestFound = false
|
||||||
onCommonDone(failure.localizedMessage)
|
|
||||||
|
if (failure is Failure.RegistrationFlowError) {
|
||||||
|
// We only support LoginFlowTypes.PASSWORD
|
||||||
|
// Check if we can provide the user password
|
||||||
|
failure.registrationFlowResponse.flows?.forEach { interactiveAuthenticationFlow ->
|
||||||
|
isPasswordRequestFound = isPasswordRequestFound || interactiveAuthenticationFlow.stages?.any { it == LoginFlowTypes.PASSWORD } == true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPasswordRequestFound) {
|
||||||
|
maybeShowDeleteDeviceWithPasswordDialog(deviceId, failure.registrationFlowResponse.session)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPasswordRequestFound) {
|
||||||
|
// LoginFlowTypes.PASSWORD not supported, and this is the only one RiotX supports so far...
|
||||||
|
onCommonDone(failure.localizedMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a delete confirmation dialog to remove a device.<br></br>
|
* Show a dialog to ask for user password, or use a previously entered password.
|
||||||
* The user is invited to enter his password to confirm the deletion.
|
|
||||||
*
|
|
||||||
* @param aDeviceInfoToDelete device info
|
|
||||||
*/
|
*/
|
||||||
private fun displayDeviceDeletionDialog(aDeviceInfoToDelete: DeviceInfo) {
|
private fun maybeShowDeleteDeviceWithPasswordDialog(deviceId: String, authSession: String?) {
|
||||||
if (aDeviceInfoToDelete.deviceId != null) {
|
if (!TextUtils.isEmpty(mAccountPassword)) {
|
||||||
if (!TextUtils.isEmpty(mAccountPassword)) {
|
deleteDeviceWithPassword(deviceId, authSession, mAccountPassword)
|
||||||
deleteDevice(aDeviceInfoToDelete.deviceId!!)
|
|
||||||
} else {
|
|
||||||
activity?.let {
|
|
||||||
val inflater = it.layoutInflater
|
|
||||||
val layout = inflater.inflate(R.layout.dialog_device_delete, null)
|
|
||||||
val passwordEditText = layout.findViewById<EditText>(R.id.delete_password)
|
|
||||||
|
|
||||||
AlertDialog.Builder(it)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
||||||
.setTitle(R.string.devices_delete_dialog_title)
|
|
||||||
.setView(layout)
|
|
||||||
.setPositiveButton(R.string.devices_delete_submit_button_label, DialogInterface.OnClickListener { _, _ ->
|
|
||||||
if (TextUtils.isEmpty(passwordEditText.toString())) {
|
|
||||||
it.toast(R.string.error_empty_field_your_password)
|
|
||||||
return@OnClickListener
|
|
||||||
}
|
|
||||||
mAccountPassword = passwordEditText.text.toString()
|
|
||||||
deleteDevice(aDeviceInfoToDelete.deviceId!!)
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
|
||||||
hideLoadingView()
|
|
||||||
}
|
|
||||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
|
||||||
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
dialog.cancel()
|
|
||||||
hideLoadingView()
|
|
||||||
return@OnKeyListener true
|
|
||||||
}
|
|
||||||
false
|
|
||||||
})
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## displayDeviceDeletionDialog(): sanity check failure")
|
activity?.let {
|
||||||
|
val inflater = it.layoutInflater
|
||||||
|
val layout = inflater.inflate(R.layout.dialog_device_delete, null)
|
||||||
|
val passwordEditText = layout.findViewById<EditText>(R.id.delete_password)
|
||||||
|
|
||||||
|
AlertDialog.Builder(it)
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setTitle(R.string.devices_delete_dialog_title)
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton(R.string.devices_delete_submit_button_label, DialogInterface.OnClickListener { _, _ ->
|
||||||
|
if (TextUtils.isEmpty(passwordEditText.toString())) {
|
||||||
|
it.toast(R.string.error_empty_field_your_password)
|
||||||
|
return@OnClickListener
|
||||||
|
}
|
||||||
|
mAccountPassword = passwordEditText.text.toString()
|
||||||
|
deleteDeviceWithPassword(deviceId, authSession, mAccountPassword)
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||||
|
hideLoadingView()
|
||||||
|
}
|
||||||
|
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
||||||
|
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
dialog.cancel()
|
||||||
|
hideLoadingView()
|
||||||
|
return@OnKeyListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deleteDeviceWithPassword(deviceId: String, authSession: String?, accountPassword: String) {
|
||||||
|
mSession.deleteDeviceWithUserPassword(deviceId, authSession, accountPassword, object : MatrixCallback<Unit> {
|
||||||
|
override fun onSuccess(data: Unit) {
|
||||||
|
hideLoadingView()
|
||||||
|
// force settings update
|
||||||
|
refreshDevicesList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
// Password is maybe not good
|
||||||
|
onCommonDone(failure.localizedMessage)
|
||||||
|
mAccountPassword = ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage the e2e keys export.
|
* Manage the e2e keys export.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue