Integrate Element Call with widget API (#1581)

* Integrate Element Call with widget API.

- Add `appconfig` module and extract constants that can be overridden in forks there.
- Add an Element Call feature flag, disabled by default.
- Refactor the whole `ElementCallActivity`, move most logic out of it.
- Integrate with the Rust Widget Driver API (note the Rust SDK version used in this PR lacks some needed changes to make the calls actually work).
- Handle calls differently based on `CallType`.
- Add UI to create/join a call.

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2023-10-19 17:38:43 +02:00 committed by GitHub
parent a814c4a95a
commit 46f78ef700
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
102 changed files with 2202 additions and 166 deletions

View file

@ -40,6 +40,8 @@ import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
import io.element.android.libraries.matrix.impl.media.map
@ -48,6 +50,8 @@ import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.location.toInner
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.util.destroyAll
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CancellationException
@ -65,6 +69,8 @@ import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
import org.matrix.rustcomponents.sdk.WidgetPermissions
import org.matrix.rustcomponents.sdk.WidgetPermissionsProvider
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import timber.log.Timber
@ -478,6 +484,27 @@ class RustMatrixRoom(
)
}
override suspend fun generateWidgetWebViewUrl(
widgetSettings: MatrixWidgetSettings,
clientId: String,
languageTag: String?,
theme: String?,
) = runCatching {
widgetSettings.generateWidgetWebViewUrl(innerRoom, clientId, languageTag, theme)
}
override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver> = runCatching {
RustWidgetDriver(
widgetSettings = widgetSettings,
room = innerRoom,
widgetPermissionsProvider = object : WidgetPermissionsProvider {
override fun acquirePermissions(permissions: WidgetPermissions): WidgetPermissions {
return permissions
}
},
)
}
private suspend fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
return runCatching {
MediaUploadHandlerImpl(files, handle())

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.widget
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import org.matrix.rustcomponents.sdk.VirtualElementCallWidgetOptions
import org.matrix.rustcomponents.sdk.newVirtualElementCallWidget
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultCallWidgetSettingsProvider @Inject constructor() : CallWidgetSettingsProvider {
override fun provide(baseUrl: String, widgetId: String): MatrixWidgetSettings {
val options = VirtualElementCallWidgetOptions(
elementCallUrl = baseUrl,
widgetId = widgetId,
parentUrl = null,
hideHeader = null,
preload = null,
fontScale = null,
appPrompt = false,
skipLobby = true,
confineToRoom = true,
fonts = null,
analyticsId = null
)
val rustWidgetSettings = newVirtualElementCallWidget(options)
return MatrixWidgetSettings.fromRustWidgetSettings(rustWidgetSettings)
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.widget
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import org.matrix.rustcomponents.sdk.ClientProperties
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.WidgetSettings
import org.matrix.rustcomponents.sdk.generateWebviewUrl
fun MatrixWidgetSettings.toRustWidgetSettings() = WidgetSettings(
id = this.id,
initAfterContentLoad = this.initAfterContentLoad,
rawUrl = this.rawUrl,
)
fun MatrixWidgetSettings.Companion.fromRustWidgetSettings(widgetSettings: WidgetSettings) = MatrixWidgetSettings(
id = widgetSettings.id,
initAfterContentLoad = widgetSettings.initAfterContentLoad,
rawUrl = widgetSettings.rawUrl,
)
suspend fun MatrixWidgetSettings.generateWidgetWebViewUrl(
room: Room,
clientId: String,
languageTag: String? = null,
theme: String? = null
) = generateWebviewUrl(
widgetSettings = this.toRustWidgetSettings(),
room = room,
props = ClientProperties(
clientId = clientId,
languageTag = languageTag,
theme = theme,
)
)

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.widget
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.WidgetPermissionsProvider
import org.matrix.rustcomponents.sdk.makeWidgetDriver
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.coroutineContext
class RustWidgetDriver(
widgetSettings: MatrixWidgetSettings,
private val room: Room,
private val widgetPermissionsProvider: WidgetPermissionsProvider,
): MatrixWidgetDriver {
override val incomingMessages = MutableSharedFlow<String>()
private val driverAndHandle = makeWidgetDriver(widgetSettings.toRustWidgetSettings())
private var receiveMessageJob: Job? = null
private var isRunning = AtomicBoolean(false)
override val id: String = widgetSettings.id
override suspend fun run() {
// Don't run the driver if it's already running
if (!isRunning.compareAndSet(false, true)) {
return
}
val coroutineScope = CoroutineScope(coroutineContext)
coroutineScope.launch {
// This call will suspend the coroutine while the driver is running, so it needs to be launched separately
driverAndHandle.driver.run(room, widgetPermissionsProvider)
}
receiveMessageJob = coroutineScope.launch(Dispatchers.IO) {
try {
while (isActive) {
driverAndHandle.handle.recv()?.let { incomingMessages.emit(it) }
}
} finally {
driverAndHandle.handle.close()
}
}
}
override suspend fun send(message: String) {
driverAndHandle.handle.send(message)
}
override fun close() {
receiveMessageJob?.cancel()
driverAndHandle.driver.close()
}
}