diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallService.kt index 5e3f331148..bb5a81907e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallService.kt @@ -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) - - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallsListener.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallsListener.kt index d48841f1bb..ff8ddb8de9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallsListener.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallsListener.kt @@ -46,5 +46,4 @@ interface CallsListener { fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) fun onCallHangupReceived(callHangupContent: CallHangupContent) - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/PeerSignalingClient.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/PeerSignalingClient.kt index 9a948adbb8..ab99f8875a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/PeerSignalingClient.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/PeerSignalingClient.kt @@ -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>) // } -//} +// } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/VoipApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/VoipApi.kt index e324822617..dc92af8023 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/VoipApi.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/VoipApi.kt @@ -24,5 +24,4 @@ internal interface VoipApi { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer") fun getTurnServer(): Call<TurnServer> - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventObserver.kt index 61e6087737..ad70cb245d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/CallEventObserver.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/DefaultCallService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/DefaultCallService.kt index 61f72e5a4c..0617a657fc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/DefaultCallService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/DefaultCallService.kt @@ -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>) { diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt index 16db7b0c38..8b4a777454 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt @@ -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")) } } diff --git a/vector/src/main/java/im/vector/riotx/core/services/CallService.kt b/vector/src/main/java/im/vector/riotx/core/services/CallService.kt index 30ab62d5b2..fba433727a 100644 --- a/vector/src/main/java/im/vector/riotx/core/services/CallService.kt +++ b/vector/src/main/java/im/vector/riotx/core/services/CallService.kt @@ -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 + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/call/CallConnection.kt b/vector/src/main/java/im/vector/riotx/features/call/CallConnection.kt index 5f1cd81383..9523b4ebdd 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/CallConnection.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/CallConnection.kt @@ -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)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt index f1d50fd7b3..51a4341336 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt @@ -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() { diff --git a/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt index 95a3677f61..c8b8a3316c 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt @@ -70,7 +70,6 @@ class VectorCallViewModel @AssistedInject constructor( } override fun onCallInviteReceived(signalingRoomId: String, callInviteContent: CallInviteContent) { - } override fun onCallHangupReceived(callHangupContent: CallHangupContent) { diff --git a/vector/src/main/java/im/vector/riotx/features/call/VectorConnectionService.kt b/vector/src/main/java/im/vector/riotx/features/call/VectorConnectionService.kt index 633f2482cf..fed93c9faa 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/VectorConnectionService.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/VectorConnectionService.kt @@ -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" + } } diff --git a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt index e58404d8fb..b9685e188a 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt @@ -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() } } - - diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index c25bd6c8da..49a469847b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -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