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