Skip to content

Commit 3214bc8

Browse files
committed
Disable speakerphone when headphones are plugged
1 parent 25dd10d commit 3214bc8

File tree

3 files changed

+53
-4
lines changed

3 files changed

+53
-4
lines changed

atox/src/main/kotlin/ui/call/CallFragment.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@
55
package ltd.evilcorp.atox.ui.call
66

77
import android.Manifest
8+
import android.media.AudioDeviceInfo.TYPE_BLE_HEADSET
9+
import android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER
10+
import android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO
11+
import android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES
12+
import android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET
13+
import android.media.AudioManager
14+
import android.os.Build
815
import android.os.Bundle
916
import android.view.View
1017
import android.widget.Toast
1118
import androidx.activity.result.contract.ActivityResultContracts
19+
import androidx.annotation.RequiresApi
20+
import androidx.core.content.ContextCompat.getSystemService
1221
import androidx.core.view.ViewCompat
1322
import androidx.core.view.WindowInsetsCompat
1423
import androidx.core.view.updatePadding
@@ -28,6 +37,20 @@ import ltd.evilcorp.domain.tox.PublicKey
2837
private const val PERMISSION = Manifest.permission.RECORD_AUDIO
2938

3039
class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::inflate) {
40+
41+
companion object {
42+
private var hasCalled = false
43+
44+
@RequiresApi(Build.VERSION_CODES.S)
45+
private val audioOutputDevices = arrayOf(
46+
TYPE_WIRED_HEADPHONES,
47+
TYPE_WIRED_HEADSET,
48+
TYPE_BLE_HEADSET,
49+
TYPE_BLE_SPEAKER,
50+
TYPE_BLUETOOTH_SCO,
51+
)
52+
}
53+
3154
private val vm: CallViewModel by viewModels { vmFactory }
3255

3356
private val requestPermissionLauncher = registerForActivityResult(
@@ -40,6 +63,7 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl
4063
}
4164
}
4265

66+
@RequiresApi(Build.VERSION_CODES.S)
4367
override fun onViewCreated(view: View, savedInstanceState: Bundle?) = binding.run {
4468
ViewCompat.setOnApplyWindowInsetsListener(view) { _, compat ->
4569
val insets = compat.getInsets(WindowInsetsCompat.Type.systemBars())
@@ -77,6 +101,7 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl
77101
}
78102
}
79103

104+
updateSpeakerphoneOnDetectHeadphones()
80105
updateSpeakerphoneIcon()
81106
speakerphone.setOnClickListener {
82107
vm.toggleSpeakerphone()
@@ -108,12 +133,28 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl
108133
binding.speakerphone.setImageResource(icon)
109134
}
110135

136+
@RequiresApi(Build.VERSION_CODES.S)
137+
private fun updateSpeakerphoneOnDetectHeadphones() {
138+
if (headphonesArePlugged()) {
139+
vm.disableSpeakerphone()
140+
}
141+
}
142+
111143
private fun startCall() {
112144
vm.startCall()
113145
vm.inCall.asLiveData().observe(viewLifecycleOwner) { inCall ->
114146
if (inCall == CallState.NotInCall) {
115147
findNavController().popBackStack()
148+
hasCalled = false
116149
}
117150
}
118151
}
152+
153+
@RequiresApi(Build.VERSION_CODES.S)
154+
private fun headphonesArePlugged(): Boolean {
155+
val audioManager = context?.let { getSystemService(it, AudioManager::class.java) } ?: return false
156+
return audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS).any { info ->
157+
audioOutputDevices.contains(info.type)
158+
}
159+
}
119160
}

atox/src/main/kotlin/ui/call/CallViewModel.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ class CallViewModel @Inject constructor(
4949
fun startSendingAudio() = callManager.startSendingAudio()
5050
fun stopSendingAudio() = callManager.stopSendingAudio()
5151

52-
fun toggleSpeakerphone() {
53-
speakerphoneOn = !speakerphoneOn
52+
fun applyProximityScreenSetting() {
5453
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
5554
if (speakerphoneOn) {
5655
proximityScreenOff.release()
@@ -60,6 +59,16 @@ class CallViewModel @Inject constructor(
6059
}
6160
}
6261

62+
fun disableSpeakerphone() {
63+
speakerphoneOn = false
64+
applyProximityScreenSetting()
65+
}
66+
67+
fun toggleSpeakerphone() {
68+
speakerphoneOn = !speakerphoneOn
69+
applyProximityScreenSetting()
70+
}
71+
6372
val inCall = callManager.inCall
6473
val sendingAudio = callManager.sendingAudio
6574

domain/src/main/kotlin/av/HeadsetPlugReceiver.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ class HeadsetPlugReceiver : BroadcastReceiver() {
2121
private val _isPlugged = MutableStateFlow(false)
2222
val isPlugged: StateFlow<Boolean> = _isPlugged.asStateFlow()
2323

24-
2524
private fun checkStateOff(intent: Intent) {
26-
Log.d(logTag, "[HeadsetPlugReceiver.checkStateOff]")
25+
Log.d(logTag, "[HeadsetPlugReceiver.checkStateOff] intent: $intent")
2726
}
2827

2928
private fun sendEvent(intent: Intent) {

0 commit comments

Comments
 (0)