feat: hanging up and bottom sheet behaviors should work now

This commit is contained in:
Harris
2021-09-06 17:06:03 +10:00
parent b3576336d9
commit 459dfa72c2
4 changed files with 126 additions and 61 deletions

View File

@@ -1,6 +1,9 @@
package org.thoughtcrime.securesms.calls package org.thoughtcrime.securesms.calls
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_webrtc_tests.* import kotlinx.android.synthetic.main.activity_webrtc_tests.*
@@ -9,6 +12,7 @@ import kotlinx.coroutines.isActive
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.WebRtcUtils
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Debouncer import org.session.libsession.utilities.Debouncer
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
@@ -28,7 +32,7 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
private const val LOCAL_STREAM_ID = "local_track" private const val LOCAL_STREAM_ID = "local_track"
const val ACTION_ANSWER = "answer" const val ACTION_ANSWER = "answer"
const val ACTION_UPDATE_ICE = "updateIce" const val ACTION_END = "end-call"
const val EXTRA_SDP = "WebRtcTestsActivity_EXTRA_SDP" const val EXTRA_SDP = "WebRtcTestsActivity_EXTRA_SDP"
const val EXTRA_ADDRESS = "WebRtcTestsActivity_EXTRA_ADDRESS" const val EXTRA_ADDRESS = "WebRtcTestsActivity_EXTRA_ADDRESS"
@@ -42,6 +46,8 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
private val audioSource by lazy { connectionFactory.createAudioSource(MediaConstraints()) } private val audioSource by lazy { connectionFactory.createAudioSource(MediaConstraints()) }
private val videoCapturer by lazy { createCameraCapturer(Camera2Enumerator(this)) } private val videoCapturer by lazy { createCameraCapturer(Camera2Enumerator(this)) }
private val acceptedCallMessageHashes = mutableSetOf<Int>()
private val connectionFactory by lazy { private val connectionFactory by lazy {
val decoderFactory = DefaultVideoDecoderFactory(eglBase.eglBaseContext) val decoderFactory = DefaultVideoDecoderFactory(eglBase.eglBaseContext)
@@ -127,13 +133,44 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
peerConnection.createOffer(this, MediaConstraints()) peerConnection.createOffer(this, MediaConstraints())
} }
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenCreated {
while (this.isActive) { while (this.isActive) {
delay(5_000L) val answer = synchronized(WebRtcUtils.callCache) {
peerConnection.getStats(this@WebRtcTestsActivity) WebRtcUtils.callCache[callAddress]?.firstOrNull { it.type == SignalServiceProtos.CallMessage.Type.ANSWER }
}
if (answer != null) {
peerConnection.setRemoteDescription(
this@WebRtcTestsActivity,
SessionDescription(SessionDescription.Type.ANSWER, answer.sdps[0])
)
break
}
delay(2_000L)
} }
} }
registerReceiver(object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
endCall()
}
}, IntentFilter(ACTION_END))
lifecycleScope.launchWhenResumed {
while (this.isActive) {
delay(2_000L)
peerConnection.getStats(this@WebRtcTestsActivity)
synchronized(WebRtcUtils.callCache) {
val set = WebRtcUtils.callCache[callAddress] ?: mutableSetOf()
set.filter { it.hashCode() !in acceptedCallMessageHashes
&& it.type == SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES }.forEach { callMessage ->
callMessage.iceCandidates().forEach { candidate ->
peerConnection.addIceCandidate(candidate)
}
acceptedCallMessageHashes.add(callMessage.hashCode())
}
}
}
}
} }
override fun onStatsDelivered(statsReport: RTCStatsReport?) { override fun onStatsDelivered(statsReport: RTCStatsReport?) {
@@ -143,11 +180,11 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
} }
private fun endCall() { private fun endCall() {
peerConnection.close()
MessageSender.sendNonDurably( MessageSender.sendNonDurably(
CallMessage(SignalServiceProtos.CallMessage.Type.END_CALL,emptyList(),emptyList(), emptyList()), CallMessage.endCall(),
callAddress callAddress
) )
peerConnection.close()
finish() finish()
} }
@@ -156,29 +193,6 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
endCall() endCall()
} }
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): CameraVideoCapturer? { private fun createCameraCapturer(enumerator: CameraEnumerator): CameraVideoCapturer? {
val deviceNames = enumerator.deviceNames val deviceNames = enumerator.deviceNames
@@ -316,4 +330,11 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
Log.d("Loki-RTC", "onSetFailure: $p0") Log.d("Loki-RTC", "onSetFailure: $p0")
} }
private fun CallMessage.iceCandidates(): List<IceCandidate> {
val candidateSize = sdpMids.size
return (0 until candidateSize).map { i ->
IceCandidate(sdpMids[i], sdpMLineIndexes[i], sdps[i])
}
}
} }

View File

@@ -20,6 +20,7 @@ import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader import androidx.loader.content.Loader
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.android.synthetic.main.seed_reminder_stub.* import kotlinx.android.synthetic.main.seed_reminder_stub.*
import kotlinx.android.synthetic.main.seed_reminder_stub.view.* import kotlinx.android.synthetic.main.seed_reminder_stub.view.*
@@ -42,6 +43,7 @@ import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.MuteDialog import org.thoughtcrime.securesms.MuteDialog
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.calls.WebRtcTestsActivity
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
@@ -138,6 +140,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
// web rtc channel handling // web rtc channel handling
for (message in WebRtcUtils.SIGNAL_QUEUE) { for (message in WebRtcUtils.SIGNAL_QUEUE) {
val sender = Address.fromSerialized(message.sender!!) val sender = Address.fromSerialized(message.sender!!)
synchronized(WebRtcUtils.callCache) { synchronized(WebRtcUtils.callCache) {
val set = WebRtcUtils.callCache[sender] ?: mutableSetOf() val set = WebRtcUtils.callCache[sender] ?: mutableSetOf()
@@ -156,37 +159,21 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
show(this@HomeActivity.supportFragmentManager,"call-sheet") show(this@HomeActivity.supportFragmentManager,"call-sheet")
} }
} }
ICE_CANDIDATES -> {
// do nothing, already have candidates saved
}
END_CALL -> { END_CALL -> {
// do nothing, maybe clear the callCache or something // dismiss the call sheet
supportFragmentManager.findFragmentByTag("call-sheet")?.let { callSheet ->
if (callSheet is BottomSheetDialogFragment) {
callSheet.dismiss()
} }
} }
// val intent = Intent(this@HomeActivity, WebRtcTestsActivity::class.java) // clear the callCache for this sender
// val bundle = bundleOf( synchronized(WebRtcUtils.callCache) {
// WebRtcTestsActivity.EXTRA_ADDRESS to sender, WebRtcUtils.callCache[sender] = mutableSetOf()
// ) }
// if (message.sdps.isNotEmpty() && message.sdpMids.isEmpty()) { sendBroadcast(Intent(WebRtcTestsActivity.ACTION_END))
// // offer message }
// Log.d("Loki-RTC", "answer receive") else -> { /* do nothing */ }
// 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 { lifecycleScope.launchWhenStarted {

View File

@@ -1,14 +1,21 @@
package org.thoughtcrime.securesms.webrtc package org.thoughtcrime.securesms.webrtc
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.* import kotlinx.android.synthetic.main.fragment_call_bottom_sheet.*
import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.nameTextView
import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.profilePictureView
import network.loki.messenger.R 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.Address
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.calls.WebRtcTestsActivity
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
class CallBottomSheet: BottomSheetDialogFragment() { class CallBottomSheet: BottomSheetDialogFragment() {
@@ -19,6 +26,8 @@ class CallBottomSheet: BottomSheetDialogFragment() {
const val ARGUMENT_TYPE = "CallBottomSheet_TYPE" const val ARGUMENT_TYPE = "CallBottomSheet_TYPE"
} }
private lateinit var address: Address
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@@ -30,9 +39,8 @@ class CallBottomSheet: BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val address = arguments?.getParcelable<Address>(ARGUMENT_ADDRESS) ?: return dismiss() address = arguments?.getParcelable(ARGUMENT_ADDRESS) ?: return dismiss()
val sdp = arguments?.getStringArray(ARGUMENT_SDP) ?: return dismiss() val sdp = arguments?.getStringArray(ARGUMENT_SDP) ?: return dismiss()
val type = arguments?.getString(ARGUMENT_TYPE) ?: return dismiss()
val recipient = Recipient.from(requireContext(), address, false) val recipient = Recipient.from(requireContext(), address, false)
profilePictureView.publicKey = address.serialize() profilePictureView.publicKey = address.serialize()
profilePictureView.glide = GlideApp.with(this) profilePictureView.glide = GlideApp.with(this)
@@ -41,5 +49,23 @@ class CallBottomSheet: BottomSheetDialogFragment() {
nameTextView.text = recipient.name ?: address.serialize() nameTextView.text = recipient.name ?: address.serialize()
acceptButton.setOnClickListener {
val intent = Intent(requireContext(), WebRtcTestsActivity::class.java)
val bundle = bundleOf(
WebRtcTestsActivity.EXTRA_ADDRESS to address,
)
intent.action = WebRtcTestsActivity.ACTION_ANSWER
bundle.putStringArray(WebRtcTestsActivity.EXTRA_SDP, sdp)
intent.putExtras(bundle)
startActivity(intent)
dismiss()
}
declineButton.setOnClickListener {
MessageSender.sendNonDurably(CallMessage.endCall(), address)
dismiss()
}
} }
} }

View File

@@ -13,7 +13,8 @@ class CallMessage(): ControlMessage() {
override val ttl: Long = 5 * 60 * 1000 override val ttl: Long = 5 * 60 * 1000
override fun isValid(): Boolean = super.isValid() && type != null && !sdps.isNullOrEmpty() override fun isValid(): Boolean = super.isValid() && type != null
&& (!sdps.isNullOrEmpty() || type == SignalServiceProtos.CallMessage.Type.END_CALL)
constructor(type: SignalServiceProtos.CallMessage.Type, constructor(type: SignalServiceProtos.CallMessage.Type,
sdps: List<String>, sdps: List<String>,
@@ -28,6 +29,8 @@ class CallMessage(): ControlMessage() {
companion object { companion object {
const val TAG = "CallMessage" const val TAG = "CallMessage"
fun endCall() = CallMessage(SignalServiceProtos.CallMessage.Type.END_CALL, emptyList(), emptyList(), emptyList())
fun fromProto(proto: SignalServiceProtos.Content): CallMessage? { fun fromProto(proto: SignalServiceProtos.Content): CallMessage? {
val callMessageProto = if (proto.hasCallMessage()) proto.callMessage else return null val callMessageProto = if (proto.hasCallMessage()) proto.callMessage else return null
val type = callMessageProto.type val type = callMessageProto.type
@@ -56,4 +59,32 @@ class CallMessage(): ControlMessage() {
) )
.build() .build()
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CallMessage
if (type != other.type) return false
if (sdps != other.sdps) return false
if (sdpMLineIndexes != other.sdpMLineIndexes) return false
if (sdpMids != other.sdpMids) return false
if (isSelfSendValid != other.isSelfSendValid) return false
if (ttl != other.ttl) return false
return true
}
override fun hashCode(): Int {
var result = type?.hashCode() ?: 0
result = 31 * result + sdps.hashCode()
result = 31 * result + sdpMLineIndexes.hashCode()
result = 31 * result + sdpMids.hashCode()
result = 31 * result + isSelfSendValid.hashCode()
result = 31 * result + ttl.hashCode()
return result
}
} }