Skip to content

Commit

Permalink
Merge pull request #3452 from vector-im/feature/fga/voip_asserted_ide…
Browse files Browse the repository at this point in the history
…ntity

Feature/fga/voip asserted identity
  • Loading branch information
bmarty authored Jun 11, 2021
2 parents 4b92f74 + 68143ab commit 75ee5d3
Show file tree
Hide file tree
Showing 18 changed files with 327 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.call

import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
Expand Down Expand Up @@ -61,4 +62,9 @@ interface CallListener {
* Called when the call has been managed by an other session
*/
fun onCallManagedByOtherSession(callId: String)

/**
* Called when an asserted identity event is received
*/
fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent)
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ object EventType {
const val CALL_NEGOTIATE = "m.call.negotiate"
const val CALL_REJECT = "m.call.reject"
const val CALL_HANGUP = "m.call.hangup"
const val CALL_ASSERTED_IDENTITY = "m.call.asserted_identity"
const val CALL_ASSERTED_IDENTITY_PREFIX = "org.matrix.call.asserted_identity"

// This type is not processed by the client, just sent to the server
const val CALL_REPLACES = "m.call.replaces"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2021 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.session.room.model.call

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

/**
* This event is sent by the callee when they wish to answer the call.
*/
@JsonClass(generateAdapter = true)
data class CallAssertedIdentityContent(
/**
* Required. The ID of the call this event relates to.
*/
@Json(name = "call_id") override val callId: String,
/**
* Required. ID to let user identify remote echo of their own events
*/
@Json(name = "party_id") override val partyId: String? = null,
/**
* Required. The version of the VoIP specification this messages adheres to.
*/
@Json(name = "version") override val version: String?,

/**
* Optional. Used to inform the transferee who they're now speaking to.
*/
@Json(name = "asserted_identity") val assertedIdentity: AssertedIdentity? = null
) : CallSignalingContent {

/**
* A user ID may be included if relevant, but unlike target_user, it is purely informational.
* The asserted identity may not represent a matrix user at all,
* in which case just a display_name may be given, or a perhaps a display_name and avatar_url.
*/
@JsonClass(generateAdapter = true)
data class AssertedIdentity(
@Json(name = "id") val id: String? = null,
@Json(name = "display_name") val displayName: String? = null,
@Json(name = "avatar_url") val avatarUrl: String? = null
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
EventType.CALL_CANDIDATES,
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.ENCRYPTED
EventType.ENCRYPTED,
EventType.CALL_ASSERTED_IDENTITY,
EventType.CALL_ASSERTED_IDENTITY_PREFIX
)

private val eventsToPostProcess = mutableListOf<Event>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
Expand Down Expand Up @@ -64,6 +65,10 @@ internal class CallListenersDispatcher(private val listeners: Set<CallListener>)
it.onCallNegotiateReceived(callNegotiateContent)
}

override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) = dispatch {
it.onCallAssertedIdentityReceived(callAssertedIdentityContent)
}

private fun dispatch(lambda: (CallListener) -> Unit) {
listeners.toList().forEach {
tryOrNull {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
Expand Down Expand Up @@ -53,28 +54,42 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa

fun onCallEvent(event: Event) {
when (event.getClearType()) {
EventType.CALL_ANSWER -> {
EventType.CALL_ANSWER -> {
handleCallAnswerEvent(event)
}
EventType.CALL_INVITE -> {
EventType.CALL_INVITE -> {
handleCallInviteEvent(event)
}
EventType.CALL_HANGUP -> {
EventType.CALL_HANGUP -> {
handleCallHangupEvent(event)
}
EventType.CALL_REJECT -> {
EventType.CALL_REJECT -> {
handleCallRejectEvent(event)
}
EventType.CALL_CANDIDATES -> {
EventType.CALL_CANDIDATES -> {
handleCallCandidatesEvent(event)
}
EventType.CALL_SELECT_ANSWER -> {
EventType.CALL_SELECT_ANSWER -> {
handleCallSelectAnswerEvent(event)
}
EventType.CALL_NEGOTIATE -> {
EventType.CALL_NEGOTIATE -> {
handleCallNegotiateEvent(event)
}
EventType.CALL_ASSERTED_IDENTITY,
EventType.CALL_ASSERTED_IDENTITY_PREFIX -> {
handleCallAssertedIdentityEvent(event)
}
}
}

private fun handleCallAssertedIdentityEvent(event: Event) {
val content = event.getClearContent().toModel<CallAssertedIdentityContent>() ?: return
val call = content.getCall() ?: return
if (call.ourPartyId == content.partyId) {
// Ignore remote echo (not that we send asserted identity, but still...)
return
}
callListenersDispatcher.onCallAssertedIdentityReceived(content)
}

private fun handleCallNegotiateEvent(event: Event) {
Expand Down
1 change: 1 addition & 0 deletions newsfragment/3451.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds support for receiving MSC3086 Asserted Identity events.
4 changes: 4 additions & 0 deletions vector/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ android {

buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping"

// If set, MSC3086 asserted identity messages sent on VoIP calls will cause the call to appear in the room corresponding to the asserted identity.
// This *must* only be set in trusted environments.
buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

// Keep abiFilter for the universalApk
Expand Down
82 changes: 82 additions & 0 deletions vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.core.glide

import android.content.Context
import android.graphics.drawable.Drawable
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.signature.ObjectKey
import im.vector.app.core.extensions.vectorComponent
import org.matrix.android.sdk.api.util.MatrixItem

data class AvatarPlaceholder(val matrixItem: MatrixItem)

class AvatarPlaceholderModelLoaderFactory(private val context: Context) : ModelLoaderFactory<AvatarPlaceholder, Drawable> {

override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<AvatarPlaceholder, Drawable> {
return AvatarPlaceholderModelLoader(context)
}

override fun teardown() {
// Is there something to do here?
}
}

class AvatarPlaceholderModelLoader(private val context: Context)
: ModelLoader<AvatarPlaceholder, Drawable> {

override fun buildLoadData(model: AvatarPlaceholder, width: Int, height: Int, options: Options): ModelLoader.LoadData<Drawable>? {
return ModelLoader.LoadData(ObjectKey(model), AvatarPlaceholderDataFetcher(context, model))
}

override fun handles(model: AvatarPlaceholder): Boolean {
return true
}
}

class AvatarPlaceholderDataFetcher(context: Context, private val data: AvatarPlaceholder)
: DataFetcher<Drawable> {

private val avatarRenderer = context.vectorComponent().avatarRenderer()

override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Drawable>) {
val avatarPlaceholder = avatarRenderer.getPlaceholderDrawable(data.matrixItem)
callback.onDataReady(avatarPlaceholder)
}

override fun cleanup() {
// NOOP
}

override fun cancel() {
// NOOP
}

override fun getDataClass(): Class<Drawable> {
return Drawable::class.java
}

override fun getDataSource(): DataSource {
return DataSource.LOCAL
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package im.vector.app.core.glide

import android.content.Context
import android.graphics.drawable.Drawable
import android.util.Log

import com.bumptech.glide.Glide
Expand All @@ -40,5 +41,10 @@ class MyAppGlideModule : AppGlideModule() {
InputStream::class.java,
VectorGlideModelLoaderFactory(context)
)
registry.append(
AvatarPlaceholder::class.java,
Drawable::class.java,
AvatarPlaceholderModelLoaderFactory(context)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
}
is CallState.Connected -> {
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
if (state.transferee !is VectorCallViewState.TransfereeState.NoTransferee) {
val transfereeName = if (state.transferee is VectorCallViewState.TransfereeState.KnownTransferee) {
state.transferee.name
} else {
getString(R.string.call_transfer_unknown_person)
}
views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, transfereeName)
views.callActionText.isVisible = true
views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) }
views.callStatusText.text = state.formattedDuration
configureCallInfo(state)
} else if (state.isLocalOnHold || state.isRemoteOnHold) {
if (state.isLocalOnHold || state.isRemoteOnHold) {
views.smallIsHeldIcon.isVisible = true
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true
Expand All @@ -221,10 +210,21 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
views.callStatusText.setText(R.string.call_held_by_you)
} else {
views.callActionText.isInvisible = true
state.callInfo.otherUserItem?.let {
state.callInfo?.opponentUserItem?.let {
views.callStatusText.text = getString(R.string.call_held_by_user, it.getBestName())
}
}
} else if (state.transferee !is VectorCallViewState.TransfereeState.NoTransferee) {
val transfereeName = if (state.transferee is VectorCallViewState.TransfereeState.KnownTransferee) {
state.transferee.name
} else {
getString(R.string.call_transfer_unknown_person)
}
views.callActionText.text = getString(R.string.call_transfer_transfer_to_title, transfereeName)
views.callActionText.isVisible = true
views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.TransferCall) }
views.callStatusText.text = state.formattedDuration
configureCallInfo(state)
} else {
views.callStatusText.text = state.formattedDuration
configureCallInfo(state)
Expand Down Expand Up @@ -255,31 +255,32 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
}

private fun configureCallInfo(state: VectorCallViewState, blurAvatar: Boolean = false) {
state.callInfo.otherUserItem?.let {
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter)
state.callInfo?.opponentUserItem?.let {
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen_blur)
avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter, addPlaceholder = false)
if (state.transferee is VectorCallViewState.TransfereeState.NoTransferee) {
views.participantNameText.text = it.getBestName()
} else {
views.participantNameText.text = getString(R.string.call_transfer_consulting_with, it.getBestName())
}
if (blurAvatar) {
avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter)
avatarRenderer.renderBlur(it, views.otherMemberAvatar, sampling = 2, rounded = true, colorFilter = colorFilter, addPlaceholder = true)
} else {
avatarRenderer.render(it, views.otherMemberAvatar)
}
}
if (state.otherKnownCallInfo?.otherUserItem == null) {
if (state.otherKnownCallInfo?.opponentUserItem == null) {
views.otherKnownCallLayout.isVisible = false
} else {
val otherCall = callManager.getCallById(state.otherKnownCallInfo.callId)
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen_blur)
avatarRenderer.renderBlur(
matrixItem = state.otherKnownCallInfo.otherUserItem,
matrixItem = state.otherKnownCallInfo.opponentUserItem,
imageView = views.otherKnownCallAvatarView,
sampling = 20,
rounded = false,
colorFilter = colorFilter
rounded = true,
colorFilter = colorFilter,
addPlaceholder = true
)
views.otherKnownCallLayout.isVisible = true
views.otherSmallIsHeldIcon.isVisible = otherCall?.let { it.isLocalOnHold || it.remoteOnHold }.orFalse()
Expand All @@ -288,7 +289,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro

private fun configureCallViews() {
views.callControlsView.interactionListener = this
views.otherKnownCallAvatarView.setOnClickListener {
views.otherKnownCallLayout.setOnClickListener {
withState(callViewModel) {
val otherCall = callManager.getCallById(it.otherKnownCallInfo?.callId ?: "") ?: return@withState
startActivity(newIntent(this, otherCall, null))
Expand Down
Loading

0 comments on commit 75ee5d3

Please sign in to comment.