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 b5e9b78a74..3ae3d30f01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -799,6 +799,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener { wantsToAnswerReceiver?.let { receiver -> LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) } + callManager.shutDownAudioManager() powerButtonReceiver = null wiredHeadsetStateReceiver = null networkChangedReceiver = null 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 07e73bedd2..d96de5eedb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -93,6 +93,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va peerConnectionObservers.remove(listener) } + fun shutDownAudioManager() { + signalAudioManager.shutdown() + } + private val _audioEvents = MutableStateFlow(AudioEnabled(false)) val audioEvents = _audioEvents.asSharedFlow() private val _videoEvents = MutableStateFlow(VideoEnabled(false)) 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 67514c58be..6eb1304430 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 @@ -9,6 +9,7 @@ import android.media.SoundPool import android.os.HandlerThread import network.loki.messenger.R import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.webrtc.AudioManagerCommand import org.thoughtcrime.securesms.webrtc.audio.SignalBluetoothManager.State as BState @@ -32,10 +33,10 @@ class SignalAudioManager(private val context: Context, private val eventListener: EventListener?, private val androidAudioManager: AudioManagerCompat) { - private var commandAndControlThread: HandlerThread? = HandlerThread("call-audio").apply { start() } - private var handler: SignalAudioHandler? = null + private var commandAndControlThread: HandlerThread? = HandlerThread("call-audio", ThreadUtils.PRIORITY_IMPORTANT_BACKGROUND_THREAD).apply { start() } + private var handler: SignalAudioHandler? = SignalAudioHandler(commandAndControlThread!!.looper) - private var signalBluetoothManager: SignalBluetoothManager? = null + private var signalBluetoothManager: SignalBluetoothManager = SignalBluetoothManager(context, this, androidAudioManager, handler!!) private var state: State = State.UNINITIALIZED @@ -62,12 +63,9 @@ class SignalAudioManager(private val context: Context, private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null fun handleCommand(command: AudioManagerCommand) { - if (command == AudioManagerCommand.Initialize) { - initialize() - return - } handler?.post { when (command) { + is AudioManagerCommand.Initialize -> initialize() is AudioManagerCommand.UpdateAudioDeviceState -> updateAudioDeviceState() is AudioManagerCommand.Start -> start() is AudioManagerCommand.Stop -> stop(command.playDisconnect) @@ -84,34 +82,37 @@ class SignalAudioManager(private val context: Context, Log.i(TAG, "Initializing audio manager state: $state") if (state == State.UNINITIALIZED) { - 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(AudioManager.ACTION_HEADSET_PLUG)) - signalBluetoothManager!!.start() + state = State.PREINITIALIZED - updateAudioDeviceState() + Log.d(TAG, "Initialized") + } + } - wiredHeadsetReceiver = WiredHeadsetReceiver() - context.registerReceiver(wiredHeadsetReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG)) - - state = State.PREINITIALIZED - - Log.d(TAG, "Initialized") + fun shutdown() { + handler!!.post { + stop(false) + if (commandAndControlThread != null) { + Log.i(TAG, "Shutting down command and control") + commandAndControlThread?.quitSafely() + commandAndControlThread = null } } } @@ -138,23 +139,11 @@ class SignalAudioManager(private val context: Context, private fun stop(playDisconnect: Boolean) { Log.d(TAG, "Stopping. state: $state") - if (state == State.UNINITIALIZED) { - Log.i(TAG, "Trying to stop AudioManager in incorrect state: $state") - return - } - handler?.post { - incomingRinger.stop() - outgoingRinger.stop() - stop(false) - if (commandAndControlThread != null) { - Log.i(TAG, "Shutting down command and control") - commandAndControlThread?.quitSafely() - commandAndControlThread = null - } - } + incomingRinger.stop() + outgoingRinger.stop() - if (playDisconnect) { + if (playDisconnect && state != State.UNINITIALIZED) { val volume: Float = androidAudioManager.ringVolumeWithMinimum() soundPool.play(disconnectedSoundId, volume, volume, 0, 0, 1.0f) } @@ -170,7 +159,7 @@ class SignalAudioManager(private val context: Context, } wiredHeadsetReceiver = null - signalBluetoothManager?.stop() + signalBluetoothManager.stop() setSpeakerphoneOn(savedIsSpeakerPhoneOn) setMicrophoneMute(savedIsMicrophoneMute) @@ -189,19 +178,19 @@ class SignalAudioManager(private val context: Context, 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 +206,7 @@ class SignalAudioManager(private val context: Context, var audioDeviceSetUpdated = audioDevices != newAudioDevices audioDevices = newAudioDevices - if (signalBluetoothManager!!.state == BState.UNAVAILABLE && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { + if (signalBluetoothManager.state == BState.UNAVAILABLE && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { userSelectedAudioDevice = AudioDevice.NONE } @@ -230,7 +219,7 @@ class SignalAudioManager(private val context: Context, userSelectedAudioDevice = AudioDevice.NONE } - val btState = signalBluetoothManager!!.state + val btState = signalBluetoothManager.state val needBluetoothAudioStart = btState == BState.AVAILABLE && (userSelectedAudioDevice == AudioDevice.NONE || userSelectedAudioDevice == AudioDevice.BLUETOOTH || autoSwitchToBluetooth) @@ -238,27 +227,27 @@ class SignalAudioManager(private val context: Context, (userSelectedAudioDevice != AudioDevice.NONE && userSelectedAudioDevice != AudioDevice.BLUETOOTH) if (btState.hasDevice()) { - Log.i(TAG, "Need bluetooth audio: state: ${signalBluetoothManager!!.state} start: $needBluetoothAudioStart stop: $needBluetoothAudioStop") + 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 == BState.UNAVAILABLE) { + if (!autoSwitchToBluetooth && signalBluetoothManager.state == BState.UNAVAILABLE) { autoSwitchToBluetooth = true } - if (needBluetoothAudioStart && !needBluetoothAudioStop) { - if (!signalBluetoothManager!!.startScoAudio()) { + if (!needBluetoothAudioStop && needBluetoothAudioStart) { + if (!signalBluetoothManager.startScoAudio()) { Log.e(TAG,"Failed to start sco audio") audioDevices.remove(AudioDevice.BLUETOOTH) audioDeviceSetUpdated = true } } - if (autoSwitchToBluetooth && signalBluetoothManager!!.state == BState.CONNECTED) { + if (autoSwitchToBluetooth && signalBluetoothManager.state == BState.CONNECTED) { userSelectedAudioDevice = AudioDevice.BLUETOOTH autoSwitchToBluetooth = false } diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt b/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt index 0700d01f66..ac81564bd1 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt @@ -1,13 +1,12 @@ package org.session.libsignal.utilities -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import java.util.concurrent.LinkedBlockingQueue -import java.util.concurrent.ThreadPoolExecutor -import java.util.concurrent.TimeUnit +import android.os.Process +import java.util.concurrent.* object ThreadUtils { + const val PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE + val executorPool: ExecutorService = Executors.newCachedThreadPool() @JvmStatic