Start creating integration manager
This commit is contained in:
parent
29c9d3070c
commit
182753e4ec
|
@ -22,11 +22,11 @@ import im.vector.matrix.android.api.util.JsonDict
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WidgetContent(
|
||||
@Json(name = "creatorUserId") val creatorUserId: String,
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "type") val type: String,
|
||||
@Json(name = "url") val url: String,
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "data") val data: JsonDict,
|
||||
@Json(name = "creatorUserId") val creatorUserId: String? = null,
|
||||
@Json(name = "id") val id: String? = null,
|
||||
@Json(name = "type") val type: String? = null,
|
||||
@Json(name = "url") val url: String? = null,
|
||||
@Json(name = "name") val name: String? = null,
|
||||
@Json(name = "data") val data: JsonDict = emptyMap(),
|
||||
@Json(name = "waitForIframeLoad") val waitForIframeLoad: Boolean = false
|
||||
)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.matrix.android.internal.extensions
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
|
||||
inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
|
||||
this.observe(owner, Observer { observer(it) })
|
||||
}
|
||||
|
||||
inline fun <T> LiveData<T>.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) {
|
||||
this.observe(owner, Observer { it?.run(observer) })
|
||||
}
|
|
@ -50,6 +50,7 @@ import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
|||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor
|
||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
||||
|
@ -97,6 +98,7 @@ internal class DefaultSession @Inject constructor(
|
|||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||
private val accountService: Lazy<AccountService>,
|
||||
private val timelineEventDecryptor: TimelineEventDecryptor,
|
||||
private val integrationManager: IntegrationManager,
|
||||
private val shieldTrustUpdater: ShieldTrustUpdater)
|
||||
: Session,
|
||||
RoomService by roomService.get(),
|
||||
|
@ -133,6 +135,7 @@ internal class DefaultSession @Inject constructor(
|
|||
eventBus.register(this)
|
||||
timelineEventDecryptor.start()
|
||||
shieldTrustUpdater.start()
|
||||
integrationManager.start()
|
||||
}
|
||||
|
||||
override fun requireBackgroundSync() {
|
||||
|
@ -175,6 +178,7 @@ internal class DefaultSession @Inject constructor(
|
|||
isOpen = false
|
||||
eventBus.unregister(this)
|
||||
shieldTrustUpdater.stop()
|
||||
integrationManager.stop()
|
||||
}
|
||||
|
||||
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||
|
|
|
@ -24,7 +24,7 @@ data class AllowedWidgetsContent(
|
|||
/**
|
||||
* Map of stateEventId to Allowed
|
||||
*/
|
||||
@Json(name = "widgets") val widgets: Map<String, Boolean>,
|
||||
@Json(name = "widgets") val widgets: Map<String, Boolean> = emptyMap(),
|
||||
|
||||
/**
|
||||
* Map of native widgetType to a map of domain to Allowed
|
||||
|
@ -35,5 +35,5 @@ data class AllowedWidgetsContent(
|
|||
* }
|
||||
* }
|
||||
*/
|
||||
@Json(name = "native_widgets") val native: Map<String, Map<String, Boolean>>
|
||||
@Json(name = "native_widgets") val native: Map<String, Map<String, Boolean>> = emptyMap()
|
||||
)
|
||||
|
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* 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.matrix.android.internal.session.integrationmanager
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.widgets.model.WidgetContent
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||
import im.vector.matrix.android.internal.extensions.observeNotNull
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* The integration manager allows to
|
||||
* - Get the Integration Manager that a user has explicitly set for its account (via account data)
|
||||
* - Get the recommended/preferred Integration Manager list as defined by the HomeServer (via wellknown)
|
||||
* - Check if the user has disabled the integration manager feature
|
||||
* - Allow / Disallow Integration manager (propagated to other riot clients)
|
||||
*
|
||||
* The integration manager listen to account data, and can notify observer for changes.
|
||||
*
|
||||
* The wellknown is refreshed at each application fresh start
|
||||
*
|
||||
*/
|
||||
@SessionScope
|
||||
internal class IntegrationManager @Inject constructor(private val taskExecutor: TaskExecutor,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||
private val accountDataDataSource: AccountDataDataSource) {
|
||||
|
||||
interface Listener {
|
||||
fun onIsEnabledChanged(enabled: Boolean) {
|
||||
//No-op
|
||||
}
|
||||
|
||||
fun onConfigurationChanged(config: IntegrationManagerConfig) {
|
||||
//No-op
|
||||
}
|
||||
|
||||
fun onWidgetPermissionsChanged(widgets: Map<String, Boolean>) {
|
||||
//No-op
|
||||
}
|
||||
}
|
||||
|
||||
private var currentConfig: IntegrationManagerConfig? = null
|
||||
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
|
||||
|
||||
private val listeners = HashSet<Listener>()
|
||||
fun addListener(listener: Listener) = synchronized(listeners) { listeners.add(listener) }
|
||||
fun removeListener(listener: Listener) = synchronized(listeners) { listeners.remove(listener) }
|
||||
|
||||
fun start() {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val allowedWidgetsContent = it.getOrNull()?.content?.toModel<AllowedWidgetsContent>()
|
||||
if (allowedWidgetsContent != null) {
|
||||
notifyWidgetPermissionsChanged(allowedWidgetsContent)
|
||||
}
|
||||
}
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val integrationProvisioningContent = it.getOrNull()?.content?.toModel<IntegrationProvisioningContent>()
|
||||
if (integrationProvisioningContent != null) {
|
||||
notifyIsEnabledChanged(integrationProvisioningContent)
|
||||
}
|
||||
}
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_WIDGETS)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val integrationManager = it.getOrNull()?.asIntegrationManagerWidgetContent()
|
||||
val config = integrationManager?.extractIntegrationManagerConfig()
|
||||
if (config != null && config != currentConfig) {
|
||||
currentConfig = config
|
||||
notifyConfigurationChanged(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the user as disabled integration manager feature
|
||||
*/
|
||||
fun isIntegrationEnabled(): Boolean {
|
||||
val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING)
|
||||
val integrationProvisioningContent = integrationProvisioningData?.content?.toModel<IntegrationProvisioningContent>()
|
||||
return integrationProvisioningContent?.enabled ?: false
|
||||
}
|
||||
|
||||
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val isIntegrationEnabled = isIntegrationEnabled()
|
||||
if (enable == isIntegrationEnabled) {
|
||||
callback.onSuccess(Unit)
|
||||
return NoOpCancellable
|
||||
}
|
||||
val integrationProvisioningContent = IntegrationProvisioningContent(enabled = enable)
|
||||
val params = UpdateUserAccountDataTask.IntegrationProvisioning(integrationProvisioningContent = integrationProvisioningContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
val newContent = if (currentContent == null) {
|
||||
val allowedWidget = mapOf(stateEventId to allowed)
|
||||
AllowedWidgetsContent(widgets = allowedWidget, native = emptyMap())
|
||||
} else {
|
||||
val allowedWidgets = currentContent.widgets.toMutableMap().apply {
|
||||
put(stateEventId, allowed)
|
||||
}
|
||||
currentContent.copy(widgets = allowedWidgets)
|
||||
}
|
||||
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun isWidgetAllowed(stateEventId: String): Boolean {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
return currentContent?.widgets?.get(stateEventId) ?: false
|
||||
}
|
||||
|
||||
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
val newContent = if (currentContent == null) {
|
||||
val nativeAllowedWidgets = mapOf(widgetType to mapOf(domain to allowed))
|
||||
AllowedWidgetsContent(widgets = emptyMap(), native = nativeAllowedWidgets)
|
||||
} else {
|
||||
val nativeAllowedWidgets = currentContent.native.toMutableMap().apply {
|
||||
(get(widgetType))?.let {
|
||||
set(widgetType, it.toMutableMap().apply { set(domain, allowed) })
|
||||
} ?: run {
|
||||
set(widgetType, mapOf(domain to allowed))
|
||||
}
|
||||
}
|
||||
currentContent.copy(native = nativeAllowedWidgets)
|
||||
}
|
||||
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
|
||||
return updateUserAccountDataTask
|
||||
.configureWith(params) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
fun isNativeWidgetAllowed(widgetType: String, domain: String?): Boolean {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
return currentContent?.native?.get(widgetType)?.get(domain) ?: false
|
||||
}
|
||||
|
||||
private fun notifyConfigurationChanged(config: IntegrationManagerConfig) {
|
||||
Timber.v("On configuration changed : $config")
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onConfigurationChanged(config)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyWidgetPermissionsChanged(allowedWidgets: AllowedWidgetsContent) {
|
||||
Timber.v("On widget permissions changed: $allowedWidgets")
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onWidgetPermissionsChanged(allowedWidgets.widgets)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyIsEnabledChanged(provisioningContent: IntegrationProvisioningContent) {
|
||||
Timber.v("On provisioningContent changed : $provisioningContent")
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onIsEnabledChanged(provisioningContent.enabled)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Failed to notify listener")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
private fun getStoreWellknownIM(): List<IntegrationManagerConfig> {
|
||||
val prefs = context.getSharedPreferences(PREFS_IM, Context.MODE_PRIVATE)
|
||||
return prefs.getString(WELLKNOWN_KEY, null)?.let {
|
||||
try {
|
||||
Gson().fromJson<List<WellKnownManagerConfig>>(it,
|
||||
object : TypeToken<List<WellKnownManagerConfig>>() {}.type)
|
||||
} catch (any: Throwable) {
|
||||
emptyList<WellKnownManagerConfig>()
|
||||
}
|
||||
} ?: emptyList<WellKnownManagerConfig>()
|
||||
}
|
||||
|
||||
private fun setStoreWellknownIM(list: List<WellKnownManagerConfig>) {
|
||||
val prefs = context.getSharedPreferences(PREFS_IM, Context.MODE_PRIVATE)
|
||||
try {
|
||||
val serialized = Gson().toJson(list)
|
||||
prefs.edit().putString(WELLKNOWN_KEY, serialized).apply()
|
||||
} catch (any: Throwable) {
|
||||
//nop
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
fun getConfig(): IntegrationManagerConfig? {
|
||||
val accountWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_WIDGETS) ?: return null
|
||||
return accountWidgets.asIntegrationManagerWidgetContent()?.extractIntegrationManagerConfig()
|
||||
}
|
||||
|
||||
private fun WidgetContent.extractIntegrationManagerConfig(): IntegrationManagerConfig? {
|
||||
if (url.isNullOrBlank()) {
|
||||
return null
|
||||
}
|
||||
val integrationManagerData = data.toModel<IntegrationManagerWidgetData>()
|
||||
return IntegrationManagerConfig(
|
||||
uiUrl = url,
|
||||
apiUrl = integrationManagerData?.apiUrl ?: url
|
||||
)
|
||||
}
|
||||
|
||||
private fun UserAccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
|
||||
return content.asSequence()
|
||||
.mapNotNull {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(it.value as? Content)?.toModel<WidgetContent>()
|
||||
}.filter {
|
||||
it.type == INTEGRATION_MANAGER_WIDGET
|
||||
}
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val INTEGRATION_MANAGER_WIDGET = "m.integration_manager"
|
||||
private const val PREFS_IM = "IntegrationManager.Storage"
|
||||
private const val WELLKNOWN_KEY = "WellKnown"
|
||||
}
|
||||
}
|
|
@ -18,9 +18,10 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserAccountDataEvent(
|
||||
@Json(name = "type") override val type: String,
|
||||
@Json(name = "content") val content: Map<String, Any>
|
||||
@Json(name = "content") val content: JsonDict
|
||||
) : UserAccountData()
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
|
|||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.AllowedWidgetsContent
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationProvisioningContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
|
@ -59,6 +60,14 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
}
|
||||
}
|
||||
|
||||
data class IntegrationProvisioning(override val type: String = UserAccountData.ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING,
|
||||
private val integrationProvisioningContent: IntegrationProvisioningContent) : Params {
|
||||
|
||||
override fun getData(): Any {
|
||||
return integrationProvisioningContent
|
||||
}
|
||||
}
|
||||
|
||||
data class AnyParams(override val type: String,
|
||||
private val any: Any
|
||||
) : Params {
|
||||
|
|
Loading…
Reference in New Issue