From bf74483b9f7a59975f3210d6d2f0d3b3f724371a Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 15 Nov 2021 18:02:55 +1100 Subject: [PATCH] feat: add fixes to bluetooth and begin the network renegotiation --- app/build.gradle | 1 + .../securesms/calls/WebRtcCallActivity.kt | 46 ++++++------ .../securesms/service/WebRtcCallService.kt | 71 +++++++++++++++---- .../securesms/webrtc/CallManager.kt | 69 ++++++++++++------ .../securesms/webrtc/CallViewModel.kt | 32 ++++++--- .../securesms/webrtc/NetworkChangeReceiver.kt | 19 +++++ .../securesms/webrtc/PeerConnectionWrapper.kt | 30 ++++++++ .../webrtc/audio/SignalAudioManager.kt | 7 +- .../webrtc/audio/SignalBluetoothManager.kt | 31 +++----- .../drawable/ic_baseline_volume_mute_24.xml | 10 +++ .../res/drawable/ic_baseline_volume_up_24.xml | 10 +++ app/src/main/res/layout/activity_webrtc.xml | 20 +++--- .../main/res/values-notnight-v21/colors.xml | 4 ++ app/src/main/res/values/colors.xml | 4 ++ 14 files changed, 253 insertions(+), 101 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/NetworkChangeReceiver.kt create mode 100644 app/src/main/res/drawable/ic_baseline_volume_mute_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_volume_up_24.xml diff --git a/app/build.gradle b/app/build.gradle index b51dc4e9a5..804a5ce402 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -234,6 +234,7 @@ android { buildTypes { release { + debuggable true minifyEnabled false } debug { 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 776d748003..40713e7305 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -5,6 +5,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.graphics.BlendMode +import android.graphics.PorterDuff import android.graphics.drawable.ColorDrawable import android.media.AudioManager import android.os.Bundle @@ -31,8 +33,11 @@ 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.AudioManagerCommand import org.thoughtcrime.securesms.webrtc.CallViewModel import org.thoughtcrime.securesms.webrtc.CallViewModel.State.* +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.* import org.webrtc.IceCandidate import java.util.* @@ -40,10 +45,6 @@ import java.util.* class WebRtcCallActivity: PassphraseRequiredActionBarActivity() { companion object { - 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_END = "end-call" @@ -51,13 +52,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 - private var uiJob: Job? = null override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -97,6 +92,11 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() { ContextCompat.startForegroundService(this,answerIntent) } + speakerPhoneButton.setOnClickListener { + val command = AudioManagerCommand.SetUserDevice( if (viewModel.isSpeaker) EARPIECE else SPEAKER_PHONE) + WebRtcCallService.sendAudioManagerCommand(this, command) + } + acceptCallButton.setOnClickListener { val answerIntent = WebRtcCallService.acceptCallIntent(this) ContextCompat.startForegroundService(this,answerIntent) @@ -146,17 +146,24 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() { uiJob = lifecycleScope.launch { + launch { + viewModel.audioDeviceState.collect { state -> + val speakerEnabled = state.selectedDevice == SPEAKER_PHONE + speakerPhoneButton.setImageResource( + if (speakerEnabled) R.drawable.ic_baseline_volume_up_24 + else R.drawable.ic_baseline_volume_mute_24 + ) + } + } + launch { viewModel.callState.collect { state -> when (state) { CALL_RINGING -> { - } CALL_OUTGOING -> { - } CALL_CONNECTED -> { - } } controlGroup.isVisible = state in listOf(CALL_CONNECTED, CALL_OUTGOING, CALL_INCOMING) @@ -198,10 +205,9 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() { } local_renderer.isVisible = isEnabled enableCameraButton.setImageResource( - if (isEnabled) R.drawable.ic_outline_videocam_off_24 - else R.drawable.ic_outline_videocam_24 + if (isEnabled) R.drawable.ic_baseline_videocam_off_24 + else R.drawable.ic_baseline_videocam_24 ) - enableCameraButton.styleEnabled(isEnabled) } } @@ -218,14 +224,6 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() { } } - fun View.styleEnabled(isEnabled: Boolean) { - if (isEnabled) { - setBackgroundResource(R.drawable.call_controls_selected) - } else { - setBackgroundResource(R.drawable.call_controls_unselected) - } - } - private fun getUserDisplayName(publicKey: String): String { val contact = DatabaseComponent.get(this).sessionContactDatabase().getContactWithSessionID(publicKey) return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey 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 c32c4cb354..00a8aefa57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.media.AudioManager +import android.net.ConnectivityManager import android.os.Build import android.os.IBinder import android.os.ResultReceiver @@ -90,7 +91,11 @@ class WebRtcCallService: Service(), PeerConnection.Observer { fun flipCamera(context: Context) = Intent(context, WebRtcCallService::class.java) .setAction(ACTION_FLIP_CAMERA) - fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_ANSWER_CALL) + fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_ANSWER_CALL) + + fun speakerIntent(context: Context, enable: Boolean) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_UPDATE_AUDIO) fun createCall(context: Context, recipient: Recipient) = Intent(context, WebRtcCallService::class.java) .setAction(ACTION_OUTGOING_CALL) @@ -132,7 +137,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { val intent = Intent(context, WebRtcCallService::class.java) .setAction(ACTION_UPDATE_AUDIO) .putExtra(EXTRA_AUDIO_COMMAND, command) - ContextCompat.startForegroundService(context, intent) + context.startService(intent) } @JvmStatic @@ -156,6 +161,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { startService(hangupIntent(this)) } + private var networkChangedReceiver: NetworkChangeReceiver? = null private var callReceiver: IncomingPstnCallReceiver? = null private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null private var uncaughtExceptionHandlerManager: UncaughtExceptionHandlerManager? = null @@ -168,6 +174,11 @@ class WebRtcCallService: Service(), PeerConnection.Observer { stopForeground(true) } + private fun isSameCall(intent: Intent): Boolean { + val expectedCallId = getCallId(intent) + return callManager.callId == expectedCallId + } + private fun isBusy() = callManager.isBusy(this) private fun isIdle() = callManager.isIdle() @@ -180,6 +191,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { val action = intent.action Log.d("Loki", "Handling ${intent.action}") when { + action == ACTION_INCOMING_RING && isSameCall(intent) -> handleNewOffer(intent) action == ACTION_INCOMING_RING && isBusy() -> handleBusyCall(intent) action == ACTION_REMOTE_BUSY -> handleBusyMessage(intent) action == ACTION_INCOMING_RING && isIdle() -> handleIncomingRing(intent) @@ -199,6 +211,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { action == ACTION_ICE_CONNECTED -> handleIceConnected(intent) action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent) action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent) + action == ACTION_UPDATE_AUDIO -> handleUpdateAudio(intent) } } return START_NOT_STICKY @@ -212,6 +225,13 @@ class WebRtcCallService: Service(), PeerConnection.Observer { getSystemService(TelephonyManager::class.java) .listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE) registerUncaughtExceptionHandler() + networkChangedReceiver = NetworkChangeReceiver { available -> + networkChange(available) + } + registerReceiver(networkChangedReceiver, IntentFilter().apply { + addAction("android.net.conn.CONNECTIVITY_CHANGE") + addAction("android.net.wifi.WIFI_STATE_CHANGED") + }) } private fun registerUncaughtExceptionHandler() { @@ -232,25 +252,24 @@ class WebRtcCallService: Service(), PeerConnection.Observer { private fun handleBusyCall(intent: Intent) { val recipient = getRemoteRecipient(intent) + val callId = getCallId(intent) val callState = callManager.currentConnectionState - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - when (callState) { - STATE_DIALING, - STATE_REMOTE_RINGING -> setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient) - STATE_IDLE -> setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient) - STATE_ANSWERING -> setCallInProgressNotification(TYPE_INCOMING_CONNECTING, callManager.recipient) - STATE_LOCAL_RINGING -> setCallInProgressNotification(TYPE_INCOMING_RINGING, callManager.recipient) - STATE_CONNECTED -> setCallInProgressNotification(TYPE_ESTABLISHED, callManager.recipient) - } - } + callManager.handleBusyCall(callId, recipient) + insertMissedCall(recipient, false) if (callState == STATE_IDLE) { stopForeground(true) } + } - // TODO: send hangup via messageSender - insertMissedCall(getRemoteRecipient(intent), false) + private fun handleUpdateAudio(intent: Intent) { + val audioCommand = intent.getParcelableExtra(EXTRA_AUDIO_COMMAND)!! + if (callManager.currentConnectionState !in arrayOf(STATE_DIALING, STATE_CONNECTED, STATE_LOCAL_RINGING)) { + Log.w(TAG, "handling audio command not in call") + return + } + callManager.handleAudioCommand(audioCommand) } private fun handleBusyMessage(intent: Intent) { @@ -270,6 +289,19 @@ class WebRtcCallService: Service(), PeerConnection.Observer { }, WebRtcCallActivity.BUSY_SIGNAL_DELAY_FINISH) } + private fun handleNewOffer(intent: Intent) { + if (callManager.currentConnectionState !in arrayOf(STATE_CONNECTED, STATE_DIALING, STATE_ANSWERING)) { + Log.w(TAG,"trying to handle new offer from non-connecting state") + return + } + + val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return + val callId = getCallId(intent) + val recipient = getRemoteRecipient(intent) + callManager.clearPendingIceUpdates() + callManager.onNewOffer(offer, callId, recipient) + } + private fun handleIncomingRing(intent: Intent) { if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Incoming ring on non-idle") @@ -344,6 +376,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { intent.putExtra(EXTRA_REMOTE_DESCRIPTION, pending) intent.putExtra(EXTRA_TIMESTAMP, timestamp) + callManager.silenceIncomingRinger() callManager.postConnectionEvent(STATE_ANSWERING) callManager.postViewModelState(CallViewModel.State.CALL_INCOMING) @@ -537,6 +570,10 @@ class WebRtcCallService: Service(), PeerConnection.Observer { callReceiver?.let { receiver -> unregisterReceiver(receiver) } + networkChangedReceiver?.let { receiver -> + unregisterReceiver(receiver) + } + networkChangedReceiver = null callReceiver = null uncaughtExceptionHandlerManager?.unregister() callManager.onDestroy() @@ -546,6 +583,12 @@ class WebRtcCallService: Service(), PeerConnection.Observer { // unregister power button } + fun networkChange(networkAvailable: Boolean) { + if (networkAvailable && callManager.currentConnectionState in arrayOf(STATE_CONNECTED, STATE_ANSWERING, STATE_DIALING)) { + callManager.networkReestablished() + } + } + private class TimeoutRunnable(private val callId: UUID, private val context: Context): Runnable { override fun run() { val intent = Intent(context, WebRtcCallService::class.java) 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 bec87dd648..ca98007566 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -2,8 +2,6 @@ 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 @@ -13,7 +11,6 @@ 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 @@ -47,6 +44,7 @@ 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 AudioDeviceUpdate(val selectedDevice: AudioDevice, val audioDevices: Set): StateEvent() data class RecipientUpdate(val recipient: Recipient?): StateEvent() { companion object { val UNKNOWN = RecipientUpdate(recipient = null) @@ -100,7 +98,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne private val _recipientEvents = MutableStateFlow(RecipientUpdate.UNKNOWN) val recipientEvents = _recipientEvents.asSharedFlow() private var localCameraState: CameraState = CameraState.UNKNOWN - private var bluetoothAvailable = false + + private val _audioDeviceEvents = MutableStateFlow(AudioDeviceUpdate(AudioDevice.NONE, setOf())) + val audioDeviceEvents = _audioDeviceEvents.asSharedFlow() + val currentConnectionState get() = (_connectionEvents.value as CallStateUpdate).state @@ -117,6 +118,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne field = value _recipientEvents.value = StateEvent.RecipientUpdate(value) } + var isReestablishing: Boolean = false fun getCurrentCallState(): Pair = currentConnectionState to callId @@ -148,6 +150,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne signalAudioManager.handleCommand(AudioManagerCommand.StartOutgoingRinger(ringerType)) } + fun silenceIncomingRinger() { + signalAudioManager.handleCommand(AudioManagerCommand.SilenceIncomingRinger) + } + fun postConnectionEvent(newState: CallState) { _connectionEvents.value = CallStateUpdate(newState) } @@ -160,18 +166,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne } - fun networkChange(networkAvailable: Boolean) { - - } - - fun acceptCall() { - - } - - fun declineCall() { - - } - fun isBusy(context: Context) = currentConnectionState != CallState.STATE_IDLE || context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE @@ -320,7 +314,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne } override fun onAudioDeviceChanged(activeDevice: AudioDevice, devices: Set) { - signalAudioManager.handleCommand(AudioManagerCommand()) + _audioDeviceEvents.value = AudioDeviceUpdate(activeDevice, devices) } private fun CallState.withState(vararg expected: CallState, transition: () -> Unit) { @@ -356,6 +350,19 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne localCameraState = newCameraState } + fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise { + if (callId != this.callId) return Promise.ofFail(NullPointerException("No callId")) + if (recipient != this.recipient) return Promise.ofFail(NullPointerException("No recipient")) + + val connection = peerConnection ?: return Promise.ofFail(NullPointerException("No peer connection")) + + connection.setNewOffer(SessionDescription(SessionDescription.Type.OFFER, offer)) + val answer = connection.createAnswer(MediaConstraints().apply { + mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true")) + }) + return MessageSender.sendNonDurably(CallMessage.answer(answer.description, callId), recipient.address) + } + fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long) { if (currentConnectionState != CallState.STATE_IDLE) return @@ -366,10 +373,6 @@ 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")) @@ -586,6 +589,30 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne } } + fun handleBusyCall(callId: UUID, recipient: Recipient): Promise { + return MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address) + } + + fun handleAudioCommand(audioCommand: AudioManagerCommand) { + signalAudioManager.handleCommand(audioCommand) + } + + fun networkReestablished() { + val connection = peerConnection ?: return + val callId = callId ?: return + val recipient = recipient ?: return + + if (isReestablishing) return + + val offer = connection.createOffer(MediaConstraints().apply { + mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true")) + }) + + isReestablishing = true + + MessageSender.sendNonDurably(CallMessage.offer(offer.description, callId), recipient.address) + } + @Serializable data class VideoEnabledMessage(val video: Boolean) 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 a8519f22e5..1fd3584c73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager import org.webrtc.SurfaceViewRenderer import javax.inject.Inject @@ -22,7 +23,12 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V private var _videoEnabled: Boolean = false val videoEnabled: Boolean - get() = _videoEnabled + get() = _videoEnabled + + private var _isSpeaker: Boolean = false + val isSpeaker: Boolean + get() = _isSpeaker + enum class State { CALL_PENDING, @@ -40,17 +46,27 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V UNTRUSTED_IDENTITY, } + val audioDeviceState + get() = callManager.audioDeviceEvents + .onEach { + _isSpeaker = it.selectedDevice == SignalAudioManager.AudioDevice.SPEAKER_PHONE + } + val localAudioEnabledState - get() = callManager.audioEvents.map { it.isEnabled } + get() = callManager.audioEvents.map { it.isEnabled } + val localVideoEnabledState - get() = callManager.videoEvents - .map { it.isEnabled } - .onEach { _videoEnabled = it } + get() = callManager.videoEvents + .map { it.isEnabled } + .onEach { _videoEnabled = it } + val remoteVideoEnabledState - get() = callManager.remoteVideoEvents.map { it.isEnabled } + get() = callManager.remoteVideoEvents.map { it.isEnabled } + val callState - get() = callManager.callStateEvents + get() = callManager.callStateEvents + val recipient - get() = callManager.recipientEvents + get() = callManager.recipientEvents } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/NetworkChangeReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/NetworkChangeReceiver.kt new file mode 100644 index 0000000000..5ad5b480a6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/NetworkChangeReceiver.kt @@ -0,0 +1,19 @@ +package org.thoughtcrime.securesms.webrtc + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager + +class NetworkChangeReceiver(private val onNetworkChangedCallback: (Boolean)->Unit): BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + onNetworkChangedCallback(context.isConnected()) + } + + fun Context.isConnected() : Boolean { + val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + return cm.activeNetworkInfo?.isConnected ?: false + } + +} \ 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 4f0845292c..c4cbbd3f84 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt @@ -107,6 +107,36 @@ class PeerConnectionWrapper(context: Context, peerConnection.dispose() } + fun setNewOffer(description: SessionDescription) { + val future = SettableFuture() + + peerConnection.setRemoteDescription(object: SdpObserver { + override fun onCreateSuccess(p0: SessionDescription?) { + throw AssertionError() + } + + override fun onCreateFailure(p0: String?) { + throw AssertionError() + } + + override fun onSetSuccess() { + future.set(true) + } + + override fun onSetFailure(error: String?) { + future.setException(PeerConnectionException(error)) + } + }, description) + + try { + future.get() + } catch (e: InterruptedException) { + throw AssertionError(e) + } catch (e: ExecutionException) { + throw PeerConnectionException(e) + } + } + fun setRemoteDescription(description: SessionDescription) { val future = SettableFuture() 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 3aa74e22b6..559db796ff 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 @@ -15,6 +15,7 @@ import org.session.libsession.utilities.ServiceUtil import org.session.libsession.utilities.concurrent.SignalExecutors import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.webrtc.AudioManagerCommand +import org.thoughtcrime.securesms.webrtc.audio.SignalBluetoothManager.State private val TAG = Log.tag(SignalAudioManager::class.java) @@ -60,8 +61,8 @@ class SignalAudioManager(private val context: Context, private val connectedSoundId = soundPool.load(context, R.raw.webrtc_completed, 1) private val disconnectedSoundId = soundPool.load(context, R.raw.webrtc_disconnected, 1) - val incomingRinger = IncomingRinger(context) - val outgoingRinger = OutgoingRinger(context) + private val incomingRinger = IncomingRinger(context) + private val outgoingRinger = OutgoingRinger(context) private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null @@ -181,6 +182,8 @@ class SignalAudioManager(private val context: Context, private fun shutdown() { handler?.post { + incomingRinger.stop() + outgoingRinger.stop() stop(false) if (commandAndControlThread != null) { Log.i(TAG, "Shutting down command and control") 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 d19177e8f4..d90814977b 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 @@ -34,7 +34,6 @@ class SignalBluetoothManager( private set private var bluetoothAdapter: BluetoothAdapter? = null - private var bluetoothDevice: BluetoothDevice? = null private var bluetoothHeadset: BluetoothHeadset? = null private var scoConnectionAttempts = 0 @@ -54,7 +53,6 @@ class SignalBluetoothManager( } bluetoothHeadset = null - bluetoothDevice = null scoConnectionAttempts = 0 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() @@ -112,13 +110,10 @@ class SignalBluetoothManager( cancelTimer() - if (bluetoothHeadset != null) { - bluetoothAdapter?.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset) - bluetoothHeadset = null - } + bluetoothAdapter?.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset) + bluetoothHeadset = null bluetoothAdapter = null - bluetoothDevice = null state = State.UNINITIALIZED } @@ -170,15 +165,12 @@ class SignalBluetoothManager( return } - val devices: List? = bluetoothHeadset?.connectedDevices - if (devices == null || devices.isEmpty()) { - bluetoothDevice = null + if (bluetoothAdapter!!.getProfileConnectionState(BluetoothProfile.HEADSET) !in arrayOf(BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED)) { state = State.UNAVAILABLE Log.i(TAG, "No connected bluetooth headset") } else { - bluetoothDevice = devices[0] state = State.AVAILABLE - Log.i(TAG, "Connected bluetooth headset. headsetState: ${bluetoothHeadset?.getConnectionState(bluetoothDevice)?.toStateString()} scoAudio: ${bluetoothHeadset?.isAudioConnected(bluetoothDevice)}") + Log.i(TAG, "Connected bluetooth headset.") } } @@ -202,16 +194,12 @@ class SignalBluetoothManager( } var scoConnected = false - val devices: List? = bluetoothHeadset?.connectedDevices - if (devices != null && devices.isNotEmpty()) { - bluetoothDevice = devices[0] - if (bluetoothHeadset?.isAudioConnected(bluetoothDevice) == true) { - Log.d(TAG, "Connected with $bluetoothDevice") - scoConnected = true - } else { - Log.d(TAG, "Not connected with $bluetoothDevice") - } + if (audioManager.isBluetoothScoOn()) { + Log.d(TAG, "Connected with device") + scoConnected = true + } else { + Log.d(TAG, "Not connected with device") } if (scoConnected) { @@ -234,7 +222,6 @@ class SignalBluetoothManager( private fun onServiceDisconnected() { stopScoAudio() bluetoothHeadset = null - bluetoothDevice = null state = State.UNAVAILABLE updateAudioDeviceState() } diff --git a/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml b/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml new file mode 100644 index 0000000000..fc41db8b9f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_volume_mute_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_volume_up_24.xml b/app/src/main/res/drawable/ic_baseline_volume_up_24.xml new file mode 100644 index 0000000000..0db34695f1 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_volume_up_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_webrtc.xml b/app/src/main/res/layout/activity_webrtc.xml index 418c562d0c..e41d35b3b3 100644 --- a/app/src/main/res/layout/activity_webrtc.xml +++ b/app/src/main/res/layout/activity_webrtc.xml @@ -85,8 +85,8 @@ android:background="@drawable/circle_tintable" android:src="@drawable/ic_baseline_flip_camera_android_24" android:padding="@dimen/medium_spacing" - app:tint="@color/unimportant" - android:backgroundTint="@color/unimportant_button_background" + app:tint="@color/call_action_foreground" + android:backgroundTint="@color/call_action_button" android:layout_width="@dimen/large_button_height" android:layout_height="@dimen/large_button_height" app:layout_constraintBottom_toTopOf="@+id/endCallButton" @@ -99,10 +99,10 @@ @@ -128,10 +128,10 @@ #99000000 #E0E0E0 + #FFFFFF + #171717 + #D8D8D8 + #ffffff #fcfcfc #fcfcfc diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index feb1432278..50ce26ee14 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -38,6 +38,10 @@ #99FFFFFF #303030 + #353535 + #D8D8D8 + #171717 + #5ff8b0 #26cdb9