diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2a3a1b8616..6f3d8e4ff9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,7 +31,6 @@
android:required="false" />
-
@@ -53,7 +52,6 @@
-
diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt
index 6b454a4b48..16b45a2484 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt
@@ -10,21 +10,26 @@ import android.os.Bundle
import android.view.MenuItem
import android.view.WindowManager
import androidx.activity.viewModels
-import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
-import com.jakewharton.rxbinding3.view.clicks
+import com.bumptech.glide.load.engine.DiskCacheStrategy
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.android.synthetic.main.activity_webrtc_tests.*
+import kotlinx.android.synthetic.main.activity_webrtc.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import network.loki.messenger.R
+import org.session.libsession.avatars.ProfileContactPhoto
+import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
+import org.thoughtcrime.securesms.dependencies.DatabaseComponent
+import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.service.WebRtcCallService
+import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
import org.thoughtcrime.securesms.webrtc.CallViewModel
+import org.thoughtcrime.securesms.webrtc.CallViewModel.State.*
import org.webrtc.IceCandidate
import java.util.*
@@ -45,6 +50,7 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
private val viewModel by viewModels()
private val candidates: MutableList = mutableListOf()
+ private val glide by lazy { GlideApp.with(this) }
private lateinit var callAddress: Address
private lateinit var callId: UUID
@@ -63,7 +69,7 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
super.onCreate(savedInstanceState, ready)
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- setContentView(R.layout.activity_webrtc_tests)
+ setContentView(R.layout.activity_webrtc)
volumeControlStream = AudioManager.STREAM_VOICE_CALL
initializeResources()
@@ -88,7 +94,13 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
},IntentFilter(ACTION_END))
enableCameraButton.setOnClickListener {
- startService(WebRtcCallService.cameraEnabled(this, true))
+ Permissions.with(this)
+ .request(Manifest.permission.CAMERA)
+ .onAllGranted {
+ val intent = WebRtcCallService.cameraEnabled(this, !viewModel.videoEnabled)
+ startService(intent)
+ }
+ .execute()
}
switchCameraButton.setOnClickListener {
@@ -99,7 +111,6 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
startService(WebRtcCallService.hangupIntent(this))
}
-
}
private fun initializeResources() {
@@ -115,22 +126,69 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
uiJob = lifecycleScope.launch {
- viewModel.callState.collect { state ->
- if (state == CallViewModel.State.CALL_CONNECTED) {
- // call connected, render the surfaces
- remote_renderer.removeAllViews()
- local_renderer.removeAllViews()
- viewModel.remoteRenderer?.let { remote_renderer.addView(it) }
- viewModel.localRenderer?.let { local_renderer.addView(it) }
+ launch {
+ viewModel.callState.collect { state ->
+ remote_loading_view.isVisible = state != CALL_CONNECTED
}
}
- viewModel.remoteVideoEnabledState.collect {
+ launch {
+ viewModel.recipient.collect { latestRecipient ->
+ if (latestRecipient.recipient != null) {
+ val signalProfilePicture = latestRecipient.recipient.contactPhoto
+ val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
+ if (signalProfilePicture != null && avatar != "0" && avatar != "") {
+ glide.clear(remote_recipient)
+ glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(remote_recipient)
+ } else {
+ val publicKey = latestRecipient.recipient.address.serialize()
+ val displayName = getUserDisplayName(publicKey)
+ val sizeInPX = resources.getDimensionPixelSize(R.dimen.extra_large_profile_picture_size)
+ glide.clear(remote_recipient)
+ glide.load(AvatarPlaceholderGenerator.generate(this@WebRtcCallActivity, sizeInPX, publicKey, displayName))
+ .diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(remote_recipient)
+ }
+ } else {
+ glide.clear(remote_recipient)
+ }
+ }
+ }
+ launch {
+ viewModel.localVideoEnabledState.collect { isEnabled ->
+ local_renderer.removeAllViews()
+ if (isEnabled) {
+ viewModel.localRenderer?.let { surfaceView ->
+ surfaceView.setZOrderOnTop(true)
+ local_renderer.addView(surfaceView)
+ }
+ }
+ local_renderer.isVisible = isEnabled
+ enableCameraButton.setImageResource(
+ if (isEnabled) R.drawable.ic_baseline_videocam_off_24
+ else R.drawable.ic_baseline_videocam_24
+ )
+ }
+ }
+
+ launch {
+ viewModel.remoteVideoEnabledState.collect { isEnabled ->
+ remote_renderer.removeAllViews()
+ if (isEnabled) {
+ viewModel.remoteRenderer?.let { remote_renderer.addView(it) }
+ }
+ remote_renderer.isVisible = isEnabled
+ remote_recipient.isVisible = !isEnabled
+ }
}
}
}
+ fun getUserDisplayName(publicKey: String): String {
+ val contact = DatabaseComponent.get(this).sessionContactDatabase().getContactWithSessionID(publicKey)
+ return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey
+ }
+
override fun onStop() {
super.onStop()
uiJob?.cancel()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt
index 4d17148530..40575838d1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt
@@ -194,11 +194,9 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
action == ACTION_BLUETOOTH_CHANGE -> handleBluetoothChange(intent)
action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent)
action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent)
- action == ACTION_REMOTE_VIDEO_MUTE -> handleRemoteVideoMute(intent)
action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent)
action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
- action == ACTION_CALL_CONNECTED -> handleCallConnected(intent)
action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent)
action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent)
}
@@ -285,6 +283,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
callManager.clearPendingIceUpdates()
callManager.onIncomingRing(offer, callId, recipient, timestamp)
callManager.postConnectionEvent(STATE_LOCAL_RINGING)
+ callManager.postViewModelState(CallViewModel.State.CALL_RINGING)
if (TextSecurePreferences.isCallNotificationsEnabled(this)) {
callManager.startIncomingRinger()
}
@@ -440,14 +439,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
callManager.handleScreenOffChange()
}
- private fun handleRemoteVideoMute(intent: Intent) {
- val muted = intent.getBooleanExtra(EXTRA_MUTE, false)
- val callId = intent.getSerializableExtra(EXTRA_CALL_ID) as UUID
-
- callManager.handleRemoteVideoMute(muted, callId)
- }
-
-
private fun handleResponseMessage(intent: Intent) {
try {
val recipient = getRemoteRecipient(intent)
@@ -492,10 +483,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
callManager.startCommunication(lockManager)
}
- private fun handleCallConnected(intent: Intent) {
-
- }
-
private fun handleIsInCallQuery(intent: Intent) {
}
@@ -508,9 +495,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
}
}
-
-
-
private fun handleCheckTimeout(intent: Intent) {
val callId = callManager.callId ?: return
val callState = callManager.currentConnectionState
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt
index a8478d6663..bec87dd648 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt
@@ -2,11 +2,18 @@ package org.thoughtcrime.securesms.webrtc
import android.content.Context
import android.telephony.TelephonyManager
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import nl.komponents.kovenant.Promise
+import nl.komponents.kovenant.functional.bind
+import nl.komponents.kovenant.task
+import nl.komponents.kovenant.then
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.utilities.Debouncer
@@ -15,6 +22,7 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES
import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.*
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
@@ -23,7 +31,6 @@ import org.thoughtcrime.securesms.webrtc.locks.LockManager
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
import org.thoughtcrime.securesms.webrtc.video.CameraState
import org.webrtc.*
-import java.lang.NullPointerException
import java.nio.ByteBuffer
import java.util.*
import java.util.concurrent.Executors
@@ -40,6 +47,11 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
data class AudioEnabled(val isEnabled: Boolean): StateEvent()
data class VideoEnabled(val isEnabled: Boolean): StateEvent()
data class CallStateUpdate(val state: CallState): StateEvent()
+ data class RecipientUpdate(val recipient: Recipient?): StateEvent() {
+ companion object {
+ val UNKNOWN = RecipientUpdate(recipient = null)
+ }
+ }
}
companion object {
@@ -75,21 +87,23 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
peerConnectionObservers.remove(listener)
}
- private val _audioEvents = MutableStateFlow(StateEvent.AudioEnabled(false))
+ private val _audioEvents = MutableStateFlow(AudioEnabled(false))
val audioEvents = _audioEvents.asSharedFlow()
- private val _videoEvents = MutableStateFlow(StateEvent.VideoEnabled(false))
+ private val _videoEvents = MutableStateFlow(VideoEnabled(false))
val videoEvents = _videoEvents.asSharedFlow()
- private val _remoteVideoEvents = MutableStateFlow(StateEvent.VideoEnabled(false))
+ private val _remoteVideoEvents = MutableStateFlow(VideoEnabled(false))
val remoteVideoEvents = _remoteVideoEvents.asSharedFlow()
- private val _connectionEvents = MutableStateFlow(StateEvent.CallStateUpdate(CallState.STATE_IDLE))
+ private val _connectionEvents = MutableStateFlow(CallStateUpdate(CallState.STATE_IDLE))
val connectionEvents = _connectionEvents.asSharedFlow()
private val _callStateEvents = MutableStateFlow(CallViewModel.State.CALL_PENDING)
val callStateEvents = _callStateEvents.asSharedFlow()
+ private val _recipientEvents = MutableStateFlow(RecipientUpdate.UNKNOWN)
+ val recipientEvents = _recipientEvents.asSharedFlow()
private var localCameraState: CameraState = CameraState.UNKNOWN
private var bluetoothAvailable = false
val currentConnectionState
- get() = (_connectionEvents.value as StateEvent.CallStateUpdate).state
+ get() = (_connectionEvents.value as CallStateUpdate).state
private val networkExecutor = Executors.newSingleThreadExecutor()
@@ -99,6 +113,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
var pendingOfferTime: Long = -1
var callId: UUID? = null
var recipient: Recipient? = null
+ set(value) {
+ field = value
+ _recipientEvents.value = StateEvent.RecipientUpdate(value)
+ }
fun getCurrentCallState(): Pair = currentConnectionState to callId
@@ -131,7 +149,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
}
fun postConnectionEvent(newState: CallState) {
- _connectionEvents.value = StateEvent.CallStateUpdate(newState)
+ _connectionEvents.value = CallStateUpdate(newState)
}
fun postViewModelState(newState: CallViewModel.State) {
@@ -192,14 +210,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
fun setAudioEnabled(isEnabled: Boolean) {
currentConnectionState.withState(*(CONNECTED_STATES + PENDING_CONNECTION_STATES)) {
peerConnection?.setAudioEnabled(isEnabled)
- _audioEvents.value = StateEvent.AudioEnabled(true)
- }
- }
-
- fun setVideoEnabled(isEnabled: Boolean) {
- currentConnectionState.withState(*(CONNECTED_STATES + PENDING_CONNECTION_STATES)) {
- peerConnection?.setVideoEnabled(isEnabled)
- _videoEvents.value = StateEvent.VideoEnabled(true)
+ _audioEvents.value = AudioEnabled(true)
}
}
@@ -299,7 +310,13 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
Log.i(TAG,"onMessage...")
buffer ?: return
- Log.i(TAG,"received: ${buffer.data}")
+ try {
+ val byteArray = ByteArray(buffer.data.remaining()) { buffer.data[it] }
+ val videoEnabled = Json.decodeFromString(VideoEnabledMessage.serializer(), byteArray.decodeToString())
+ _remoteVideoEvents.value = VideoEnabled(videoEnabled.video)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to deserialize data channel message", e)
+ }
}
override fun onAudioDeviceChanged(activeDevice: AudioDevice, devices: Set) {
@@ -324,12 +341,13 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
remoteRenderer = null
eglBase = null
- _connectionEvents.value = StateEvent.CallStateUpdate(CallState.STATE_IDLE)
+ _connectionEvents.value = CallStateUpdate(CallState.STATE_IDLE)
localCameraState = CameraState.UNKNOWN
recipient = null
callId = null
- _audioEvents.value = StateEvent.AudioEnabled(false)
- _videoEvents.value = StateEvent.VideoEnabled(false)
+ _audioEvents.value = AudioEnabled(false)
+ _videoEvents.value = VideoEnabled(false)
+ _remoteVideoEvents.value = VideoEnabled(false)
pendingOutgoingIceUpdates.clear()
pendingIncomingIceUpdates.clear()
}
@@ -348,6 +366,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
startIncomingRinger()
}
+ fun onReconnect(newOffer: String): Promise {
+ return task {}
+ }
+
fun onIncomingCall(context: Context, isAlwaysTurn: Boolean = false): Promise {
val callId = callId ?: return Promise.ofFail(NullPointerException("callId is null"))
val recipient = recipient ?: return Promise.ofFail(NullPointerException("recipient is null"))
@@ -418,10 +440,14 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
Log.i(TAG, "Sending offer: ${offer.description}")
- return MessageSender.sendNonDurably(CallMessage.offer(
- offer.description,
+ return MessageSender.sendNonDurably(CallMessage.preOffer(
callId
- ), recipient.address)
+ ), recipient.address).bind {
+ MessageSender.sendNonDurably(CallMessage.offer(
+ offer.description,
+ callId
+ ), recipient.address)
+ }
}
fun callNotSetup(): Boolean =
@@ -450,13 +476,13 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
}
fun handleSetMuteAudio(muted: Boolean) {
- _audioEvents.value = StateEvent.AudioEnabled(!muted)
- peerConnection?.setAudioEnabled(_audioEvents.value.isEnabled)
+ _audioEvents.value = AudioEnabled(!muted)
+ peerConnection?.setAudioEnabled(!muted)
}
fun handleSetMuteVideo(muted: Boolean, lockManager: LockManager) {
- _videoEvents.value = StateEvent.VideoEnabled(!muted)
- peerConnection?.setVideoEnabled(_videoEvents.value.isEnabled)
+ _videoEvents.value = VideoEnabled(!muted)
+ peerConnection?.setVideoEnabled(!muted)
dataChannel?.let { channel ->
val toSend = if (muted) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON
val buffer = DataChannel.Buffer(ByteBuffer.wrap(toSend.toString().encodeToByteArray()), false)
@@ -507,17 +533,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
}
}
- fun handleRemoteVideoMute(muted: Boolean, intentCallId: UUID) {
- val recipient = recipient ?: return
- val callId = callId ?: return
- if (currentConnectionState != CallState.STATE_CONNECTED || callId != intentCallId) {
- Log.w(TAG,"Got video toggle for inactive call, ignoring..")
- return
- }
-
- _remoteVideoEvents.value = StateEvent.VideoEnabled(!muted)
- }
-
fun handleResponseMessage(recipient: Recipient, callId: UUID, answer: SessionDescription) {
if (currentConnectionState != CallState.STATE_DIALING || recipient != this.recipient || callId != this.callId) {
Log.w(TAG,"Got answer for recipient and call ID we're not currently dialing")
@@ -563,8 +578,15 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
if (localCameraState.enabled) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO)
else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL)
connection.setCommunicationMode()
- connection.setAudioEnabled(_audioEvents.value.isEnabled)
- connection.setVideoEnabled(localCameraState.enabled)
+ setAudioEnabled(true)
+ dataChannel?.let { channel ->
+ val toSend = if (!_videoEvents.value.isEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON
+ val buffer = DataChannel.Buffer(ByteBuffer.wrap(toSend.toString().encodeToByteArray()), false)
+ channel.send(buffer)
+ }
}
+ @Serializable
+ data class VideoEnabledMessage(val video: Boolean)
+
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt
index cb5239b363..4ed07aced5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt
@@ -21,13 +21,13 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) {
lifecycle.coroutineScope.launch {
while (isActive) {
val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive()
- Log.d("Loki", nextMessage.toString())
+ Log.d("Loki", nextMessage.type?.name ?: "CALL MESSAGE RECEIVED")
when (nextMessage.type) {
OFFER -> incomingCall(nextMessage)
ANSWER -> incomingAnswer(nextMessage)
END_CALL -> incomingHangup(nextMessage)
ICE_CANDIDATES -> handleIceCandidates(nextMessage)
- PRE_OFFER -> incomingCall(nextMessage)
+ PRE_OFFER -> incomingPreOffer(nextMessage)
PROVISIONAL_ANSWER -> {} // TODO: if necessary
}
}
@@ -69,6 +69,10 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) {
context.startService(iceIntent)
}
+ private fun incomingPreOffer(callMessage: CallMessage) {
+ // handle notification state
+ }
+
private fun incomingCall(callMessage: CallMessage) {
val recipientAddress = callMessage.sender ?: return
val callId = callMessage.callId ?: return
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt
index 1f98e8157f..a8519f22e5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt
@@ -1,8 +1,12 @@
package org.thoughtcrime.securesms.webrtc
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
import org.webrtc.SurfaceViewRenderer
import javax.inject.Inject
@@ -15,6 +19,11 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
val remoteRenderer: SurfaceViewRenderer?
get() = callManager.remoteRenderer
+ private var _videoEnabled: Boolean = false
+
+ val videoEnabled: Boolean
+ get() = _videoEnabled
+
enum class State {
CALL_PENDING,
@@ -31,14 +40,17 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
UNTRUSTED_IDENTITY,
}
- val localAudioEnabledState = callManager.audioEvents.map { it.isEnabled }
- val localVideoEnabledState = callManager.videoEvents.map { it.isEnabled }
- val remoteVideoEnabledState = callManager.remoteVideoEvents.map { it.isEnabled }
- val callState = callManager.callStateEvents
-
- // set up listeners for establishing connection toggling video / audio
- init {
-
- }
+ val localAudioEnabledState
+ get() = callManager.audioEvents.map { it.isEnabled }
+ val localVideoEnabledState
+ get() = callManager.videoEvents
+ .map { it.isEnabled }
+ .onEach { _videoEnabled = it }
+ val remoteVideoEnabledState
+ get() = callManager.remoteVideoEvents.map { it.isEnabled }
+ val callState
+ get() = callManager.callStateEvents
+ val recipient
+ get() = callManager.recipientEvents
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt
index 2b9464b757..4f0845292c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt
@@ -28,11 +28,11 @@ class PeerConnectionWrapper(context: Context,
get() = peerConnection.localDescription != null && peerConnection.remoteDescription != null
init {
- val stun = PeerConnection.IceServer.builder("stun:freyr.getsession.org:5349").createIceServer()
- val turn = PeerConnection.IceServer.builder("turn:freyr.getsession.org:5349").setUsername("webrtc").setPassword("webrtc").createIceServer()
- val iceServers = listOf(stun,turn)
+ val turn = PeerConnection.IceServer.builder("turn:freyr.getsession.org").setUsername("session").setPassword("session").createIceServer()
+ val iceServers = listOf(turn)
val constraints = MediaConstraints().apply {
+ optional.add(MediaConstraints.KeyValuePair("IceRestart", "true"))
optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"))
}
val audioConstraints = MediaConstraints().apply {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt
index d598bb8162..3aa74e22b6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt
@@ -66,9 +66,12 @@ class SignalAudioManager(private val context: Context,
private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null
fun handleCommand(command: AudioManagerCommand) {
+ if (command == AudioManagerCommand.Initialize) {
+ initialize()
+ return
+ }
handler?.post {
when (command) {
- is AudioManagerCommand.Initialize -> initialize()
is AudioManagerCommand.Shutdown -> shutdown()
is AudioManagerCommand.UpdateAudioDeviceState -> updateAudioDeviceState()
is AudioManagerCommand.Start -> start()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt
index f7177547ea..a7af86dc07 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt
@@ -32,6 +32,8 @@ class SignalBluetoothManager(
}
private set
+ private fun hasPermission() = false
+
private var bluetoothAdapter: BluetoothAdapter? = null
private var bluetoothDevice: BluetoothDevice? = null
private var bluetoothHeadset: BluetoothHeadset? = null
@@ -90,7 +92,7 @@ class SignalBluetoothManager(
Log.d(TAG, "stop(): state: $state")
- if (bluetoothAdapter == null) {
+ if (bluetoothAdapter == null || !hasPermission()) {
return
}
@@ -123,6 +125,7 @@ class SignalBluetoothManager(
fun startScoAudio(): Boolean {
handler.assertHandlerThread()
+ if (!hasPermission()) return false
Log.i(TAG, "startScoAudio(): $state attempts: $scoConnectionAttempts")
@@ -147,6 +150,7 @@ class SignalBluetoothManager(
fun stopScoAudio() {
handler.assertHandlerThread()
+ if (!hasPermission()) return
Log.i(TAG, "stopScoAudio(): $state")
@@ -162,6 +166,7 @@ class SignalBluetoothManager(
fun updateDevice() {
handler.assertHandlerThread()
+ if (!hasPermission()) return
Log.d(TAG, "updateDevice(): state: $state")
@@ -195,6 +200,7 @@ class SignalBluetoothManager(
private fun onBluetoothTimeout() {
Log.i(TAG, "onBluetoothTimeout: state: $state bluetoothHeadset: $bluetoothHeadset")
+ if (!hasPermission()) return
if (state == State.UNINITIALIZED || bluetoothHeadset == null || state != State.CONNECTING) {
return
diff --git a/app/src/main/res/drawable/ic_baseline_videocam_24.xml b/app/src/main/res/drawable/ic_baseline_videocam_24.xml
new file mode 100644
index 0000000000..340bff20c2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_videocam_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_videocam_off_24.xml b/app/src/main/res/drawable/ic_baseline_videocam_off_24.xml
new file mode 100644
index 0000000000..7977a645c3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_videocam_off_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_webrtc_tests.xml b/app/src/main/res/layout/activity_webrtc.xml
similarity index 78%
rename from app/src/main/res/layout/activity_webrtc_tests.xml
rename to app/src/main/res/layout/activity_webrtc.xml
index c858ef2ecd..9236eec887 100644
--- a/app/src/main/res/layout/activity_webrtc_tests.xml
+++ b/app/src/main/res/layout/activity_webrtc.xml
@@ -16,7 +16,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
+
+
+
\ No newline at end of file
diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt
index 1d94a477c4..960a536e4e 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/CallMessage.kt
@@ -15,8 +15,8 @@ class CallMessage(): ControlMessage() {
override val ttl: Long = 300000L // 30s
- override fun isValid(): Boolean = super.isValid() && type != null
- && (!sdps.isNullOrEmpty() || type == SignalServiceProtos.CallMessage.Type.END_CALL)
+ override fun isValid(): Boolean = super.isValid() && type != null && callId != null
+ && (!sdps.isNullOrEmpty() || type in listOf(SignalServiceProtos.CallMessage.Type.END_CALL,SignalServiceProtos.CallMessage.Type.PRE_OFFER))
constructor(type: SignalServiceProtos.CallMessage.Type,
sdps: List,
@@ -40,6 +40,13 @@ class CallMessage(): ControlMessage() {
callId
)
+ fun preOffer(callId: UUID) = CallMessage(SignalServiceProtos.CallMessage.Type.PRE_OFFER,
+ listOf(),
+ listOf(),
+ listOf(),
+ callId
+ )
+
fun offer(sdp: String, callId: UUID) = CallMessage(SignalServiceProtos.CallMessage.Type.OFFER,
listOf(sdp),
listOf(),
diff --git a/libsession/src/main/res/values/dimens.xml b/libsession/src/main/res/values/dimens.xml
index 8ca4c0aa43..3aed51ebfe 100644
--- a/libsession/src/main/res/values/dimens.xml
+++ b/libsession/src/main/res/values/dimens.xml
@@ -20,6 +20,7 @@
36dp
46dp
76dp
+ 128dp
14dp
1dp
60dp