feat: hooking up calls and fixing broken dependencies and compile errors

This commit is contained in:
jubb
2021-11-08 17:32:25 +11:00
parent 3755315b4c
commit a0e604dbaf
16 changed files with 141 additions and 134 deletions

View File

@@ -37,10 +37,12 @@ dependencies {
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.exifinterface:exifinterface:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-process:2.3.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.activity:activity-ktx:1.2.2'
implementation 'androidx.fragment:fragment-ktx:1.3.2'
implementation "androidx.core:core-ktx:1.3.2"
@@ -153,7 +155,7 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.4'
}
def canonicalVersionCode = 231
def canonicalVersionCode = 232
def canonicalVersionName = "1.11.12"
def postFixSize = 10

View File

@@ -54,7 +54,6 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities;
import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.database.Storage;
import org.thoughtcrime.securesms.dependencies.CallComponent;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.dependencies.DatabaseModule;
import org.thoughtcrime.securesms.groups.OpenGroupManager;
@@ -82,6 +81,7 @@ import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
import org.thoughtcrime.securesms.util.Broadcaster;
import org.thoughtcrime.securesms.util.UiModeUtilities;
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions;
import org.webrtc.voiceengine.WebRtcAudioManager;
@@ -134,6 +134,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
@Inject Storage storage;
@Inject MessageDataProvider messageDataProvider;
@Inject JobDatabase jobDatabase;
CallMessageProcessor callMessageProcessor;
private volatile boolean isAppVisible;
@@ -160,6 +161,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
public void onCreate() {
DatabaseModule.init(this);
super.onCreate();
callMessageProcessor = new CallMessageProcessor(this, ProcessLifecycleOwner.get().getLifecycle());
Log.i(TAG, "onCreate()");
startKovenant();
initializeSecurityProvider();

View File

@@ -8,28 +8,17 @@ import android.content.IntentFilter
import android.media.AudioManager
import android.os.Bundle
import android.view.MenuItem
import android.view.Window
import android.view.WindowManager
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.activity_webrtc_tests.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.WebRtcUtils
import org.session.libsession.utilities.Address
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.webrtc.CallViewModel
import org.webrtc.*
import org.webrtc.IceCandidate
import java.util.*
@AndroidEntryPoint
@@ -67,7 +56,6 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
super.onCreate(savedInstanceState, ready)
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(R.layout.activity_webrtc_tests)
volumeControlStream = AudioManager.STREAM_VOICE_CALL
@@ -81,16 +69,16 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
.execute()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel
}
// repeat on start or something
}
registerReceiver(object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
finish()
}
}, IntentFilter(ACTION_END))
},IntentFilter(ACTION_END))
}
private fun initializeResources() {

View File

@@ -24,7 +24,6 @@ import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
@@ -457,7 +456,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
private fun setUpLinkPreviewObserver() {
val linkPreviewViewModel = ViewModelProviders.of(this, LinkPreviewViewModel.Factory(LinkPreviewRepository()))[LinkPreviewViewModel::class.java]
val linkPreviewViewModel = ViewModelProvider(this, LinkPreviewViewModel.Factory(LinkPreviewRepository()))[LinkPreviewViewModel::class.java]
this.linkPreviewViewModel = linkPreviewViewModel
if (!TextSecurePreferences.isLinkPreviewsEnabled(this)) {
linkPreviewViewModel.onUserCancel(); return

View File

@@ -43,10 +43,10 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity
import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey
import org.thoughtcrime.securesms.service.WebRtcCallService
import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.getColorWithID
import java.io.IOException
import java.util.*
object ConversationMenuHelper {
@@ -183,16 +183,14 @@ object ConversationMenuHelper {
}
private fun call(context: Context, thread: Recipient) {
AlertDialog.Builder(context)
.setTitle("Call")
.setMessage("Use relay?")
.setPositiveButton("Use Relay") { d, w ->
TODO()
}
.setNeutralButton("P2P only") { d, w ->
TODO()
}
.show()
val service = WebRtcCallService.createCall(context, thread)
context.startService(service)
val activity = Intent(context, WebRtcCallActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
context.startActivity(activity)
}
@SuppressLint("StaticFieldLeak")

View File

@@ -148,49 +148,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
}
this.broadcastReceiver = broadcastReceiver
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged"))
lifecycleScope.launchWhenCreated {
// web rtc channel handling
for (message in WebRtcUtils.SIGNAL_QUEUE) {
// TODO: check errors here in the
val sender = Address.fromSerialized(message.sender!!)
val callId = message.callId!!
synchronized(WebRtcUtils.callCache) {
val set = WebRtcUtils.callCache[callId] ?: mutableSetOf()
set += message
WebRtcUtils.callCache[callId] = set
}
when (message.type) {
SignalServiceProtos.CallMessage.Type.OFFER -> {
// show bottom sheet
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
CallBottomSheet().apply {
arguments = bundleOf(
CallBottomSheet.ARGUMENT_ADDRESS to sender,
CallBottomSheet.ARGUMENT_SDP to message.sdps.toTypedArray(),
CallBottomSheet.ARGUMENT_TYPE to message.type!!.number,
CallBottomSheet.ARGUMENT_CALL_ID to callId.toString()
)
show(this@HomeActivity.supportFragmentManager,"call-sheet")
}
}
}
SignalServiceProtos.CallMessage.Type.END_CALL -> {
// dismiss the call sheet
supportFragmentManager.findFragmentByTag("call-sheet")?.let { callSheet ->
if (callSheet is BottomSheetDialogFragment) {
callSheet.dismiss()
}
}
// clear the callCache for this sender
synchronized(WebRtcUtils.callCache) {
WebRtcUtils.callCache[callId] = mutableSetOf()
}
sendBroadcast(Intent(WebRtcCallActivity.ACTION_END))
}
else -> { /* do nothing */ }
}
}
}
lifecycleScope.launchWhenStarted {
launch(Dispatchers.IO) {

View File

@@ -86,6 +86,17 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_ANSWER_CALL)
fun createCall(context: Context, recipient: Recipient) = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_OUTGOING_CALL)
.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address)
fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_INCOMING_CALL)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
fun denyCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL)
fun hangupIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_LOCAL_HANGUP)
@@ -122,15 +133,14 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
@Synchronized
private fun terminate() {
stopForeground(true)
sendBroadcast(Intent(WebRtcCallActivity.ACTION_END))
callManager.stop()
stopForeground(true)
}
private fun isBusy() = callManager.isBusy(this)
private fun initializeVideo() {
callManager.initializeVideo(this)
}
private fun isIdle() = callManager.isIdle()
override fun onBind(intent: Intent?): IBinder? = null
@@ -142,7 +152,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
action == ACTION_INCOMING_CALL && isBusy() -> handleBusyCall(intent)
action == ACTION_REMOTE_BUSY -> handleBusyMessage(intent)
action == ACTION_INCOMING_CALL -> handleIncomingCall(intent)
action == ACTION_OUTGOING_CALL -> handleOutgoingCall(intent)
action == ACTION_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent)
action == ACTION_ANSWER_CALL -> handleAnswerCall(intent)
action == ACTION_DENY_CALL -> handleDenyCall(intent)
action == ACTION_LOCAL_HANGUP -> handleLocalHangup(intent)
@@ -288,12 +298,11 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
callManager.initializeVideo(this)
callManager.postViewModelState(CallViewModel.State.CALL_OUTGOING)
// update phone state IN_CALL
lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL)
callManager.initializeAudioForCall()
callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING)
// bluetoothStateManager.setWantsConnection(true)
setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient)
// DatabaseComponent.get(this).insertOutgoingCall(callManager.recipient!!.address)
// TODO: DatabaseComponent.get(this).insertOutgoingCall(callManager.recipient!!.address)
timeoutExecutor.schedule(TimeoutRunnable(callId, this), 2, TimeUnit.MINUTES)
val expectedState = callManager.currentConnectionState

View File

@@ -54,17 +54,17 @@ class CallBottomSheet: BottomSheetDialogFragment() {
nameTextView.text = recipient.name ?: address.serialize()
acceptButton.setOnClickListener {
val intent = Intent(requireContext(), WebRtcCallActivity::class.java)
val bundle = bundleOf(
WebRtcCallActivity.EXTRA_ADDRESS to address,
WebRtcCallActivity.EXTRA_CALL_ID to callId
)
intent.action = WebRtcCallActivity.ACTION_ANSWER
bundle.putStringArray(WebRtcCallActivity.EXTRA_SDP, sdp)
intent.putExtras(bundle)
startActivity(intent)
dismiss()
// val intent = Intent(requireContext(), WebRtcCallActivity::class.java)
// val bundle = bundleOf(
// WebRtcCallActivity.EXTRA_ADDRESS to address,
// WebRtcCallActivity.EXTRA_CALL_ID to callId
// )
// intent.action = WebRtcCallActivity.ACTION_ANSWER
// bundle.putStringArray(WebRtcCallActivity.EXTRA_SDP, sdp)
//
// intent.putExtras(bundle)
// startActivity(intent)
// dismiss()
}
declineButton.setOnClickListener {

View File

@@ -147,6 +147,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
fun isBusy(context: Context) = currentConnectionState != CallState.STATE_IDLE
|| context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE
fun isIdle() = currentConnectionState == CallState.STATE_IDLE
fun initializeVideo(context: Context) {
Util.runOnMainSync {
val base = EglBase.create()

View File

@@ -0,0 +1,45 @@
package org.thoughtcrime.securesms.webrtc
import android.content.Context
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.coroutineScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.utilities.WebRtcUtils
import org.session.libsession.utilities.Address
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.OFFER
import org.thoughtcrime.securesms.service.WebRtcCallService
import javax.inject.Inject
class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) {
init {
lifecycle.coroutineScope.launch {
while (isActive) {
val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive()
when {
// TODO: handle messages as they come in
nextMessage.type == OFFER -> incomingCall(nextMessage)
}
}
}
}
private fun incomingCall(callMessage: CallMessage) {
val recipientAddress = callMessage.recipient ?: return
val callId = callMessage.callId ?: return
val sdp = callMessage.sdps.firstOrNull() ?: return
val incomingIntent = WebRtcCallService.incomingCall(
context = context,
address = Address.fromSerialized(recipientAddress),
sdp = sdp,
callId = callId
)
context.startService(incomingIntent)
}
}

View File

@@ -165,7 +165,7 @@ class PeerConnectionWrapper(context: Context,
fun createOffer(mediaConstraints: MediaConstraints): SessionDescription {
val future = SettableFuture<SessionDescription>()
peerConnection.createAnswer(object:SdpObserver {
peerConnection.createOffer(object:SdpObserver {
override fun onCreateSuccess(sdp: SessionDescription?) {
future.set(sdp)
}

View File

@@ -37,9 +37,9 @@ class SignalAudioManager(private val context: Context,
private val androidAudioManager: AudioManagerCompat) {
private var commandAndControlThread: HandlerThread? = HandlerThread("call-audio").apply { start() }
private var handler = SignalAudioHandler(commandAndControlThread!!.looper)
private var handler: SignalAudioHandler? = null
private val signalBluetoothManager = SignalBluetoothManager(context, this, androidAudioManager, handler)
private var signalBluetoothManager: SignalBluetoothManager? = null
private var state: State = State.UNINITIALIZED
@@ -66,7 +66,7 @@ class SignalAudioManager(private val context: Context,
private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null
fun handleCommand(command: AudioManagerCommand) {
handler.post {
handler?.post {
when (command) {
is AudioManagerCommand.Initialize -> initialize()
is AudioManagerCommand.Shutdown -> shutdown()
@@ -89,27 +89,32 @@ class SignalAudioManager(private val context: Context,
commandAndControlThread = HandlerThread("call-audio").apply { start() }
handler = SignalAudioHandler(commandAndControlThread!!.looper)
savedAudioMode = androidAudioManager.mode
savedIsSpeakerPhoneOn = androidAudioManager.isSpeakerphoneOn
savedIsMicrophoneMute = androidAudioManager.isMicrophoneMute
hasWiredHeadset = androidAudioManager.isWiredHeadsetOn
signalBluetoothManager = SignalBluetoothManager(context, this, androidAudioManager, handler!!)
androidAudioManager.requestCallAudioFocus()
handler!!.post {
setMicrophoneMute(false)
savedAudioMode = androidAudioManager.mode
savedIsSpeakerPhoneOn = androidAudioManager.isSpeakerphoneOn
savedIsMicrophoneMute = androidAudioManager.isMicrophoneMute
hasWiredHeadset = androidAudioManager.isWiredHeadsetOn
audioDevices.clear()
androidAudioManager.requestCallAudioFocus()
signalBluetoothManager.start()
setMicrophoneMute(false)
updateAudioDeviceState()
audioDevices.clear()
wiredHeadsetReceiver = WiredHeadsetReceiver()
context.registerReceiver(wiredHeadsetReceiver, IntentFilter(if (Build.VERSION.SDK_INT >= 21) AudioManager.ACTION_HEADSET_PLUG else Intent.ACTION_HEADSET_PLUG))
signalBluetoothManager!!.start()
state = State.PREINITIALIZED
updateAudioDeviceState()
Log.d(TAG, "Initialized")
wiredHeadsetReceiver = WiredHeadsetReceiver()
context.registerReceiver(wiredHeadsetReceiver, IntentFilter(if (Build.VERSION.SDK_INT >= 21) AudioManager.ACTION_HEADSET_PLUG else Intent.ACTION_HEADSET_PLUG))
state = State.PREINITIALIZED
Log.d(TAG, "Initialized")
}
}
}
@@ -159,7 +164,7 @@ class SignalAudioManager(private val context: Context,
}
wiredHeadsetReceiver = null
signalBluetoothManager.stop()
signalBluetoothManager?.stop()
setSpeakerphoneOn(savedIsSpeakerPhoneOn)
setMicrophoneMute(savedIsMicrophoneMute)
@@ -172,7 +177,7 @@ class SignalAudioManager(private val context: Context,
}
private fun shutdown() {
handler.post {
handler?.post {
stop(false)
if (commandAndControlThread != null) {
Log.i(TAG, "Shutting down command and control")
@@ -183,25 +188,25 @@ class SignalAudioManager(private val context: Context,
}
private fun updateAudioDeviceState() {
handler.assertHandlerThread()
handler!!.assertHandlerThread()
Log.i(
TAG,
"updateAudioDeviceState(): " +
"wired: $hasWiredHeadset " +
"bt: ${signalBluetoothManager.state} " +
"bt: ${signalBluetoothManager!!.state} " +
"available: $audioDevices " +
"selected: $selectedAudioDevice " +
"userSelected: $userSelectedAudioDevice"
)
if (signalBluetoothManager.state.shouldUpdate()) {
signalBluetoothManager.updateDevice()
if (signalBluetoothManager!!.state.shouldUpdate()) {
signalBluetoothManager!!.updateDevice()
}
val newAudioDevices = mutableSetOf(AudioDevice.SPEAKER_PHONE)
if (signalBluetoothManager.state.hasDevice()) {
if (signalBluetoothManager!!.state.hasDevice()) {
newAudioDevices += AudioDevice.BLUETOOTH
}
@@ -217,7 +222,7 @@ class SignalAudioManager(private val context: Context,
var audioDeviceSetUpdated = audioDevices != newAudioDevices
audioDevices = newAudioDevices
if (signalBluetoothManager.state == SignalBluetoothManager.State.UNAVAILABLE && userSelectedAudioDevice == AudioDevice.BLUETOOTH) {
if (signalBluetoothManager!!.state == SignalBluetoothManager.State.UNAVAILABLE && userSelectedAudioDevice == AudioDevice.BLUETOOTH) {
userSelectedAudioDevice = AudioDevice.NONE
}
@@ -230,33 +235,33 @@ class SignalAudioManager(private val context: Context,
userSelectedAudioDevice = AudioDevice.NONE
}
val needBluetoothAudioStart = signalBluetoothManager.state == SignalBluetoothManager.State.AVAILABLE &&
val needBluetoothAudioStart = signalBluetoothManager!!.state == SignalBluetoothManager.State.AVAILABLE &&
(userSelectedAudioDevice == AudioDevice.NONE || userSelectedAudioDevice == AudioDevice.BLUETOOTH || autoSwitchToBluetooth)
val needBluetoothAudioStop = (signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTED || signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTING) &&
val needBluetoothAudioStop = (signalBluetoothManager!!.state == SignalBluetoothManager.State.CONNECTED || signalBluetoothManager!!.state == SignalBluetoothManager.State.CONNECTING) &&
(userSelectedAudioDevice != AudioDevice.NONE && userSelectedAudioDevice != AudioDevice.BLUETOOTH)
if (signalBluetoothManager.state.hasDevice()) {
Log.i(TAG, "Need bluetooth audio: state: ${signalBluetoothManager.state} start: $needBluetoothAudioStart stop: $needBluetoothAudioStop")
if (signalBluetoothManager!!.state.hasDevice()) {
Log.i(TAG, "Need bluetooth audio: state: ${signalBluetoothManager!!.state} start: $needBluetoothAudioStart stop: $needBluetoothAudioStop")
}
if (needBluetoothAudioStop) {
signalBluetoothManager.stopScoAudio()
signalBluetoothManager.updateDevice()
signalBluetoothManager!!.stopScoAudio()
signalBluetoothManager!!.updateDevice()
}
if (!autoSwitchToBluetooth && signalBluetoothManager.state == SignalBluetoothManager.State.UNAVAILABLE) {
if (!autoSwitchToBluetooth && signalBluetoothManager!!.state == SignalBluetoothManager.State.UNAVAILABLE) {
autoSwitchToBluetooth = true
}
if (needBluetoothAudioStart && !needBluetoothAudioStop) {
if (!signalBluetoothManager.startScoAudio()) {
if (!signalBluetoothManager!!.startScoAudio()) {
audioDevices.remove(AudioDevice.BLUETOOTH)
audioDeviceSetUpdated = true
}
}
if (autoSwitchToBluetooth && signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTED) {
if (autoSwitchToBluetooth && signalBluetoothManager!!.state == SignalBluetoothManager.State.CONNECTED) {
userSelectedAudioDevice = AudioDevice.BLUETOOTH
autoSwitchToBluetooth = false
}
@@ -373,7 +378,7 @@ class SignalAudioManager(private val context: Context,
val pluggedIn = intent.getIntExtra("state", 0) == 1
val hasMic = intent.getIntExtra("microphone", 0) == 1
handler.post { onWiredHeadsetChange(pluggedIn, hasMic) }
handler?.post { onWiredHeadsetChange(pluggedIn, hasMic) }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B