Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions atox/src/main/kotlin/ui/call/CallFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@
package ltd.evilcorp.atox.ui.call

import android.Manifest
import android.media.AudioDeviceInfo.TYPE_BLE_HEADSET
import android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER
import android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO
import android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES
import android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET
import android.media.AudioManager
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
Expand All @@ -28,6 +37,20 @@ import ltd.evilcorp.domain.tox.PublicKey
private const val PERMISSION = Manifest.permission.RECORD_AUDIO

class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::inflate) {

companion object {
private var hasCalled = false

@RequiresApi(Build.VERSION_CODES.S)
private val audioOutputDevices = arrayOf(
TYPE_WIRED_HEADPHONES,
TYPE_WIRED_HEADSET,
TYPE_BLE_HEADSET,
TYPE_BLE_SPEAKER,
TYPE_BLUETOOTH_SCO,
)
}

private val vm: CallViewModel by viewModels { vmFactory }

private val requestPermissionLauncher = registerForActivityResult(
Expand All @@ -40,6 +63,7 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl
}
}

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

updateSpeakerphoneOnDetectHeadphones()
updateSpeakerphoneIcon()
speakerphone.setOnClickListener {
vm.toggleSpeakerphone()
Expand Down Expand Up @@ -108,12 +133,28 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl
binding.speakerphone.setImageResource(icon)
}

@RequiresApi(Build.VERSION_CODES.S)
private fun updateSpeakerphoneOnDetectHeadphones() {
if (headphonesArePlugged()) {
vm.disableSpeakerphone()
}
}

private fun startCall() {
vm.startCall()
vm.inCall.asLiveData().observe(viewLifecycleOwner) { inCall ->
if (inCall == CallState.NotInCall) {
findNavController().popBackStack()
hasCalled = false
}
}
}

@RequiresApi(Build.VERSION_CODES.S)
private fun headphonesArePlugged(): Boolean {
val audioManager = context?.let { getSystemService(it, AudioManager::class.java) } ?: return false
return audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS).any { info ->
audioOutputDevices.contains(info.type)
}
}
}
13 changes: 11 additions & 2 deletions atox/src/main/kotlin/ui/call/CallViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ class CallViewModel @Inject constructor(
fun startSendingAudio() = callManager.startSendingAudio()
fun stopSendingAudio() = callManager.stopSendingAudio()

fun toggleSpeakerphone() {
speakerphoneOn = !speakerphoneOn
fun applyProximityScreenSetting() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (speakerphoneOn) {
proximityScreenOff.release()
Expand All @@ -60,6 +59,16 @@ class CallViewModel @Inject constructor(
}
}

fun disableSpeakerphone() {
speakerphoneOn = false
applyProximityScreenSetting()
}

fun toggleSpeakerphone() {
speakerphoneOn = !speakerphoneOn
applyProximityScreenSetting()
}

val inCall = callManager.inCall
val sendingAudio = callManager.sendingAudio

Expand Down
46 changes: 46 additions & 0 deletions domain/src/main/kotlin/av/HeadsetPlugReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ltd.evilcorp.domain.av

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.media.AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED
import android.media.AudioManager.EXTRA_SCO_AUDIO_STATE
import android.media.AudioManager.SCO_AUDIO_STATE_CONNECTED
import android.util.Log
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

class HeadsetPlugReceiver : BroadcastReceiver() {

private companion object {
private const val logTag = "HeadsetPlugReceiver"
private const val headsetState = "state"
}

private val _isPlugged = MutableStateFlow(false)
val isPlugged: StateFlow<Boolean> = _isPlugged.asStateFlow()

private fun checkStateOff(intent: Intent) {
Log.d(logTag, "[HeadsetPlugReceiver.checkStateOff] intent: $intent")
}

private fun sendEvent(intent: Intent) {
val isPlugged = when (intent.action) {
Intent.ACTION_HEADSET_PLUG -> intent.getIntExtra(headsetState, 0) == 1
ACTION_SCO_AUDIO_STATE_UPDATED -> intent.getIntExtra(EXTRA_SCO_AUDIO_STATE, 0) == SCO_AUDIO_STATE_CONNECTED
else -> false
}
Log.d(logTag, "[HeadsetPlugReceiver.sendEvent] isPlugged: $isPlugged")
_isPlugged.value = isPlugged
}

override fun onReceive(context: Context, intent: Intent?) {
val action = intent?.action ?: return
Log.d(logTag, "[HeadsetPlugReceiver.onReceive] action: $action")
when (action) {
Intent.ACTION_HEADSET_PLUG, ACTION_SCO_AUDIO_STATE_UPDATED -> sendEvent(intent)
else -> checkStateOff(intent)
}
}
}