Merge pull request #3452 from vector-im/feature/fga/voip_asserted_identity
Feature/fga/voip asserted identity
This commit is contained in:
commit
75ee5d38fa
@ -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
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
)
|
||||
}
|
@ -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>()
|
||||
|
@ -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
|
||||
@ -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 {
|
||||
|
@ -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
|
||||
@ -53,30 +54,44 @@ 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) {
|
||||
val content = event.getClearContent().toModel<CallNegotiateContent>() ?: return
|
||||
val call = content.getCall() ?: return
|
||||
|
1
newsfragment/3451.feature
Normal file
1
newsfragment/3451.feature
Normal file
@ -0,0 +1 @@
|
||||
Adds support for receiving MSC3086 Asserted Identity events.
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -40,5 +41,10 @@ class MyAppGlideModule : AppGlideModule() {
|
||||
InputStream::class.java,
|
||||
VectorGlideModelLoaderFactory(context)
|
||||
)
|
||||
registry.append(
|
||||
AvatarPlaceholder::class.java,
|
||||
Drawable::class.java,
|
||||
AvatarPlaceholderModelLoaderFactory(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -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)
|
||||
@ -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()
|
||||
@ -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))
|
||||
|
@ -34,11 +34,13 @@ import im.vector.app.features.call.webrtc.getOpponentAsMatrixItem
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||
import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
class VectorCallViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: VectorCallViewState,
|
||||
@ -87,6 +89,12 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun assertedIdentityChanged() {
|
||||
setState {
|
||||
copy(callInfo = call?.extractCallInfo())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateUpdate(call: MxCall) {
|
||||
val callState = call.state
|
||||
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||
@ -160,8 +168,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
if (otherCall == null) {
|
||||
copy(otherKnownCallInfo = null)
|
||||
} else {
|
||||
val otherUserItem = otherCall.getOpponentAsMatrixItem(session)
|
||||
copy(otherKnownCallInfo = VectorCallViewState.CallInfo(otherCall.callId, otherUserItem))
|
||||
copy(otherKnownCallInfo = otherCall.extractCallInfo())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,7 +182,6 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
} else {
|
||||
call = webRtcCall
|
||||
callManager.addCurrentCallListener(currentCallListener)
|
||||
val item = webRtcCall.getOpponentAsMatrixItem(session)
|
||||
webRtcCall.addListener(callListener)
|
||||
val currentSoundDevice = callManager.audioManager.selectedDevice
|
||||
if (currentSoundDevice == CallAudioManager.Device.PHONE) {
|
||||
@ -185,7 +191,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
copy(
|
||||
isVideoCall = webRtcCall.mxCall.isVideoCall,
|
||||
callState = Success(webRtcCall.mxCall.state),
|
||||
callInfo = VectorCallViewState.CallInfo(callId, item),
|
||||
callInfo = webRtcCall.extractCallInfo(),
|
||||
device = currentSoundDevice ?: CallAudioManager.Device.PHONE,
|
||||
isLocalOnHold = webRtcCall.isLocalOnHold,
|
||||
isRemoteOnHold = webRtcCall.remoteOnHold,
|
||||
@ -202,6 +208,22 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun WebRtcCall.extractCallInfo(): VectorCallViewState.CallInfo {
|
||||
val assertedIdentity = this.remoteAssertedIdentity
|
||||
val matrixItem = if (assertedIdentity != null) {
|
||||
val userId = if (MatrixPatterns.isUserId(assertedIdentity.id)) {
|
||||
assertedIdentity.id!!
|
||||
} else {
|
||||
// Need an id starting with @
|
||||
"@${assertedIdentity.displayName}"
|
||||
}
|
||||
MatrixItem.UserItem(userId, assertedIdentity.displayName, assertedIdentity.avatarUrl)
|
||||
} else {
|
||||
getOpponentAsMatrixItem(session)
|
||||
}
|
||||
return VectorCallViewState.CallInfo(callId, matrixItem)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
callManager.removeCurrentCallListener(currentCallListener)
|
||||
call?.removeListener(callListener)
|
||||
|
@ -39,7 +39,7 @@ data class VectorCallViewState(
|
||||
val availableDevices: Set<CallAudioManager.Device> = emptySet(),
|
||||
val callState: Async<CallState> = Uninitialized,
|
||||
val otherKnownCallInfo: CallInfo? = null,
|
||||
val callInfo: CallInfo = CallInfo(callId),
|
||||
val callInfo: CallInfo? = null,
|
||||
val formattedDuration: String = "",
|
||||
val canOpponentBeTransferred: Boolean = false,
|
||||
val transferee: TransfereeState = TransfereeState.NoTransferee
|
||||
@ -53,7 +53,7 @@ data class VectorCallViewState(
|
||||
|
||||
data class CallInfo(
|
||||
val callId: String,
|
||||
val otherUserItem: MatrixItem? = null
|
||||
val opponentUserItem: MatrixItem? = null
|
||||
)
|
||||
|
||||
constructor(callArgs: CallArgs) : this(
|
||||
|
@ -27,6 +27,7 @@ import im.vector.app.features.call.CameraProxy
|
||||
import im.vector.app.features.call.CameraType
|
||||
import im.vector.app.features.call.CaptureFormat
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
import im.vector.app.features.call.lookup.sipNativeLookup
|
||||
import im.vector.app.features.call.utils.asWebRTC
|
||||
import im.vector.app.features.call.utils.awaitCreateAnswer
|
||||
import im.vector.app.features.call.utils.awaitCreateOffer
|
||||
@ -51,6 +52,7 @@ import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
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
|
||||
@ -104,6 +106,7 @@ class WebRtcCall(
|
||||
fun onCaptureStateChanged() {}
|
||||
fun onCameraChanged() {}
|
||||
fun onHoldUnhold() {}
|
||||
fun assertedIdentityChanged() {}
|
||||
fun onTick(formattedDuration: String) {}
|
||||
override fun onStateUpdate(call: MxCall) {}
|
||||
}
|
||||
@ -168,6 +171,8 @@ class WebRtcCall(
|
||||
|
||||
// This value is used to track localOnHold when changing remoteOnHold value
|
||||
private var wasLocalOnHold = false
|
||||
var remoteAssertedIdentity: CallAssertedIdentityContent.AssertedIdentity? = null
|
||||
private set
|
||||
|
||||
var offerSdp: CallInviteContent.Offer? = null
|
||||
|
||||
@ -877,6 +882,38 @@ class WebRtcCall(
|
||||
}
|
||||
}
|
||||
|
||||
fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) {
|
||||
sessionScope?.launch(dispatcher) {
|
||||
val session = sessionProvider.get() ?: return@launch
|
||||
val newAssertedIdentity = callAssertedIdentityContent.assertedIdentity ?: return@launch
|
||||
if (newAssertedIdentity.id == null && newAssertedIdentity.displayName == null) {
|
||||
Timber.v("Asserted identity received with no relevant information, skip")
|
||||
return@launch
|
||||
}
|
||||
remoteAssertedIdentity = newAssertedIdentity
|
||||
if (newAssertedIdentity.id != null) {
|
||||
val nativeUserId = session.sipNativeLookup(newAssertedIdentity.id!!).firstOrNull()?.userId
|
||||
if (nativeUserId != null) {
|
||||
val resolvedUser = tryOrNull {
|
||||
session.resolveUser(nativeUserId)
|
||||
}
|
||||
if (resolvedUser != null) {
|
||||
remoteAssertedIdentity = newAssertedIdentity.copy(
|
||||
id = nativeUserId,
|
||||
avatarUrl = resolvedUser.avatarUrl,
|
||||
displayName = resolvedUser.displayName
|
||||
)
|
||||
} else {
|
||||
remoteAssertedIdentity = newAssertedIdentity.copy(id = nativeUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
listeners.forEach {
|
||||
tryOrNull { it.assertedIdentityChanged() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MxCall.StateListener
|
||||
|
||||
override fun onStateUpdate(call: MxCall) {
|
||||
|
@ -21,6 +21,7 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import im.vector.app.ActiveSessionDataSource
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.core.services.CallService
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
import im.vector.app.features.call.audio.CallAudioManager
|
||||
@ -37,6 +38,7 @@ import org.matrix.android.sdk.api.session.call.CallListener
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
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
|
||||
@ -420,4 +422,15 @@ class WebRtcCallManager @Inject constructor(
|
||||
Timber.v("## VOIP onCallManagedByOtherSession: $callId")
|
||||
onCallEnded(callId)
|
||||
}
|
||||
|
||||
override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) {
|
||||
if (!BuildConfig.handleCallAssertedIdentityEvents) {
|
||||
return
|
||||
}
|
||||
val call = callsByCallId[callAssertedIdentityContent.callId]
|
||||
?: return Unit.also {
|
||||
Timber.w("onCallAssertedIdentityReceived for non active call? ${callAssertedIdentityContent.callId}")
|
||||
}
|
||||
call.onCallAssertedIdentityReceived(callAssertedIdentityContent)
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import im.vector.app.core.contacts.MappedContact
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.glide.AvatarPlaceholder
|
||||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.core.glide.GlideRequest
|
||||
import im.vector.app.core.glide.GlideRequests
|
||||
@ -136,7 +137,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||
matrixItem: MatrixItem,
|
||||
target: Target<Drawable>) {
|
||||
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||
buildGlideRequest(glideRequests, matrixItem.avatarUrl)
|
||||
glideRequests.loadResolvedUrl(matrixItem.avatarUrl)
|
||||
.apply {
|
||||
when (matrixItem) {
|
||||
is MatrixItem.SpaceItem -> {
|
||||
@ -175,7 +176,12 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun renderBlur(matrixItem: MatrixItem, imageView: ImageView, sampling: Int, rounded: Boolean, @ColorInt colorFilter: Int? = null) {
|
||||
fun renderBlur(matrixItem: MatrixItem,
|
||||
imageView: ImageView,
|
||||
sampling: Int,
|
||||
rounded: Boolean,
|
||||
@ColorInt colorFilter: Int? = null,
|
||||
addPlaceholder: Boolean) {
|
||||
val transformations = mutableListOf<Transformation<Bitmap>>(
|
||||
BlurTransformation(20, sampling)
|
||||
)
|
||||
@ -185,14 +191,26 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||
if (rounded) {
|
||||
transformations.add(CircleCrop())
|
||||
}
|
||||
buildGlideRequest(GlideApp.with(imageView), matrixItem.avatarUrl)
|
||||
.apply(RequestOptions.bitmapTransform(MultiTransformation(transformations)))
|
||||
val bitmapTransform = RequestOptions.bitmapTransform(MultiTransformation(transformations))
|
||||
val glideRequests = GlideApp.with(imageView)
|
||||
val placeholderRequest = if (addPlaceholder) {
|
||||
glideRequests
|
||||
.load(AvatarPlaceholder(matrixItem))
|
||||
.apply(bitmapTransform)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
glideRequests.loadResolvedUrl(matrixItem.avatarUrl)
|
||||
.apply(bitmapTransform)
|
||||
// We are using thumbnail and error API so we can have blur transformation on it...
|
||||
.thumbnail(placeholderRequest)
|
||||
.error(placeholderRequest)
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun getCachedDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem): Drawable {
|
||||
return buildGlideRequest(glideRequests, matrixItem.avatarUrl)
|
||||
return glideRequests.loadResolvedUrl(matrixItem.avatarUrl)
|
||||
.onlyRetrieveFromCache(true)
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
.submit()
|
||||
@ -220,9 +238,9 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||
|
||||
// PRIVATE API *********************************************************************************
|
||||
|
||||
private fun buildGlideRequest(glideRequests: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
|
||||
private fun GlideRequests.loadResolvedUrl(avatarUrl: String?): GlideRequest<Drawable> {
|
||||
val resolvedUrl = resolvedUrl(avatarUrl)
|
||||
return glideRequests.load(resolvedUrl)
|
||||
return load(resolvedUrl)
|
||||
}
|
||||
|
||||
private fun resolvedUrl(avatarUrl: String?): String? {
|
||||
|
@ -9,7 +9,7 @@
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/bg_call_screen"
|
||||
android:background="@color/bg_call_screen_blur"
|
||||
tools:ignore="MergeRootFrame">
|
||||
|
||||
<ImageView
|
||||
@ -37,7 +37,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<FrameLayout
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/otherKnownCallLayout"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="144dp"
|
||||
@ -45,15 +45,19 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@color/element_background_light"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="4dp"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="@color/bg_call_screen"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/otherKnownCallAvatarView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
@ -66,7 +70,7 @@
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_call_small_pause" />
|
||||
|
||||
</FrameLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/otherMemberAvatar"
|
||||
|
@ -19,7 +19,8 @@
|
||||
|
||||
<!-- Source: https://zpl.io/aBKw9Mk -->
|
||||
|
||||
<color name="bg_call_screen">#99000000</color>
|
||||
<color name="bg_call_screen_blur">#99000000</color>
|
||||
<color name="bg_call_screen">#27303A</color>
|
||||
|
||||
<color name="vctr_notice_secondary">#FF61708B</color>
|
||||
<color name="vctr_notice_secondary_alpha12">#1E61708B</color>
|
||||
|
Loading…
x
Reference in New Issue
Block a user