feat: add fixes to bluetooth and begin the network renegotiation
This commit is contained in:
parent
b6c53b4964
commit
bf74483b9f
|
@ -234,6 +234,7 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
}
|
||||
debug {
|
||||
|
|
|
@ -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<CallViewModel>()
|
||||
|
||||
private val candidates: MutableList<IceCandidate> = 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
|
||||
|
|
|
@ -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<AudioManagerCommand>(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)
|
||||
|
|
|
@ -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<AudioDevice>): 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<CallState, UUID?> = 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<AudioDevice>) {
|
||||
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<Unit, Exception> {
|
||||
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<Unit, Exception> {
|
||||
return task {}
|
||||
}
|
||||
|
||||
fun onIncomingCall(context: Context, isAlwaysTurn: Boolean = false): Promise<Unit, Exception> {
|
||||
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<Unit, Exception> {
|
||||
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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -107,6 +107,36 @@ class PeerConnectionWrapper(context: Context,
|
|||
peerConnection.dispose()
|
||||
}
|
||||
|
||||
fun setNewOffer(description: SessionDescription) {
|
||||
val future = SettableFuture<Boolean>()
|
||||
|
||||
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<Boolean>()
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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<BluetoothDevice>? = 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<BluetoothDevice>? = 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()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,9v6h4l5,5V4l-5,5H7z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
|
||||
</vector>
|
|
@ -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 @@
|
|||
<ImageView
|
||||
android:id="@+id/enableCameraButton"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:src="@drawable/ic_baseline_videocam_24"
|
||||
android:src="@drawable/ic_baseline_videocam_off_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"
|
||||
|
@ -117,10 +117,10 @@
|
|||
android:layout_height="@dimen/large_button_height"
|
||||
android:padding="@dimen/medium_spacing"
|
||||
android:src="@drawable/ic_microphone"
|
||||
app:tint="@color/unimportant"
|
||||
app:tint="@color/call_action_foreground"
|
||||
android:backgroundTint="@color/call_action_button"
|
||||
android:layout_marginBottom="@dimen/large_spacing"
|
||||
app:layout_constraintBottom_toTopOf="@+id/endCallButton"
|
||||
android:backgroundTint="@color/unimportant_button_background"
|
||||
android:background="@drawable/circle_tintable"
|
||||
app:layout_constraintEnd_toStartOf="@id/speakerPhoneButton"
|
||||
app:layout_constraintStart_toEndOf="@id/enableCameraButton"/>
|
||||
|
@ -128,10 +128,10 @@
|
|||
<ImageView
|
||||
android:id="@+id/speakerPhoneButton"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:src="@drawable/ic_audio_light"
|
||||
android:src="@drawable/ic_baseline_volume_mute_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"
|
||||
|
|
|
@ -32,6 +32,10 @@
|
|||
<color name="scroll_to_bottom_button_border">#99000000</color>
|
||||
<color name="conversation_unread_count_indicator_background">#E0E0E0</color>
|
||||
|
||||
<color name="call_action_button">#FFFFFF</color>
|
||||
<color name="call_action_foreground">#171717</color>
|
||||
<color name="call_action_foreground_highlighted">#D8D8D8</color>
|
||||
|
||||
<color name="default_background_start">#ffffff</color>
|
||||
<color name="default_background_end">#fcfcfc</color>
|
||||
<color name="action_bar_background">#fcfcfc</color>
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
<color name="scroll_to_bottom_button_border">#99FFFFFF</color>
|
||||
<color name="conversation_unread_count_indicator_background">#303030</color>
|
||||
|
||||
<color name="call_action_button">#353535</color>
|
||||
<color name="call_action_foreground">#D8D8D8</color>
|
||||
<color name="call_action_foreground_highlighted">#171717</color>
|
||||
|
||||
<array name="profile_picture_placeholder_colors">
|
||||
<item>#5ff8b0</item>
|
||||
<item>#26cdb9</item>
|
||||
|
|
Loading…
Reference in New Issue