feat: add fixes to bluetooth and begin the network renegotiation

This commit is contained in:
jubb 2021-11-15 18:02:55 +11:00
parent b6c53b4964
commit bf74483b9f
14 changed files with 253 additions and 101 deletions

View File

@ -234,6 +234,7 @@ android {
buildTypes {
release {
debuggable true
minifyEnabled false
}
debug {

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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>()

View File

@ -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")

View File

@ -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()
}

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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>