Experimental implementation of Telecom API.

This commit is contained in:
onurays 2020-05-19 10:57:17 +03:00 committed by Valere
parent a1fc0db8a2
commit 4a4edcf82a
14 changed files with 176 additions and 42 deletions

View File

@ -22,12 +22,10 @@ import org.webrtc.SessionDescription
interface CallService {
fun getTurnServer(callback: MatrixCallback<TurnServer?>)
fun isCallSupportedInRoom(roomId: String) : Boolean
/**
* Send offer SDP to the other participant.
*/
@ -48,10 +46,7 @@ interface CallService {
*/
fun sendLocalIceCandidateRemovals(callId: String, roomId: String, candidates: List<IceCandidate>)
fun addCallListener(listener: CallsListener)
fun removeCallListener(listener: CallsListener)
}

View File

@ -46,5 +46,4 @@ interface CallsListener {
fun onCallAnswerReceived(callAnswerContent: CallAnswerContent)
fun onCallHangupReceived(callHangupContent: CallHangupContent)
}

View File

@ -1,4 +1,4 @@
///*
// /*
// * Copyright (c) 2020 New Vector Ltd
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
@ -14,13 +14,13 @@
// * limitations under the License.
// */
//
//package im.vector.matrix.android.api.session.call
// package im.vector.matrix.android.api.session.call
//
//import im.vector.matrix.android.api.MatrixCallback
//import org.webrtc.IceCandidate
//import org.webrtc.SessionDescription
// import im.vector.matrix.android.api.MatrixCallback
// import org.webrtc.IceCandidate
// import org.webrtc.SessionDescription
//
//interface PeerSignalingClient {
// interface PeerSignalingClient {
//
// val callID: String
//
@ -63,4 +63,4 @@
// */
// fun onRemoteIceCandidatesRemoved(candidates: List<IceCandidate>)
// }
//}
// }

View File

@ -24,5 +24,4 @@ internal interface VoipApi {
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer")
fun getTurnServer(): Call<TurnServer>
}

View File

@ -24,7 +24,6 @@ import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.whereTypes
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationTask
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults

View File

@ -51,11 +51,11 @@ internal class DefaultCallService @Inject constructor(
private val callListeners = ArrayList<CallsListener>()
override fun getTurnServer(callback: MatrixCallback<TurnServer?>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isCallSupportedInRoom(roomId: String): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendOfferSdp(callId: String, roomId: String, sdp: SessionDescription, callback: MatrixCallback<String>) {

View File

@ -184,7 +184,7 @@ class DebugMenuActivity : VectorBaseActivity() {
@OnClick(R.id.debug_scan_qr_code)
fun scanQRCode() {
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
//doScanQRCode()
// doScanQRCode()
startActivity(VectorCallActivity.newIntent(this, "!cyIJhOLwWgmmqreHLD:matrix.org"))
}
}

View File

@ -1,3 +1,4 @@
/*
* Copyright 2019 New Vector Ltd
*
@ -20,8 +21,10 @@ package im.vector.riotx.core.services
import android.content.Context
import android.content.Intent
import android.os.Binder
import androidx.core.content.ContextCompat
import im.vector.riotx.core.extensions.vectorComponent
import im.vector.riotx.features.call.CallConnection
import im.vector.riotx.features.notifications.NotificationUtils
import timber.log.Timber
@ -30,6 +33,8 @@ import timber.log.Timber
*/
class CallService : VectorService() {
private val connections = mutableMapOf<String, CallConnection>()
/**
* call in progress (foreground notification)
*/
@ -154,6 +159,10 @@ class CallService : VectorService() {
myStopSelf()
}
fun addConnection(callConnection: CallConnection) {
connections[callConnection.callId] = callConnection
}
companion object {
private const val NOTIFICATION_ID = 6480
@ -214,4 +223,10 @@ class CallService : VectorService() {
ContextCompat.startForegroundService(context, intent)
}
}
inner class CallServiceBinder : Binder() {
fun getCallService(): CallService {
return this@CallService
}
}
}

View File

@ -19,24 +19,113 @@ package im.vector.riotx.features.call
import android.content.Context
import android.os.Build
import android.telecom.Connection
import android.telecom.DisconnectCause
import androidx.annotation.RequiresApi
import org.webrtc.Camera1Enumerator
import org.webrtc.Camera2Enumerator
import org.webrtc.IceCandidate
import org.webrtc.MediaStream
import org.webrtc.PeerConnection
import org.webrtc.SessionDescription
import org.webrtc.VideoTrack
import timber.log.Timber
import javax.inject.Inject
@RequiresApi(Build.VERSION_CODES.M) class CallConnection(
private val context: Context,
private val roomId: String,
private val callId: String
) : Connection() {
val callId: String
) : Connection(), WebRtcPeerConnectionManager.Listener {
@Inject lateinit var peerConnectionManager: WebRtcPeerConnectionManager
@Inject lateinit var callViewModel: VectorCallViewModel
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
connectionProperties = PROPERTY_SELF_MANAGED
}
}
/**
* The telecom subsystem calls this method when you add a new incoming call and your app should show its incoming call UI.
*/
override fun onShowIncomingCallUi() {
super.onShowIncomingCallUi()
Timber.i("onShowIncomingCallUi")
/*
VectorCallActivity.newIntent(context, roomId).let {
context.startActivity(it)
}
*/
}
override fun onAnswer() {
super.onAnswer()
// startCall()
Timber.i("onShowIncomingCallUi")
}
override fun onStateChanged(state: Int) {
super.onStateChanged(state)
Timber.i("onStateChanged${stateToString(state)}")
}
override fun onReject() {
super.onReject()
Timber.i("onReject")
close()
}
override fun onDisconnect() {
onDisconnect()
Timber.i("onDisconnect")
close()
}
private fun close() {
setDisconnected(DisconnectCause(DisconnectCause.CANCELED))
destroy()
}
private fun startCall() {
peerConnectionManager.createPeerConnectionFactory()
peerConnectionManager.listener = this
val cameraIterator = if (Camera2Enumerator.isSupported(context)) Camera2Enumerator(context) else Camera1Enumerator(false)
val frontCamera = cameraIterator.deviceNames
?.firstOrNull { cameraIterator.isFrontFacing(it) }
?: cameraIterator.deviceNames?.first()
?: return
val videoCapturer = cameraIterator.createCapturer(frontCamera, null)
val iceServers = ArrayList<PeerConnection.IceServer>().apply {
listOf("turn:turn.matrix.org:3478?transport=udp", "turn:turn.matrix.org:3478?transport=tcp", "turns:turn.matrix.org:443?transport=tcp").forEach {
add(
PeerConnection.IceServer.builder(it)
.setUsername("xxxxx")
.setPassword("xxxxx")
.createIceServer()
)
}
}
peerConnectionManager.createPeerConnection(videoCapturer, iceServers)
peerConnectionManager.startCall()
}
override fun addLocalIceCandidate(candidates: IceCandidate) {
}
override fun addRemoteVideoTrack(videoTrack: VideoTrack) {
}
override fun addLocalVideoTrack(videoTrack: VideoTrack) {
}
override fun removeRemoteVideoStream(mediaStream: MediaStream) {
}
override fun sendOffer(sessionDescription: SessionDescription) {
callViewModel.handle(VectorCallViewActions.SendOffer(sessionDescription))
}
}

View File

@ -82,7 +82,7 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis
// private var peerConnectionFactory: PeerConnectionFactory? = null
//private var peerConnection: PeerConnection? = null
// private var peerConnection: PeerConnection? = null
// private var remoteVideoTrack: VideoTrack? = null
@ -152,12 +152,11 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis
fullscreenRenderer.init(rootEglBase!!.eglBaseContext, null)
fullscreenRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
pipRenderer.setZOrderMediaOverlay(true);
pipRenderer.setEnableHardwareScaler(true /* enabled */);
fullscreenRenderer.setEnableHardwareScaler(true /* enabled */);
pipRenderer.setZOrderMediaOverlay(true)
pipRenderer.setEnableHardwareScaler(true /* enabled */)
fullscreenRenderer.setEnableHardwareScaler(true /* enabled */)
// Start with local feed in fullscreen and swap it to the pip when the call is connected.
//setSwappedFeeds(true /* isSwappedFeeds */);
// setSwappedFeeds(true /* isSwappedFeeds */);
if (isFirstCreation()) {
peerConnectionManager.createPeerConnectionFactory()
@ -374,9 +373,9 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis
// if (requestCode != CAPTURE_PERMISSION_REQUEST_CODE) {
// super.onActivityResult(requestCode, resultCode, data)
// }
//// mediaProjectionPermissionResultCode = resultCode;
//// mediaProjectionPermissionResultData = data;
//// startCall();
// // mediaProjectionPermissionResultCode = resultCode;
// // mediaProjectionPermissionResultData = data;
// // startCall();
// }
companion object {
@ -426,7 +425,6 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis
}
override fun removeRemoteVideoStream(mediaStream: MediaStream) {
}
override fun onDisconnect() {

View File

@ -70,7 +70,6 @@ class VectorCallViewModel @AssistedInject constructor(
}
override fun onCallInviteReceived(signalingRoomId: String, callInviteContent: CallInviteContent) {
}
override fun onCallHangupReceived(callHangupContent: CallHangupContent) {

View File

@ -16,12 +16,20 @@
package im.vector.riotx.features.call
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.telecom.Connection
import android.telecom.ConnectionRequest
import android.telecom.ConnectionService
import android.telecom.PhoneAccountHandle
import android.telecom.StatusHints
import android.telecom.TelecomManager
import androidx.annotation.RequiresApi
import im.vector.riotx.core.services.CallService
/**
* No active calls in other apps
@ -49,4 +57,35 @@ import androidx.annotation.RequiresApi
val roomId = request.extras.getString("MX_CALL_ROOM_ID") ?: return null
return CallConnection(applicationContext, roomId, callId)
}
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
val roomId = request?.extras?.getString("MX_CALL_ROOM_ID") ?: return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
val callId = request.extras.getString("MX_CALL_CALL_ID") ?: return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
val connection = CallConnection(applicationContext, roomId, callId)
connection.connectionCapabilities = Connection.CAPABILITY_MUTE
connection.audioModeIsVoip = true
connection.setAddress(Uri.fromParts("tel", "+905000000000", null), TelecomManager.PRESENTATION_ALLOWED)
connection.setCallerDisplayName("RiotX Caller", TelecomManager.PRESENTATION_ALLOWED)
connection.statusHints = StatusHints("Testing Hint...", null, null)
bindService(Intent(applicationContext, CallService::class.java), CallServiceConnection(connection), 0)
connection.setInitializing()
return CallConnection(applicationContext, roomId, callId)
}
inner class CallServiceConnection(private val callConnection: CallConnection) : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
val callSrvBinder = binder as CallService.CallServiceBinder
callSrvBinder.getCallService().addConnection(callConnection)
unbindService(this)
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
companion object {
const val TAG = "TComService"
}
}

View File

@ -24,6 +24,7 @@ import android.os.Bundle
import android.telecom.PhoneAccount
import android.telecom.PhoneAccountHandle
import android.telecom.TelecomManager
import android.telecom.VideoProfile
import androidx.core.content.ContextCompat
import im.vector.matrix.android.api.session.call.CallsListener
import im.vector.matrix.android.api.session.call.EglUtils
@ -80,8 +81,11 @@ class WebRtcPeerConnectionManager @Inject constructor(
val componentName = ComponentName(BuildConfig.APPLICATION_ID, VectorConnectionService::class.java.name)
val appName = context.getString(R.string.app_name)
phoneAccountHandle = PhoneAccountHandle(componentName, appName)
val phoneAccount = PhoneAccount.Builder(phoneAccountHandle, appName)
val phoneAccount = PhoneAccount.Builder(phoneAccountHandle, BuildConfig.APPLICATION_ID)
.setIcon(Icon.createWithResource(context, R.drawable.riotx_logo))
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
.setCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)
.setCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT)
.build()
ContextCompat.getSystemService(context, TelecomManager::class.java)
?.registerPhoneAccount(phoneAccount)
@ -96,7 +100,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
// Executor thread is started once and is used for all
// peer connection API calls to ensure new peer connection factory is
// created on the same thread as previously destroyed factory.
private val executor = Executors.newSingleThreadExecutor();
private val executor = Executors.newSingleThreadExecutor()
private val rootEglBase by lazy { EglUtils.rootEglBase }
@ -139,7 +143,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
true)
val defaultVideoDecoderFactory = DefaultVideoDecoderFactory(eglBaseContext)
Timber.v("## VOIP PeerConnectionFactory.createPeerConnectionFactory ...")
peerConnectionFactory = PeerConnectionFactory.builder()
.setOptions(options)
@ -152,7 +155,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
fun createPeerConnection(videoCapturer: VideoCapturer, iceServers: List<PeerConnection.IceServer>) {
executor.execute {
Timber.v("## VOIP PeerConnectionFactory.createPeerConnection ${peerConnectionFactory}...")
Timber.v("## VOIP PeerConnectionFactory.createPeerConnection $peerConnectionFactory...")
// Following instruction here: https://stackoverflow.com/questions/55085726/webrtc-create-peerconnectionfactory-object
val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", rootEglBase!!.eglBaseContext)
@ -165,8 +168,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
Timber.v("## VOIP Local video track created")
listener?.addLocalVideoTrack(it)
// localSurfaceRenderer?.get()?.let { surface ->
//// it.addSink(surface)
//// }
// // it.addSink(surface)
// // }
}
// create a local audio track
@ -282,7 +285,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
peerConnection?.setLocalDescription(object : SdpObserverAdapter() {
override fun onSetSuccess() {
listener?.sendOffer(sessionDescription)
//callViewModel.handle(VectorCallViewActions.SendOffer(sessionDescription))
// callViewModel.handle(VectorCallViewActions.SendOffer(sessionDescription))
}
}, sessionDescription)
}
@ -361,10 +364,12 @@ class WebRtcPeerConnectionManager @Inject constructor(
Bundle().apply {
putString("MX_CALL_ROOM_ID", signalingRoomId)
putString("MX_CALL_CALL_ID", callInviteContent.callId)
putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL)
putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL)
}
)
}
}
}
}
@ -376,5 +381,3 @@ class WebRtcPeerConnectionManager @Inject constructor(
close()
}
}

View File

@ -66,7 +66,6 @@ import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.core.utils.subscribeLogError
import im.vector.riotx.features.call.VectorCallActivity
import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider