feat: added basic call functionality
This commit is contained in:
parent
5ea37254b9
commit
9e5e137919
|
@ -53,7 +53,8 @@ dependencies {
|
|||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
||||
implementation 'org.conscrypt:conscrypt-android:2.0.0'
|
||||
implementation 'org.signal:aesgcmprovider:0.0.3'
|
||||
implementation 'org.whispersystems:webrtc-android:M77'
|
||||
// implementation 'org.whispersystems:webrtc-android:M77'
|
||||
implementation 'org.webrtc:google-webrtc:1.0.32006'
|
||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||
|
@ -153,7 +154,7 @@ dependencies {
|
|||
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 220
|
||||
def canonicalVersionCode = 222
|
||||
def canonicalVersionName = "1.11.9"
|
||||
|
||||
def postFixSize = 10
|
||||
|
@ -232,6 +233,7 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
}
|
||||
debug {
|
||||
|
|
|
@ -297,6 +297,7 @@
|
|||
android:theme="@style/Theme.Session.DayNight.NoActionBar" />
|
||||
<activity android:name="org.thoughtcrime.securesms.calls.WebRtcTestsActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName="org.thoughtcrime.securesms.home.HomeActivity"
|
||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar">
|
||||
<meta-data
|
||||
|
|
|
@ -1,85 +1,136 @@
|
|||
package org.thoughtcrime.securesms.calls
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import kotlinx.android.synthetic.main.activity_webrtc_tests.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.messages.control.CallMessage
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.Debouncer
|
||||
import org.session.libsignal.protos.SignalServiceProtos
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.webrtc.*
|
||||
import org.webrtc.RendererCommon.ScalingType
|
||||
|
||||
|
||||
class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection.Observer,
|
||||
SdpObserver, RendererCommon.RendererEvents {
|
||||
SdpObserver {
|
||||
|
||||
companion object {
|
||||
const val HD_VIDEO_WIDTH = 1280
|
||||
const val HD_VIDEO_HEIGHT = 720
|
||||
const val HD_VIDEO_WIDTH = 320
|
||||
const val HD_VIDEO_HEIGHT = 240
|
||||
const val CALL_ID = "call_id_session"
|
||||
private const val LOCAL_TRACK_ID = "local_track"
|
||||
private const val LOCAL_STREAM_ID = "local_track"
|
||||
|
||||
const val ACTION_ANSWER = "answer"
|
||||
const val ACTION_UPDATE_ICE = "updateIce"
|
||||
|
||||
const val EXTRA_SDP = "WebRtcTestsActivity_EXTRA_SDP"
|
||||
const val EXTRA_ADDRESS = "WebRtcTestsActivity_EXTRA_ADDRESS"
|
||||
const val EXTRA_SDP_MLINE_INDEXES = "WebRtcTestsActivity_EXTRA_SDP_MLINE_INDEXES"
|
||||
const val EXTRA_SDP_MIDS = "WebRtcTestsActivity_EXTRA_SDP_MIDS"
|
||||
|
||||
}
|
||||
|
||||
private class ProxyVideoSink : VideoSink {
|
||||
private var target: VideoSink? = null
|
||||
private val eglBase by lazy { EglBase.create() }
|
||||
private val surfaceHelper by lazy { SurfaceTextureHelper.create(Thread.currentThread().name, eglBase.eglBaseContext) }
|
||||
|
||||
@Synchronized
|
||||
override fun onFrame(frame: VideoFrame) {
|
||||
if (target == null) {
|
||||
Log.d("Loki-RTC", "Dropping frame in proxy because target is null.")
|
||||
return
|
||||
}
|
||||
target!!.onFrame(frame)
|
||||
}
|
||||
private val connectionFactory by lazy {
|
||||
|
||||
@Synchronized
|
||||
fun setTarget(target: VideoSink?) {
|
||||
this.target = target
|
||||
}
|
||||
val decoderFactory = DefaultVideoDecoderFactory(eglBase.eglBaseContext)
|
||||
val encoderFactory = DefaultVideoEncoderFactory(eglBase.eglBaseContext, true, true)
|
||||
|
||||
PeerConnectionFactory.builder()
|
||||
.setVideoDecoderFactory(decoderFactory)
|
||||
.setVideoEncoderFactory(encoderFactory)
|
||||
.setOptions(PeerConnectionFactory.Options())
|
||||
.createPeerConnectionFactory()
|
||||
}
|
||||
|
||||
private val connectionFactory by lazy { PeerConnectionFactory.builder().createPeerConnectionFactory() }
|
||||
private val remoteVideoSink = ProxyVideoSink()
|
||||
private val localVideoSink = ProxyVideoSink()
|
||||
private val candidates: MutableList<IceCandidate> = mutableListOf()
|
||||
private val iceDebouncer = Debouncer(2_000)
|
||||
|
||||
private lateinit var callAddress: Address
|
||||
private val peerConnection by lazy {
|
||||
// TODO: in a lokinet world, ice servers shouldn't be needed as .loki addresses should suffice to p2p
|
||||
val server = PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer()
|
||||
val server1 = PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer()
|
||||
val server2 = PeerConnection.IceServer.builder("stun:stun2.l.google.com:19302").createIceServer()
|
||||
val server3 = PeerConnection.IceServer.builder("stun:stun3.l.google.com:19302").createIceServer()
|
||||
val server4 = PeerConnection.IceServer.builder("stun:stun4.l.google.com:19302").createIceServer()
|
||||
val rtcConfig = PeerConnection.RTCConfiguration(listOf(server, server1, server2, server3, server4))
|
||||
rtcConfig.keyType = PeerConnection.KeyType.ECDSA
|
||||
connectionFactory.createPeerConnection(rtcConfig, this)!!
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
super.onCreate(savedInstanceState, ready)
|
||||
setContentView(R.layout.activity_webrtc_tests)
|
||||
|
||||
val server = PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer()
|
||||
local_renderer.run {
|
||||
setMirror(true)
|
||||
setEnableHardwareScaler(true)
|
||||
init(eglBase.eglBaseContext, null)
|
||||
}
|
||||
remote_renderer.run {
|
||||
setMirror(true)
|
||||
setEnableHardwareScaler(true)
|
||||
init(eglBase.eglBaseContext, null)
|
||||
}
|
||||
|
||||
val rtcConfig = PeerConnection.RTCConfiguration(listOf(server))
|
||||
rtcConfig.keyType = PeerConnection.KeyType.ECDSA
|
||||
rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN
|
||||
|
||||
val peerConnection = connectionFactory.createPeerConnection(rtcConfig, this) ?: return
|
||||
|
||||
Log.d("Loki-RTC", "peer connecting?")
|
||||
val stream = connectionFactory.createLocalMediaStream("stream")
|
||||
val audioSource = connectionFactory.createAudioSource(MediaConstraints())
|
||||
val audioTrack = connectionFactory.createAudioTrack("audio", audioSource)
|
||||
val videoSource = connectionFactory.createVideoSource(false)
|
||||
val videoTrack = connectionFactory.createVideoTrack("video", videoSource)
|
||||
stream.addTrack(audioTrack)
|
||||
stream.addTrack(videoTrack)
|
||||
val remoteTrack = getRemoteVideoTrack(peerConnection) ?: return
|
||||
videoTrack.addSink(localVideoSink)
|
||||
remoteTrack.addSink(remoteVideoSink)
|
||||
remoteTrack.setEnabled(true)
|
||||
videoTrack.setEnabled(true)
|
||||
|
||||
val eglBase = EglBase.create()
|
||||
local_renderer.init(eglBase.eglBaseContext, this)
|
||||
local_renderer.setScalingType(ScalingType.SCALE_ASPECT_FILL)
|
||||
remote_renderer.init(eglBase.eglBaseContext, this)
|
||||
|
||||
val videoCapturer = createCameraCapturer(Camera2Enumerator(this)) ?: kotlin.run { finish(); return }
|
||||
val surfaceHelper = SurfaceTextureHelper.create("video-thread", eglBase.eglBaseContext)
|
||||
surfaceHelper.startListening(localVideoSink)
|
||||
videoCapturer.initialize(surfaceHelper, this, null)
|
||||
videoCapturer.initialize(surfaceHelper, local_renderer.context, videoSource.capturerObserver)
|
||||
videoCapturer.startCapture(HD_VIDEO_WIDTH, HD_VIDEO_HEIGHT, 10)
|
||||
|
||||
videoCapturer.startCapture(HD_VIDEO_WIDTH, HD_VIDEO_HEIGHT, 30)
|
||||
peerConnection.createOffer(this, MediaConstraints())
|
||||
val audioTrack = connectionFactory.createAudioTrack(LOCAL_TRACK_ID + "_audio", audioSource)
|
||||
val videoTrack = connectionFactory.createVideoTrack(LOCAL_TRACK_ID, videoSource)
|
||||
videoTrack.addSink(local_renderer)
|
||||
|
||||
val stream = connectionFactory.createLocalMediaStream(LOCAL_STREAM_ID)
|
||||
stream.addTrack(videoTrack)
|
||||
stream.addTrack(audioTrack)
|
||||
|
||||
peerConnection.addStream(stream)
|
||||
|
||||
// create either call or answer
|
||||
if (intent.action == ACTION_ANSWER) {
|
||||
callAddress = intent.getParcelableExtra(EXTRA_ADDRESS) ?: run { finish(); return }
|
||||
val offerSdp = intent.getStringArrayExtra(EXTRA_SDP)!![0]
|
||||
peerConnection.setRemoteDescription(this, SessionDescription(SessionDescription.Type.OFFER, offerSdp))
|
||||
peerConnection.createAnswer(this, MediaConstraints())
|
||||
} else {
|
||||
callAddress = intent.getParcelableExtra(EXTRA_ADDRESS) ?: run { finish(); return }
|
||||
peerConnection.createOffer(this, MediaConstraints())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRemoteVideoTrack(peerConnection: PeerConnection): VideoTrack? = peerConnection.transceivers.firstOrNull { it.receiver.track() is VideoTrack } as VideoTrack?
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
if (intent == null) return
|
||||
callAddress = intent.getParcelableExtra(EXTRA_ADDRESS) ?: run { finish(); return }
|
||||
when (intent.action) {
|
||||
ACTION_ANSWER -> {
|
||||
peerConnection.setRemoteDescription(this,
|
||||
SessionDescription(SessionDescription.Type.ANSWER, intent.getStringArrayExtra(EXTRA_SDP)!![0])
|
||||
)
|
||||
}
|
||||
ACTION_UPDATE_ICE -> {
|
||||
val sdpIndexes = intent.getIntArrayExtra(EXTRA_SDP_MLINE_INDEXES)!!
|
||||
val sdpMids = intent.getStringArrayExtra(EXTRA_SDP_MIDS)!!
|
||||
val sdp = intent.getStringArrayExtra(EXTRA_SDP)!!
|
||||
val amount = minOf(sdpIndexes.size, sdpMids.size)
|
||||
(0 until amount).map { index ->
|
||||
val candidate = IceCandidate(sdpMids[index], sdpIndexes[index], sdp[index])
|
||||
peerConnection.addIceCandidate(candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCameraCapturer(enumerator: CameraEnumerator): VideoCapturer? {
|
||||
val deviceNames = enumerator.deviceNames
|
||||
|
@ -96,8 +147,6 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
|
|||
}
|
||||
}
|
||||
|
||||
// Front facing camera not found, try something else
|
||||
|
||||
// Front facing camera not found, try something else
|
||||
Log.d("Loki-RTC-vid", "Looking for other cameras.")
|
||||
for (deviceName in deviceNames) {
|
||||
|
@ -113,14 +162,6 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
|
|||
return null
|
||||
}
|
||||
|
||||
override fun onFirstFrameRendered() {
|
||||
Log.d("Loki-RTC-vid", "first frame rendered")
|
||||
}
|
||||
|
||||
override fun onFrameResolutionChanged(p0: Int, p1: Int, p2: Int) {
|
||||
Log.d("Loki-RTC-vid", "frame resolution changed")
|
||||
}
|
||||
|
||||
override fun onSignalingChange(p0: PeerConnection.SignalingState?) {
|
||||
Log.d("Loki-RTC", "onSignalingChange: $p0")
|
||||
}
|
||||
|
@ -135,18 +176,48 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
|
|||
|
||||
override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) {
|
||||
Log.d("Loki-RTC", "onIceGatheringChange: $p0")
|
||||
p0 ?: return
|
||||
Log.d("Loki-RTC","sending IceCandidates of size: ${candidates.size}")
|
||||
MessageSender.sendNonDurably(
|
||||
CallMessage(SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES,
|
||||
candidates.map { it.sdp },
|
||||
candidates.map { it.sdpMLineIndex },
|
||||
candidates.map { it.sdpMid }
|
||||
),
|
||||
callAddress
|
||||
)
|
||||
}
|
||||
|
||||
override fun onIceCandidate(p0: IceCandidate?) {
|
||||
Log.d("Loki-RTC", "onIceCandidate: $p0")
|
||||
override fun onIceCandidate(iceCandidate: IceCandidate?) {
|
||||
Log.d("Loki-RTC", "onIceCandidate: $iceCandidate")
|
||||
if (iceCandidate == null) return
|
||||
// TODO: in a lokinet world, these might have to be filtered specifically to drop anything that is not .loki
|
||||
peerConnection.addIceCandidate(iceCandidate)
|
||||
candidates.add(iceCandidate)
|
||||
iceDebouncer.publish {
|
||||
MessageSender.sendNonDurably(
|
||||
CallMessage(SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES,
|
||||
candidates.map { it.sdp },
|
||||
candidates.map { it.sdpMLineIndex },
|
||||
candidates.map { it.sdpMid }
|
||||
),
|
||||
callAddress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIceCandidatesRemoved(p0: Array<out IceCandidate>?) {
|
||||
Log.d("Loki-RTC", "onIceCandidatesRemoved: $p0")
|
||||
peerConnection.removeIceCandidates(p0)
|
||||
}
|
||||
|
||||
override fun onAddStream(p0: MediaStream?) {
|
||||
Log.d("Loki-RTC", "onAddStream: $p0")
|
||||
override fun onAddStream(remoteStream: MediaStream?) {
|
||||
Log.d("Loki-RTC", "onAddStream: $remoteStream")
|
||||
if (remoteStream == null) {
|
||||
return
|
||||
}
|
||||
|
||||
remoteStream.videoTracks.firstOrNull()?.addSink(remote_renderer)
|
||||
}
|
||||
|
||||
override fun onRemoveStream(p0: MediaStream?) {
|
||||
|
@ -165,8 +236,25 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
|
|||
Log.d("Loki-RTC", "onAddTrack: $p0: $p1")
|
||||
}
|
||||
|
||||
override fun onCreateSuccess(p0: SessionDescription) {
|
||||
Log.d("Loki-RTC", "onCreateSuccess: ${p0.description}, ${p0.type}")
|
||||
override fun onCreateSuccess(sdp: SessionDescription) {
|
||||
Log.d("Loki-RTC", "onCreateSuccess: ${sdp.type}")
|
||||
when (sdp.type) {
|
||||
SessionDescription.Type.OFFER -> {
|
||||
peerConnection.setLocalDescription(this, sdp)
|
||||
MessageSender.sendNonDurably(
|
||||
CallMessage(SignalServiceProtos.CallMessage.Type.OFFER, listOf(sdp.description), listOf(), listOf()),
|
||||
callAddress
|
||||
)
|
||||
}
|
||||
SessionDescription.Type.ANSWER -> {
|
||||
peerConnection.setLocalDescription(this, sdp)
|
||||
MessageSender.sendNonDurably(
|
||||
CallMessage(SignalServiceProtos.CallMessage.Type.ANSWER, listOf(sdp.description), listOf(), listOf()),
|
||||
callAddress
|
||||
)
|
||||
}
|
||||
SessionDescription.Type.PRANSWER -> TODO("do the PR answer create success handling") // MessageSender.send()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSetSuccess() {
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.*
|
||||
import org.thoughtcrime.securesms.calls.WebRtcTestsActivity
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
|
||||
|
@ -98,6 +99,11 @@ object ConversationMenuHelper {
|
|||
inflater.inflate(R.menu.menu_conversation_notification_settings, menu)
|
||||
}
|
||||
|
||||
// Call Tests
|
||||
if (!isOpenGroup) {
|
||||
inflater.inflate(R.menu.menu_conversation_call, menu)
|
||||
}
|
||||
|
||||
// Search
|
||||
val searchViewItem = menu.findItem(R.id.menu_search)
|
||||
(context as ConversationActivityV2).searchViewItem = searchViewItem
|
||||
|
@ -158,6 +164,7 @@ object ConversationMenuHelper {
|
|||
R.id.menu_unmute_notifications -> { unmute(context, thread) }
|
||||
R.id.menu_mute_notifications -> { mute(context, thread) }
|
||||
R.id.menu_notification_settings -> { setNotifyType(context, thread) }
|
||||
R.id.menu_call -> { call(context, thread) }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -174,6 +181,13 @@ object ConversationMenuHelper {
|
|||
searchViewModel.onSearchOpened()
|
||||
}
|
||||
|
||||
private fun call(context: Context, thread: Recipient) {
|
||||
val intent = Intent(context, WebRtcTestsActivity::class.java)
|
||||
intent.putExtra(WebRtcTestsActivity.EXTRA_ADDRESS, thread.address)
|
||||
val activity = context as AppCompatActivity
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private fun addShortcut(context: Context, thread: Recipient) {
|
||||
object : AsyncTask<Void?, Void?, IconCompat?>() {
|
||||
|
|
|
@ -12,6 +12,7 @@ import android.text.SpannableString
|
|||
import android.text.style.ForegroundColorSpan
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
@ -32,8 +33,10 @@ import org.greenrobot.eventbus.Subscribe
|
|||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.utilities.WebRtcUtils
|
||||
import org.session.libsession.utilities.*
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.ThreadUtils
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
|
@ -53,6 +56,7 @@ import org.thoughtcrime.securesms.mms.GlideApp
|
|||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.onboarding.SeedActivity
|
||||
import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate
|
||||
import org.thoughtcrime.securesms.preferences.SettingsActivity
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
@ -131,6 +135,36 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
|
|||
}
|
||||
this.broadcastReceiver = broadcastReceiver
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged"))
|
||||
lifecycleScope.launchWhenCreated {
|
||||
// web rtc channel handling
|
||||
for (message in WebRtcUtils.SIGNAL_QUEUE) {
|
||||
val sender = Address.fromSerialized(message.sender!!)
|
||||
val intent = Intent(this@HomeActivity, WebRtcTestsActivity::class.java)
|
||||
val bundle = bundleOf(
|
||||
WebRtcTestsActivity.EXTRA_ADDRESS to sender,
|
||||
)
|
||||
if (message.sdps.isNotEmpty() && message.sdpMids.isEmpty()) {
|
||||
// offer message
|
||||
Log.d("Loki-RTC", "answer receive")
|
||||
val sdps = message.sdps
|
||||
intent.action = WebRtcTestsActivity.ACTION_ANSWER
|
||||
bundle.putStringArray(WebRtcTestsActivity.EXTRA_SDP, sdps.toTypedArray())
|
||||
} else if (message.sdpMids.isNotEmpty()) {
|
||||
// ice candidates message
|
||||
Log.d("Loki-RTC", "update ice intent")
|
||||
val sdpMLineIndexes = message.sdpMLineIndexes
|
||||
val sdpMids = message.sdpMids
|
||||
val sdps = message.sdps
|
||||
intent.action = WebRtcTestsActivity.ACTION_UPDATE_ICE
|
||||
bundle.putStringArray(WebRtcTestsActivity.EXTRA_SDP, sdps.toTypedArray())
|
||||
bundle.putIntArray(WebRtcTestsActivity.EXTRA_SDP_MLINE_INDEXES, sdpMLineIndexes.toIntArray())
|
||||
bundle.putStringArray(WebRtcTestsActivity.EXTRA_SDP_MIDS, sdpMids.toTypedArray())
|
||||
}
|
||||
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
lifecycleScope.launchWhenStarted {
|
||||
launch(Dispatchers.IO) {
|
||||
// Double check that the long poller is up
|
||||
|
@ -400,8 +434,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
|
|||
}
|
||||
|
||||
private fun openSettings() {
|
||||
val intent = Intent(this, WebRtcTestsActivity::class.java)
|
||||
// val intent = Intent(this, SettingsActivity::class.java)
|
||||
val intent = Intent(this, SettingsActivity::class.java)
|
||||
show(intent, isForResult = false)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20.01,15.38c-1.23,0 -2.42,-0.2 -3.53,-0.56 -0.35,-0.12 -0.74,-0.03 -1.01,0.24l-1.57,1.97c-2.83,-1.35 -5.48,-3.9 -6.89,-6.83l1.95,-1.66c0.27,-0.28 0.35,-0.67 0.24,-1.02 -0.37,-1.11 -0.56,-2.3 -0.56,-3.53 0,-0.54 -0.45,-0.99 -0.99,-0.99H4.19C3.65,3 3,3.24 3,3.99 3,13.28 10.73,21 20.01,21c0.71,0 0.99,-0.63 0.99,-1.18v-3.45c0,-0.54 -0.45,-0.99 -0.99,-0.99z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:title="@string/conversation_context__menu_call"
|
||||
android:icon="@drawable/ic_baseline_call_24"
|
||||
app:showAsAction="always"
|
||||
android:id="@+id/menu_call"/>
|
||||
</menu>
|
|
@ -585,6 +585,7 @@
|
|||
<string name="conversation_context__menu_ban_and_delete_all">Ban and delete all</string>
|
||||
<string name="conversation_context__menu_resend_message">Resend message</string>
|
||||
<string name="conversation_context__menu_reply_to_message">Reply to message</string>
|
||||
<string name="conversation_context__menu_call">Call</string>
|
||||
|
||||
<!-- conversation_context_image -->
|
||||
<string name="conversation_context_image__save_attachment">Save attachment</string>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
import org.session.libsignal.protos.SignalServiceProtos
|
||||
import org.session.libsignal.utilities.Log
|
||||
|
||||
class CallMessage(): ControlMessage() {
|
||||
var type: SignalServiceProtos.CallMessage.Type? = null
|
||||
var sdps: List<String> = listOf()
|
||||
var sdpMLineIndexes: List<Int> = listOf()
|
||||
var sdpMids: List<String> = listOf()
|
||||
|
||||
override val isSelfSendValid: Boolean = false
|
||||
|
||||
override fun isValid(): Boolean = super.isValid() && type != null && !sdps.isNullOrEmpty()
|
||||
|
||||
constructor(type: SignalServiceProtos.CallMessage.Type,
|
||||
sdps: List<String>,
|
||||
sdpMLineIndexes: List<Int>,
|
||||
sdpMids: List<String>) : this() {
|
||||
this.type = type
|
||||
this.sdps = sdps
|
||||
this.sdpMLineIndexes = sdpMLineIndexes
|
||||
this.sdpMids = sdpMids
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "CallMessage"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): CallMessage? {
|
||||
val callMessageProto = if (proto.hasCallMessage()) proto.callMessage else return null
|
||||
val type = callMessageProto.type
|
||||
val sdps = callMessageProto.sdpsList
|
||||
val sdpMLineIndexes = callMessageProto.sdpMLineIndexesList
|
||||
val sdpMids = callMessageProto.sdpMidsList
|
||||
return CallMessage(type,sdps, sdpMLineIndexes, sdpMids)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toProto(): SignalServiceProtos.Content? {
|
||||
val nonNullType = type ?: run {
|
||||
Log.w(TAG,"Couldn't construct call message request proto from: $this")
|
||||
return null
|
||||
}
|
||||
|
||||
val callMessage = SignalServiceProtos.CallMessage.newBuilder()
|
||||
.setType(nonNullType)
|
||||
.addAllSdps(sdps)
|
||||
.addAllSdpMLineIndexes(sdpMLineIndexes)
|
||||
.addAllSdpMids(sdpMids)
|
||||
|
||||
return SignalServiceProtos.Content.newBuilder()
|
||||
.setCallMessage(
|
||||
callMessage
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -95,6 +95,7 @@ object MessageReceiver {
|
|||
ExpirationTimerUpdate.fromProto(proto) ?:
|
||||
ConfigurationMessage.fromProto(proto) ?:
|
||||
UnsendRequest.fromProto(proto) ?:
|
||||
CallMessage.fromProto(proto) ?:
|
||||
VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage
|
||||
// Ignore self send if needed
|
||||
if (!message.isSelfSendValid && sender == userPublicKey) throw Error.SelfSend
|
||||
|
|
|
@ -14,24 +14,20 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi
|
|||
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||
import org.session.libsession.messaging.utilities.WebRtcUtils
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.GroupRecord
|
||||
import org.session.libsession.utilities.*
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.SSKEnvironment
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.ProfileKeyUtil
|
||||
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
||||
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.session.libsignal.messages.SignalServiceGroup
|
||||
import org.session.libsignal.protos.SignalServiceProtos
|
||||
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
@ -52,6 +48,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
|
|||
is ConfigurationMessage -> handleConfigurationMessage(message)
|
||||
is UnsendRequest -> handleUnsendRequest(message)
|
||||
is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID)
|
||||
is CallMessage -> handleCallMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +58,11 @@ private fun MessageReceiver.handleReadReceipt(message: ReadReceipt) {
|
|||
SSKEnvironment.shared.readReceiptManager.processReadReceipts(context, message.sender!!, message.timestamps!!, message.receivedTimestamp!!)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleCallMessage(message: CallMessage) {
|
||||
// TODO: refactor this out to persistence, just to help debug the flow and send/receive in synchronous testing
|
||||
WebRtcUtils.SIGNAL_QUEUE.offer(message)
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleTypingIndicator(message: TypingIndicator) {
|
||||
when (message.kind!!) {
|
||||
TypingIndicator.Kind.STARTED -> showTypingIndicatorIfNeeded(message.sender!!)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package org.session.libsession.messaging.utilities
|
||||
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import org.session.libsession.messaging.messages.control.CallMessage
|
||||
|
||||
object WebRtcUtils {
|
||||
|
||||
// TODO: move this to a better place that is persistent
|
||||
val SIGNAL_QUEUE = Channel<CallMessage>(Channel.UNLIMITED)
|
||||
|
||||
}
|
|
@ -168,12 +168,15 @@ message CallMessage {
|
|||
OFFER = 1;
|
||||
ANSWER = 2;
|
||||
PROVISIONAL_ANSWER = 3;
|
||||
ICE_CANDIDATES = 4;
|
||||
END_CALL = 5;
|
||||
}
|
||||
|
||||
// @required
|
||||
required Type type = 1;
|
||||
// @required
|
||||
required string sdp = 2;
|
||||
required Type type = 1;
|
||||
repeated string sdps = 2;
|
||||
repeated uint32 sdpMLineIndexes = 3;
|
||||
repeated string sdpMids = 4;
|
||||
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue