Create a RawService SDK side, to avoid that the SDK manage client needs

This commit is contained in:
Benoit Marty 2020-09-16 16:14:20 +02:00
parent f882986f7d
commit 82bf0dcae9
31 changed files with 741 additions and 87 deletions

View File

@ -19,7 +19,7 @@ Translations 🗣:
-
SDK API changes ⚠️:
-
- Create a new RawService to get plain data from the server.
Build 🧱:
-

View File

@ -24,6 +24,7 @@ import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.network.UserAgentHolder
@ -41,6 +42,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
@Inject internal lateinit var authenticationService: AuthenticationService
@Inject internal lateinit var rawService: RawService
@Inject internal lateinit var userAgentHolder: UserAgentHolder
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@ -61,6 +63,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
return authenticationService
}
fun rawService() = rawService
fun legacySessionImporter(): LegacySessionImporter {
return legacySessionImporter
}

View File

@ -25,6 +25,7 @@ import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.di.DaggerMatrixComponent
import org.matrix.android.sdk.internal.network.UserAgentHolder
@ -42,6 +43,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
@Inject internal lateinit var authenticationService: AuthenticationService
@Inject internal lateinit var rawService: RawService
@Inject internal lateinit var userAgentHolder: UserAgentHolder
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@ -62,6 +64,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
return authenticationService
}
fun rawService() = rawService
fun legacySessionImporter(): LegacySessionImporter {
return legacySessionImporter
}

View File

@ -42,9 +42,6 @@ import org.matrix.android.sdk.api.util.JsonDict
* }
* ]
* }
* "im.vector.riot.jitsi": {
* "preferredDomain": "https://jitsi.riot.im/"
* }
* }
* </pre>
*/
@ -57,24 +54,5 @@ data class WellKnown(
val identityServer: WellKnownBaseConfig? = null,
@Json(name = "m.integrations")
val integrations: JsonDict? = null,
@Json(name = "im.vector.riot.e2ee")
val e2eAdminSetting: E2EWellKnownConfig? = null,
@Json(name = "im.vector.riot.jitsi")
val jitsiServer: WellKnownPreferredConfig? = null
)
@JsonClass(generateAdapter = true)
data class E2EWellKnownConfig(
@Json(name = "default")
val e2eDefault: Boolean = true
)
@JsonClass(generateAdapter = true)
data class WellKnownPreferredConfig(
@Json(name = "preferredDomain")
val preferredDomain: String? = null
val integrations: JsonDict? = null
)

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.api.raw
sealed class RawCacheStrategy {
// Data is always fetched from the server
object NoCache: RawCacheStrategy()
// Once data is retrieved, it is stored for the provided amount of time.
// In case of error, and if strict is set to false, the cache can be returned if available
data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean): RawCacheStrategy()
// Once retrieved, the data is stored in cache and will be always get from the cache
object InfiniteCache: RawCacheStrategy()
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.api.raw
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
/**
* Useful methods to fetch raw data from the server. The access token will not be used to fetched the data
*/
interface RawService {
/**
* Get a URL, either from cache or from the remote server, depending on the cache strategy
*/
fun getUrl(url: String,
rawCacheStrategy: RawCacheStrategy,
matrixCallback: MatrixCallback<String>): Cancelable
/**
* Specific case for the well-known file. Cache validity is 8 hours
*/
fun getWellknown(userId: String, matrixCallback: MatrixCallback<String>): Cancelable
/**
* Clear all the cache data
*/
fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable
}

View File

@ -33,16 +33,7 @@ data class HomeServerCapabilities(
/**
* Default identity server url, provided in Wellknown
*/
val defaultIdentityServerUrl: String? = null,
/**
* Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms
* (as it was before) for various environments where this is desired.
*/
val adminE2EByDefault: Boolean = true,
/**
* Preferred Jitsi domain, provided in Wellknown
*/
val preferredJitsiDomain: String? = null
val defaultIdentityServerUrl: String? = null
) {
companion object {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L

View File

@ -28,7 +28,7 @@ import javax.inject.Inject
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
companion object {
const val SESSION_STORE_SCHEMA_VERSION = 4L
const val SESSION_STORE_SCHEMA_VERSION = 5L
}
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
@ -38,6 +38,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
if (oldVersion <= 1) migrateTo2(realm)
if (oldVersion <= 2) migrateTo3(realm)
if (oldVersion <= 3) migrateTo4(realm)
if (oldVersion <= 4) migrateTo5(realm)
}
private fun migrateTo1(realm: DynamicRealm) {
@ -54,16 +55,16 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
private fun migrateTo2(realm: DynamicRealm) {
Timber.d("Step 1 -> 2")
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, Boolean::class.java)
?.addField("adminE2EByDefault", Boolean::class.java)
?.transform { obj ->
obj.setBoolean(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, true)
obj.setBoolean("adminE2EByDefault", true)
}
}
private fun migrateTo3(realm: DynamicRealm) {
Timber.d("Step 2 -> 3")
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.PREFERRED_JITSI_DOMAIN, String::class.java)
?.addField("preferredJitsiDomain", String::class.java)
?.transform { obj ->
// Schedule a refresh of the capabilities
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
@ -82,4 +83,11 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
.setRequired(PendingThreePidEntityFields.SID, true)
.addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java)
}
private fun migrateTo5(realm: DynamicRealm) {
Timber.d("Step 4 -> 5")
realm.schema.get("HomeServerCapabilitiesEntity")
?.removeField("adminE2EByDefault")
?.removeField("preferredJitsiDomain")
}
}

View File

@ -30,9 +30,7 @@ internal object HomeServerCapabilitiesMapper {
canChangePassword = entity.canChangePassword,
maxUploadFileSize = entity.maxUploadFileSize,
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
adminE2EByDefault = entity.adminE2EByDefault,
preferredJitsiDomain = entity.preferredJitsiDomain
defaultIdentityServerUrl = entity.defaultIdentityServerUrl
)
}
}

View File

@ -17,17 +17,15 @@
package org.matrix.android.sdk.internal.database.model
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import io.realm.RealmObject
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
internal open class HomeServerCapabilitiesEntity(
var canChangePassword: Boolean = true,
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
var lastVersionIdentityServerSupported: Boolean = false,
var defaultIdentityServerUrl: String? = null,
var adminE2EByDefault: Boolean = true,
var lastUpdatedTimestamp: Long = 0L,
var preferredJitsiDomain: String? = null
var lastUpdatedTimestamp: Long = 0L
) : RealmObject() {
companion object

View File

@ -0,0 +1,31 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.database.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
internal open class RawCacheEntity(
@PrimaryKey
var url: String = "",
var data: String = "",
var lastUpdatedTimestamp: Long = 0L
) : RealmObject() {
companion object
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.database.query
import io.realm.Realm
import io.realm.kotlin.createObject
import io.realm.kotlin.where
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
import org.matrix.android.sdk.internal.database.model.RawCacheEntityFields
/**
* Get the current RawCacheEntity, return null if it does not exist
*/
internal fun RawCacheEntity.Companion.get(realm: Realm, url: String): RawCacheEntity? {
return realm.where<RawCacheEntity>()
.equalTo(RawCacheEntityFields.URL, url)
.findFirst()
}
/**
* Get the current RawCacheEntity, create one if it does not exist
*/
internal fun RawCacheEntity.Companion.getOrCreate(realm: Realm, url: String): RawCacheEntity {
return get(realm, url) ?: realm.createObject(url)
}

View File

@ -23,6 +23,10 @@ import javax.inject.Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class AuthDatabase
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class GlobalDatabase
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class SessionDatabase

View File

@ -22,22 +22,30 @@ import android.content.res.Resources
import com.squareup.moshi.Moshi
import dagger.BindsInstance
import dagger.Component
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.AuthModule
import org.matrix.android.sdk.internal.auth.SessionParamsStore
import org.matrix.android.sdk.internal.raw.RawModule
import org.matrix.android.sdk.internal.session.MockHttpInterceptor
import org.matrix.android.sdk.internal.session.TestInterceptor
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import okhttp3.OkHttpClient
import org.matrix.olm.OlmManager
import java.io.File
@Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class, NoOpTestModule::class])
@Component(modules = [
MatrixModule::class,
NetworkModule::class,
AuthModule::class,
RawModule::class,
NoOpTestModule::class
])
@MatrixScope
internal interface MatrixComponent {
@ -53,6 +61,8 @@ internal interface MatrixComponent {
fun authenticationService(): AuthenticationService
fun rawService(): RawService
fun context(): Context
fun matrixConfiguration(): MatrixConfiguration

View File

@ -0,0 +1,41 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.raw
import com.zhuinden.monarchy.Monarchy
import io.realm.kotlin.where
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
import org.matrix.android.sdk.internal.di.GlobalDatabase
import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction
import javax.inject.Inject
internal interface CleanRawCacheTask : Task<Unit, Unit>
internal class DefaultCleanRawCacheTask @Inject constructor(
@GlobalDatabase private val monarchy: Monarchy
) : CleanRawCacheTask {
override suspend fun execute(params: Unit) {
monarchy.awaitTransaction { realm ->
realm.where<RawCacheEntity>()
.findAll()
.deleteAllFromRealm()
}
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.raw
import com.zhuinden.monarchy.Monarchy
import okhttp3.ResponseBody
import org.matrix.android.sdk.api.raw.RawCacheStrategy
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.di.GlobalDatabase
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction
import java.util.Date
import javax.inject.Inject
internal interface GetUrlTask : Task<GetUrlTask.Params, String> {
data class Params(
val url: String,
val rawCacheStrategy: RawCacheStrategy
)
}
internal class DefaultGetUrlTask @Inject constructor(
private val rawAPI: RawAPI,
@GlobalDatabase private val monarchy: Monarchy
) : GetUrlTask {
override suspend fun execute(params: GetUrlTask.Params): String {
return when (params.rawCacheStrategy) {
RawCacheStrategy.NoCache -> doRequest(params.url)
is RawCacheStrategy.TtlCache -> doRequestWithCache(
params.url,
params.rawCacheStrategy.validityDurationInMillis,
params.rawCacheStrategy.strict
)
RawCacheStrategy.InfiniteCache -> doRequestWithCache(
params.url,
Long.MAX_VALUE,
true
)
}
}
private suspend fun doRequest(url: String): String {
return executeRequest<ResponseBody>(null) {
apiCall = rawAPI.getUrl(url)
}
.string()
}
private suspend fun doRequestWithCache(url: String, validityDurationInMillis: Long, strict: Boolean): String {
// Get data from cache
var dataFromCache: String? = null
var isCacheValid = false
monarchy.awaitTransaction { realm ->
val entity = RawCacheEntity.get(realm, url)
dataFromCache = entity?.data
isCacheValid = entity != null && Date().time < entity.lastUpdatedTimestamp + validityDurationInMillis
}
if (dataFromCache != null && isCacheValid) {
return dataFromCache as String
}
// No cache or outdated cache
val data = try {
doRequest(url)
} catch (throwable: Throwable) {
// In case of error, we can return value from cache even if outdated
return dataFromCache
?.takeIf { !strict }
?: throw throwable
}
// Store cache
monarchy.awaitTransaction { realm ->
val rawCacheEntity = RawCacheEntity.getOrCreate(realm, url)
rawCacheEntity.data = data
rawCacheEntity.lastUpdatedTimestamp = Date().time
}
return data
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.raw
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.raw.RawCacheStrategy
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import java.util.concurrent.TimeUnit
import javax.inject.Inject
internal class DefaultRawService @Inject constructor(
private val taskExecutor: TaskExecutor,
private val getUrlTask: GetUrlTask,
private val cleanRawCacheTask: CleanRawCacheTask
) : RawService {
override fun getUrl(url: String,
rawCacheStrategy: RawCacheStrategy,
matrixCallback: MatrixCallback<String>): Cancelable {
return getUrlTask
.configureWith(GetUrlTask.Params(url, rawCacheStrategy)) {
callback = matrixCallback
}
.executeBy(taskExecutor)
}
override fun getWellknown(userId: String,
matrixCallback: MatrixCallback<String>): Cancelable {
val homeServerDomain = userId.substringAfter(":")
return getUrl(
"https://$homeServerDomain/.well-known/matrix/client",
RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false),
matrixCallback
)
}
override fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable {
return cleanRawCacheTask
.configureWith(Unit) {
callback = matrixCallback
}
.executeBy(taskExecutor)
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.raw
import io.realm.annotations.RealmModule
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
/**
* Realm module for global classes
*/
@RealmModule(library = true,
classes = [
RawCacheEntity::class
])
internal class GlobalRealmModule

View File

@ -0,0 +1,29 @@
/*
* Copyright 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.raw
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Url
internal interface RawAPI {
@GET
fun getUrl(@Url url: String): Call<ResponseBody>
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2020 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.raw
import com.zhuinden.monarchy.Monarchy
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import io.realm.RealmConfiguration
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.di.GlobalDatabase
import org.matrix.android.sdk.internal.di.Unauthenticated
import org.matrix.android.sdk.internal.network.RetrofitFactory
@Module
internal abstract class RawModule {
@Module
companion object {
private const val DB_ALIAS = "matrix-sdk-global"
@JvmStatic
@Provides
@GlobalDatabase
fun providesMonarchy(@GlobalDatabase realmConfiguration: RealmConfiguration): Monarchy {
return Monarchy.Builder()
.setRealmConfiguration(realmConfiguration)
.build()
}
@JvmStatic
@Provides
@GlobalDatabase
fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder()
.apply {
realmKeysUtils.configureEncryption(this, DB_ALIAS)
}
.name("matrix-sdk-global.realm")
.modules(GlobalRealmModule())
.build()
}
@Provides
@JvmStatic
fun providesRawAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>,
retrofitFactory: RetrofitFactory): RawAPI {
return retrofitFactory.create(okHttpClient, "https://example.org").create(RawAPI::class.java)
}
}
@Binds
abstract fun bindRawService(service: DefaultRawService): RawService
@Binds
abstract fun bindGetUrlTask(task: DefaultGetUrlTask): GetUrlTask
@Binds
abstract fun bindCleanRawCacheTask(task: DefaultCleanRawCacheTask): CleanRawCacheTask
}

View File

@ -18,6 +18,7 @@
package org.matrix.android.sdk.internal.session.homeserver
import com.zhuinden.monarchy.Monarchy
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@ -32,7 +33,6 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan
import org.matrix.android.sdk.internal.task.Task
import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import java.util.Date
import javax.inject.Inject
@ -109,16 +109,12 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
homeServerCapabilitiesEntity.adminE2EByDefault = getWellknownResult.wellKnown.e2eAdminSetting?.e2eDefault ?: true
homeServerCapabilitiesEntity.preferredJitsiDomain = getWellknownResult.wellKnown.jitsiServer?.preferredDomain
// We are also checking for integration manager configurations
val config = configExtractor.extract(getWellknownResult.wellKnown)
if (config != null) {
Timber.v("Extracted integration config : $config")
realm.insertOrUpdate(config)
}
} else {
homeServerCapabilitiesEntity.adminE2EByDefault = true
}
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
}

View File

@ -57,6 +57,7 @@ import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.ui.UiStateRepository
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import javax.inject.Singleton
@ -118,6 +119,8 @@ interface VectorComponent {
fun authenticationService(): AuthenticationService
fun rawService(): RawService
fun bugReporter(): BugReporter
fun vectorUncaughtExceptionHandler(): VectorUncaughtExceptionHandler

View File

@ -34,6 +34,7 @@ import im.vector.app.features.ui.UiStateRepository
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
@Module
@ -78,6 +79,12 @@ abstract class VectorModule {
fun providesAuthenticationService(matrix: Matrix): AuthenticationService {
return matrix.authenticationService()
}
@Provides
@JvmStatic
fun providesRawService(matrix: Matrix): RawService {
return matrix.rawService()
}
}
@Binds

View File

@ -16,6 +16,7 @@
package im.vector.app.features.createdirect
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
@ -23,13 +24,20 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.homeserver.ElementWellKnownMapper
import im.vector.app.features.homeserver.isE2EByDefault
import im.vector.app.features.userdirectory.PendingInvitee
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
initialState: CreateDirectRoomViewState,
private val rawService: RawService,
private val session: Session)
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
@ -54,22 +62,29 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
}
private fun createRoomAndInviteSelectedUsers(invitees: Set<PendingInvitee>) {
val roomParams = CreateRoomParams()
.apply {
invitees.forEach {
when (it) {
is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId)
is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid)
}.exhaustive
}
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = session.getHomeServerCapabilities().adminE2EByDefault
}
viewModelScope.launch(Dispatchers.IO) {
val adminE2EByDefault = awaitCallback<String> { rawService.getWellknown(session.myUserId, it) }
.let { ElementWellKnownMapper.from(it) }
?.isE2EByDefault()
?: true
session.rx()
.createRoom(roomParams)
.execute {
copy(createAndInviteState = it)
}
val roomParams = CreateRoomParams()
.apply {
invitees.forEach {
when (it) {
is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId)
is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid)
}.exhaustive
}
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
}
session.rx()
.createRoom(roomParams)
.execute {
copy(createAndInviteState = it)
}
}
}
}

View File

@ -42,6 +42,7 @@ import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandle
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder
import im.vector.app.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.app.features.homeserver.ElementWellKnownMapper
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorPreferences
@ -59,11 +60,12 @@ import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.extensions.tryThis
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
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.isAttachmentMessage
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
import org.matrix.android.sdk.api.session.events.model.isTextMessage
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
@ -110,6 +112,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val stringProvider: StringProvider,
private val rainbowGenerator: RainbowGenerator,
private val session: Session,
private val rawService: RawService,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val stickerPickerActionHandler: StickerPickerActionHandler,
private val roomSummaryHolder: RoomSummaryHolder,
@ -349,7 +352,13 @@ class RoomDetailViewModel @AssistedInject constructor(
val roomId: String = room.roomId
val confId = roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.toLowerCase(VectorLocale.applicationLocale)
val jitsiDomain = session.getHomeServerCapabilities().preferredJitsiDomain ?: stringProvider.getString(R.string.preferred_jitsi_domain)
val preferredJitsiDomain = tryThis {
awaitCallback<String> { rawService.getWellknown(session.myUserId, it) }
.let { ElementWellKnownMapper.from(it) }
?.jitsiServer
?.preferredDomain
}
val jitsiDomain = preferredJitsiDomain ?: stringProvider.getString(R.string.preferred_jitsi_domain)
// We use the default element wrapper for this widget
// https://github.com/vector-im/element-web/blob/develop/docs/jitsi-dev.md

View File

@ -16,18 +16,42 @@
package im.vector.app.features.homeserver
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.di.HasScreenInjector
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.userdirectory.KnownUsersFragment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryThis
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.util.awaitCallback
class HomeServerCapabilitiesViewModel(initialState: HomeServerCapabilitiesViewState)
: VectorViewModel<HomeServerCapabilitiesViewState, EmptyAction, EmptyViewEvents>(initialState) {
class HomeServerCapabilitiesViewModel @AssistedInject constructor(
@Assisted initialState: HomeServerCapabilitiesViewState,
private val session: Session,
private val rawService: RawService
) : VectorViewModel<HomeServerCapabilitiesViewState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: HomeServerCapabilitiesViewState): HomeServerCapabilitiesViewModel
}
companion object : MvRxViewModelFactory<HomeServerCapabilitiesViewModel, HomeServerCapabilitiesViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: HomeServerCapabilitiesViewState): HomeServerCapabilitiesViewModel? {
val fragment: KnownUsersFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.homeServerCapabilitiesViewModelFactory.create(state)
}
override fun initialState(viewModelContext: ViewModelContext): HomeServerCapabilitiesViewState? {
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getSafeActiveSession()
@ -37,5 +61,26 @@ class HomeServerCapabilitiesViewModel(initialState: HomeServerCapabilitiesViewSt
}
}
init {
initAdminE2eByDefault()
}
private fun initAdminE2eByDefault() {
viewModelScope.launch(Dispatchers.IO) {
val adminE2EByDefault = tryThis {
awaitCallback<String> { rawService.getWellknown(session.myUserId, it) }
.let { ElementWellKnownMapper.from(it) }
?.isE2EByDefault()
?: true
} ?: true
setState {
copy(
isE2EByDefault = adminE2EByDefault
)
}
}
}
override fun handle(action: EmptyAction) {}
}

View File

@ -20,5 +20,6 @@ import com.airbnb.mvrx.MvRxState
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
data class HomeServerCapabilitiesViewState(
val capabilities: HomeServerCapabilities = HomeServerCapabilities()
val capabilities: HomeServerCapabilities = HomeServerCapabilities(),
val isE2EByDefault: Boolean = true
) : MvRxState

View File

@ -0,0 +1,68 @@
/*
* 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.homeserver
import com.squareup.moshi.Json
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.internal.di.MoshiProvider
@JsonClass(generateAdapter = true)
data class ElementWellKnown(
/**
*Preferred Jitsi domain, provided in Wellknown
*/
@Json(name = "im.vector.riot.jitsi")
val jitsiServer: WellKnownPreferredConfig? = null,
/**
* The settings above were first proposed under a im.vector.riot.e2ee key, which is now deprecated.
* Element will check for either key, preferring io.element.e2ee if both exist.
*/
@Json(name = "io.element.e2ee")
val elementE2E: E2EWellKnownConfig? = null,
@Json(name = "im.vector.riot.e2ee")
val riotE2E: E2EWellKnownConfig? = null
)
object ElementWellKnownMapper {
val adapter: JsonAdapter<ElementWellKnown> = MoshiProvider.providesMoshi().adapter(ElementWellKnown::class.java)
fun from(value: String): ElementWellKnown? {
return adapter.fromJson(value)
}
}
@JsonClass(generateAdapter = true)
data class E2EWellKnownConfig(
/**
* Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms
* (as it was before) for various environments where this is desired.
*/
@Json(name = "default")
val e2eDefault: Boolean? = null
)
@JsonClass(generateAdapter = true)
data class WellKnownPreferredConfig(
@Json(name = "preferredDomain")
val preferredDomain: String? = null
)
fun ElementWellKnown.isE2EByDefault() = elementE2E?.e2eDefault ?: riotE2E?.e2eDefault ?: true

View File

@ -17,6 +17,7 @@
package im.vector.app.features.roomdirectory.createroom
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
@ -27,15 +28,23 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.homeserver.ElementWellKnownMapper
import im.vector.app.features.homeserver.isE2EByDefault
import im.vector.app.features.roomdirectory.RoomDirectoryActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.tryThis
import org.matrix.android.sdk.api.raw.RawService
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.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import org.matrix.android.sdk.internal.util.awaitCallback
class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState,
private val session: Session
private val session: Session,
private val rawService: RawService
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory
@ -44,11 +53,26 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
}
init {
setState {
copy(
isEncrypted = !this.isPublic && session.getHomeServerCapabilities().adminE2EByDefault,
hsAdminHasDisabledE2E = !session.getHomeServerCapabilities().adminE2EByDefault
)
initAdminE2eByDefault()
}
private var adminE2EByDefault = true
private fun initAdminE2eByDefault() {
viewModelScope.launch(Dispatchers.IO) {
adminE2EByDefault = tryThis {
awaitCallback<String> { rawService.getWellknown(session.myUserId, it) }
.let { ElementWellKnownMapper.from(it) }
?.isE2EByDefault()
?: true
} ?: true
setState {
copy(
isEncrypted = !isPublic && adminE2EByDefault,
hsAdminHasDisabledE2E = !adminE2EByDefault
)
}
}
}
@ -81,7 +105,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState {
copy(
isPublic = action.isPublic,
isEncrypted = !action.isPublic && session.getHomeServerCapabilities().adminE2EByDefault
isEncrypted = !action.isPublic && adminE2EByDefault
)
}

View File

@ -51,6 +51,8 @@ import im.vector.app.features.crypto.keys.KeysExporter
import im.vector.app.features.crypto.keys.KeysImporter
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.homeserver.ElementWellKnownMapper
import im.vector.app.features.homeserver.isE2EByDefault
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinCodeStore
@ -151,8 +153,14 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
disposables.add(it)
}
val e2eByDefault = session.getHomeServerCapabilities().adminE2EByDefault
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible = !e2eByDefault
vectorActivity.getVectorComponent()
.rawService()
.getWellknown(session.myUserId, object : MatrixCallback<String> {
override fun onSuccess(data: String) {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =
ElementWellKnownMapper.from(data)?.isE2EByDefault() == false
}
})
}
private val secureBackupCategory by lazy {
@ -273,8 +281,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
text = getString(R.string.settings_hs_admin_e2e_disabled)
textColor = ContextCompat.getColor(requireContext(), R.color.riotx_destructive_accent)
}
it.isVisible = session.getHomeServerCapabilities().adminE2EByDefault
}
}

View File

@ -29,7 +29,6 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.chip.Chip
import com.jakewharton.rxbinding3.widget.textChanges
import org.matrix.android.sdk.api.session.user.model.User
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
@ -39,12 +38,14 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
import kotlinx.android.synthetic.main.fragment_known_users.*
import org.matrix.android.sdk.api.session.user.model.User
import javax.inject.Inject
class KnownUsersFragment @Inject constructor(
val userDirectoryViewModelFactory: UserDirectoryViewModel.Factory,
private val knownUsersController: KnownUsersController,
private val dimensionConverter: DimensionConverter
private val dimensionConverter: DimensionConverter,
val homeServerCapabilitiesViewModelFactory: HomeServerCapabilitiesViewModel.Factory
) : VectorBaseFragment(), KnownUsersController.Callback {
private val args: KnownUsersFragmentArgs by args()
@ -71,7 +72,7 @@ class KnownUsersFragment @Inject constructor(
setupCloseView()
homeServerCapabilitiesViewModel.subscribe {
knownUsersE2EbyDefaultDisabled.isVisible = !it.capabilities.adminE2EByDefault
knownUsersE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
}
viewModel.selectSubscribe(this, UserDirectoryViewState::pendingInvitees) {