From 5195a24d08a542decadc041ff3d9081c2e636b1c Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Tue, 24 Jan 2023 05:37:56 +0200 Subject: [PATCH] More speakerphone and call related enhancements for API 31+ --- .../com/tutpro/baresip/BaresipService.kt | 3 - .../main/kotlin/com/tutpro/baresip/Call.kt | 6 ++ .../kotlin/com/tutpro/baresip/MainActivity.kt | 91 ++++++++++++++----- .../main/kotlin/com/tutpro/baresip/Utils.kt | 46 ++++++---- 4 files changed, 103 insertions(+), 43 deletions(-) diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index 65cde5b0..1cf7bcd6 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -637,8 +637,6 @@ class BaresipService: Service() { if (call!!.status == "transferring") break stopMediaPlayer() - if (am.mode != AudioManager.MODE_IN_COMMUNICATION) - am.mode = AudioManager.MODE_IN_COMMUNICATION requestAudioFocus(AudioAttributes.CONTENT_TYPE_SPEECH) setCallVolume() proximitySensing(true) @@ -879,7 +877,6 @@ class BaresipService: Service() { call.remove() if (Call.calls().size == 0) { resetCallVolume() - Utils.setSpeakerPhone(am, false) am.mode = AudioManager.MODE_NORMAL am.stopBluetoothSco() abandonAudioFocus() diff --git a/app/src/main/kotlin/com/tutpro/baresip/Call.kt b/app/src/main/kotlin/com/tutpro/baresip/Call.kt index 64fd2efb..33e9297f 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Call.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Call.kt @@ -112,5 +112,11 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str return null } + fun connectedCall(): Call? { + for (c in BaresipService.calls.reversed()) + if (c.status == "connected") return c + return null + } + } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index d906dcc6..2f631b0c 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -10,6 +10,7 @@ import android.content.* import android.content.Intent.ACTION_CALL import android.content.pm.PackageManager import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.media.AudioDeviceInfo import android.media.AudioManager import android.net.Uri import android.os.* @@ -85,11 +86,13 @@ class MainActivity : AppCompatActivity() { private lateinit var restoreRequest: ActivityResultLauncher private lateinit var contactsRequest: ActivityResultLauncher private lateinit var callsRequest: ActivityResultLauncher + private lateinit var comDevChangedListener: AudioManager.OnCommunicationDeviceChangedListener private var callHandler: Handler = Handler(Looper.getMainLooper()) private var callRunnable: Runnable? = null private var downloadsInputUri: Uri? = null private var downloadsOutputUri: Uri? = null + private var audioModeChangedListener: AudioManager.OnModeChangedListener? = null private lateinit var baresipService: Intent @@ -188,6 +191,21 @@ class MainActivity : AppCompatActivity() { addAction(Intent.ACTION_SCREEN_ON) }) + if (Build.VERSION.SDK_INT >= 31) { + comDevChangedListener = AudioManager.OnCommunicationDeviceChangedListener { device -> + if (device != null) { + Log.d(TAG, "Com device changed to type ${device.type} in mode ${am.mode}") + if (speakerIcon != null) { + if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) + speakerIcon!!.setIcon(R.drawable.speaker_on) + else + speakerIcon!!.setIcon(R.drawable.speaker_off) + } + } + } + am.addOnCommunicationDeviceChangedListener(mainExecutor, comDevChangedListener) + } + uaAdapter = UaSpinnerAdapter(applicationContext, BaresipService.uas) aorSpinner.adapter = uaAdapter aorSpinner.setSelection(-1) @@ -340,12 +358,22 @@ class MainActivity : AppCompatActivity() { hangupButton.setOnClickListener { val ua = BaresipService.uas[aorSpinner.selectedItemPosition] - if (callRunnable != null && callHandler.hasCallbacks(callRunnable!!)) { - callHandler.removeCallbacks(callRunnable!!) - callRunnable = null - am.mode = AudioManager.MODE_NORMAL - showCall(ua) - return@setOnClickListener + if (Build.VERSION.SDK_INT < 31) { + if (callRunnable != null && callHandler.hasCallbacks(callRunnable!!)) { + callHandler.removeCallbacks(callRunnable!!) + callRunnable = null + am.mode = AudioManager.MODE_NORMAL + showCall(ua) + return@setOnClickListener + } + } else { + if (audioModeChangedListener != null) { + am.removeOnModeChangedListener(audioModeChangedListener!!) + audioModeChangedListener = null + am.mode = AudioManager.MODE_NORMAL + showCall(ua) + return@setOnClickListener + } } val aor = ua.account.aor val uaCalls = ua.calls() @@ -744,6 +772,8 @@ class MainActivity : AppCompatActivity() { super.onDestroy() Log.d(TAG, "Main onDestroy") this.unregisterReceiver(screenEventReceiver) + if (Build.VERSION.SDK_INT >= 31) + am.removeOnCommunicationDeviceChangedListener(comDevChangedListener) BaresipService.serviceEvent.removeObserver(serviceEventObserver) BaresipService.serviceEvents.clear() BaresipService.activities.clear() @@ -1234,13 +1264,7 @@ class MainActivity : AppCompatActivity() { } R.id.speakerIcon -> { - Utils.toggleSpeakerPhone(am) - Handler(Looper.getMainLooper()).postDelayed({ - if (Utils.isSpeakerPhoneOn(am)) - item.setIcon(R.drawable.speaker_on) - else - item.setIcon(R.drawable.speaker_off) - }, 750) + Utils.toggleSpeakerPhone(mainExecutor, am) } R.id.config -> { @@ -1783,21 +1807,46 @@ class MainActivity : AppCompatActivity() { ) } else { callUri.isFocusable = false - am.mode = AudioManager.MODE_IN_COMMUNICATION uaAdapter.notifyDataSetChanged() callButton.visibility = View.INVISIBLE callButton.isEnabled = false hangupButton.visibility = View.VISIBLE hangupButton.isEnabled = true - callRunnable = Runnable { - if (!call(ua, uri)) { - callButton.visibility = View.VISIBLE - callButton.isEnabled = true - hangupButton.visibility = View.INVISIBLE - hangupButton.isEnabled = false + if (Build.VERSION.SDK_INT < 31) { + am.mode = AudioManager.MODE_IN_COMMUNICATION + callRunnable = Runnable { + if (!call(ua, uri)) { + callButton.visibility = View.VISIBLE + callButton.isEnabled = true + hangupButton.visibility = View.INVISIBLE + hangupButton.isEnabled = false + } } + callHandler.postDelayed(callRunnable!!, 1000) + } else { + audioModeChangedListener = AudioManager.OnModeChangedListener { mode -> + if (mode == AudioManager.MODE_IN_COMMUNICATION) { + Log.d(TAG, "Audio mode changed to MODE_IN_COMMUNICATION using " + + "device ${am.communicationDevice!!.type}") + if (audioModeChangedListener != null) { + am.removeOnModeChangedListener(audioModeChangedListener!!) + audioModeChangedListener = null + } + if (!call(ua, uri)) { + callButton.visibility = View.VISIBLE + callButton.isEnabled = true + hangupButton.visibility = View.INVISIBLE + hangupButton.isEnabled = false + } + } else { + Log.d(TAG, "Audio mode changed to MODE_NORMAL using " + + "device ${am.communicationDevice!!.type}") + } + } + am.addOnModeChangedListener(mainExecutor, audioModeChangedListener!!) + Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") + am.mode = AudioManager.MODE_IN_COMMUNICATION } - callHandler.postDelayed(callRunnable!!, 1000) } } else { val latestPeerUri = NewCallHistory.aorLatestPeerUri(aor) diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index fae09532..eaf4d565 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -41,6 +41,7 @@ import java.net.SocketException import java.security.SecureRandom import java.text.DateFormat import java.util.* +import java.util.concurrent.Executor import java.util.zip.ZipEntry import java.util.zip.ZipFile import java.util.zip.ZipOutputStream @@ -897,9 +898,10 @@ object Utils { am.isSpeakerphoneOn } - fun setSpeakerPhone(am: AudioManager, enabled: Boolean) { - // Currently at API levels 31+, speakerphone cannot be turned on during call - if (enabled) { + private fun setSpeakerPhone(executor: Executor, am: AudioManager, enable: Boolean) { + if (enable == isSpeakerPhoneOn(am)) + return + if (enable) { if (Build.VERSION.SDK_INT >= 31) { Log.d(TAG, "Setting current device from ${am.communicationDevice!!.type} to " + "${AudioDeviceInfo.TYPE_BUILTIN_SPEAKER} in mode ${am.mode}") @@ -910,12 +912,25 @@ object Utils { break } if (speakerDevice != null) { - am.mode = AudioManager.MODE_NORMAL - Handler(Looper.getMainLooper()).postDelayed({ + if (am.mode == AudioManager.MODE_NORMAL) { if (!am.setCommunicationDevice(speakerDevice)) Log.e(TAG, "Could not turn on speaker device") Log.d(TAG, "Type of current device is ${am.communicationDevice!!.type}") - }, 500) + } else { + // Currently at API levels 31+, speakerphone needs to be turned on in normal mode + val normalListener = object : AudioManager.OnModeChangedListener { + override fun onModeChanged(mode: Int) { + if (mode == AudioManager.MODE_NORMAL) { + am.removeOnModeChangedListener(this) + if (!am.setCommunicationDevice(speakerDevice)) + Log.e(TAG, "Could not turn on speaker device") + Log.d(TAG, "Type of current device is ${am.communicationDevice!!.type}") + } + } + } + am.addOnModeChangedListener(executor, normalListener) + am.mode = AudioManager.MODE_NORMAL + } } } else { am.isSpeakerphoneOn = true @@ -925,17 +940,10 @@ object Utils { if (Build.VERSION.SDK_INT >= 31) { Log.d(TAG, "Setting current device from type ${am.communicationDevice!!.type} to " + "${AudioDeviceInfo.TYPE_BUILTIN_EARPIECE} in mode ${am.mode}") - var earDevice: AudioDeviceInfo? = null - for (device in am.availableCommunicationDevices) - if (device.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) { - earDevice = device - break - } - if (earDevice != null) { - if (!am.setCommunicationDevice(earDevice)) - Log.e(TAG, "Could not turn on earpiece device") + am.clearCommunicationDevice() + // Restore communication mode + if (Call.connectedCall() != null && am.mode == AudioManager.MODE_NORMAL) am.mode = AudioManager.MODE_IN_COMMUNICATION - } Log.d(TAG, "Type of current device is ${am.communicationDevice!!.type}") } else { am.isSpeakerphoneOn = false @@ -944,11 +952,11 @@ object Utils { } } - fun toggleSpeakerPhone(am: AudioManager) { + fun toggleSpeakerPhone(executor: Executor, am: AudioManager) { if (Build.VERSION.SDK_INT >= 31) - setSpeakerPhone(am, + setSpeakerPhone(executor, am, am.communicationDevice!!.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) else - setSpeakerPhone(am, !am.isSpeakerphoneOn) + setSpeakerPhone(executor, am, !am.isSpeakerphoneOn) } }