통화관련 블루투스 Audio
제대로된 방법이 아닐 수 있습니다 !!
통화 관련 SDK를 이용하고 있고, 블루투스를 이용하여 Audio Control을 해야 하는 상황.
참고로 OS 12이상에서는 BLUETOOTH_CONNECT 권한 필요(연결된 디바이스 가져오려면)
1. 휴대폰의 블루투스가 Enable / Disabled 되어있는지 확인하기
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
publishEnableBluetooth(bluetoothAdapter?.isEnabled == true)
publishEnable함수는 EventBus를 통해 뷰쪽으로 값 전달 하는 함수.
2. 브로드 캐스트 액션 등록 - 블루투스 STATE값 가져오기.
val intentFilter = IntentFilter()
intentFilter.addAction(ACTION_STATE_CHANGED)
intentFilter.addAction(ACTION_AUDIO_STATE_CHANGED)
intentFilter.addAction(ACTION_CONNECTION_STATE_CHANGED)
registerReceiver(
bluetoothStateReceiver,
intentFilter
)
private val bluetoothStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACTION_STATE_CHANGED ->
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR)) {
STATE_TURNING_OFF -> {
ExLog.d(TAG, "************* STATE_TURNING_OFF")
publishEnableBluetooth(false)
}
STATE_ON -> {
ExLog.d(TAG, "************* STATE_ON")
publishEnableBluetooth(true)
}
}
ACTION_AUDIO_STATE_CHANGED ->
when (intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, ERROR)) {
STATE_AUDIO_CONNECTED -> {
ExLog.d(TAG, "************* STATE_AUDIO_CONNECTED")
}
STATE_AUDIO_DISCONNECTED -> {
ExLog.d(TAG, "************* STATE_AUDIO_DISCONNECTED")
if (currentAudioType == SelectButtonType.BLUETOOTH) {
publishAudioType(
if (isCurrentTypeAndRingPlay()) SelectButtonType.SPEAKER else SelectButtonType.PHONE
)
}
}
}
ACTION_CONNECTION_STATE_CHANGED -> {
when (intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, ERROR)) {
STATE_CONNECTED -> {
isBluetoothConnected = true
ExLog.d(TAG, "************* BluetoothHeadset STATE_CONNECTED")
getProfileProxy()
}
STATE_DISCONNECTED -> {
isBluetoothConnected = false
ExLog.d(TAG, "************* BluetoothHeadset STATE_DISCONNECTED")
publishAudioType(
if (isCurrentTypeAndRingPlay()) SelectButtonType.SPEAKER else SelectButtonType.PHONE
)
}
}
}
}
}
}
isCurrentTypeAndRingPlay는
벨소리가 울리는 상황인지 & 현재 오디오타입이 스피커인지 판단해주는 함수.
ACTION_STATE_CHANGED : 휴대폰 블루투스 Enable/Disable변경시 호출된다.
ACTION_AUDIO_STATE_CHANGED : 블루투스의 오디오가 실제 연결/연결끊김 발생시 호출된다.
ACTION_CONNECTION_STATE_CHANGED : 블루투스가 실제 휴대폰에 연결/연결끊김 발생시 호출 (이미 연결되어진 상태라면 호출 안되는 듯!)
3. getProfileProxy
호출 할 때마다 listener이 불리며, 연결된 디바이스를 가져와서 블루투스 or 다른곳으로 송출할지 판단함
bluetoothAdapter?.getProfileProxy(this, profileListener, BluetoothProfile.HEADSET)
private val profileListener = object : BluetoothProfile.ServiceListener {
@SuppressLint("MissingPermission")
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) {
ExLog.d(TAG, "************* bluetooth onServiceConnected : $profile")
if (profile == BluetoothProfile.HEADSET) {
bluetoothHeadset = proxy as BluetoothHeadset
bluetoothHeadset
?.connectedDevices
?.takeIf {
0 < it.size &&
ringBleManager.isBluetoothDevice() == true //블루투스 기기 연결 여부
}
?.let {
//실제 블루투스가 연결되어있는 상태라면
ExLog.d(TAG, "************* bluetooth device Connected")
ringBleManager.isBluetoothConnected = true
ringBleManager.publishAudioType(
BLUETOOTH,
ringMediaManager.isRingPlayingAndReceiveCall() //추후 소스 분리하여 manager파일 생김.
)
}
?: run {
ExLog.d(TAG, "************* bluetooth device not Connected")
ringBleManager.isBluetoothConnected = false
ringBleManager.publishAudioType(
if (isCurrentTypeAndRingPlay()) SPEAKER else PHONE,
ringMediaManager.isRingPlayingAndReceiveCall()
)
}
}
}
override fun onServiceDisconnected(profile: Int) {
if (profile == BluetoothProfile.HEADSET) {
bluetoothHeadset = null
publishAudioType(
if (isCurrentTypeAndRingPlay()) SelectButtonType.SPEAKER else SelectButtonType.PHONE
)
}
}
}
연결된 디바이스 타입이 BLUETOOTH_SCO라면
디바이스 연결 되었다고 판단하여 값 true로 설정하고 AudioManager를 통해 startBluetoothSCO를 해준다.
4. AudioManager를 통해 출력 변경
/**
* AudioManager를 통해 벨소리 및 통화중 Phone/Speaker/Bluetooth로 오디오 출력 설정
*/
private fun requestCurrentAudioType(audioType: SelectButtonType) {
if (audioType == currentAudioType) {
ExLog.d(TAG, "************* SAME A U D I O T Y P E : $audioType")
return
}
resetAudioType()
AudioHelper.audioManager?.apply {
currentAudioType = when (audioType) {
SelectButtonType.BLUETOOTH -> {
if (!isBluetoothConnected) {
publishAudioType(SelectButtonType.PHONE)
return@apply
}
setAudioOutDevice(this, getAudioDevice(this, AudioDeviceInfo.TYPE_BLUETOOTH_SCO))
SelectButtonType.BLUETOOTH
}
SelectButtonType.SPEAKER -> {
setAudioOutDevice(this, getAudioDevice(this, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER))
SelectButtonType.SPEAKER
}
else -> { //phone
setAudioOutDevice(this, getAudioDevice(this, AudioDeviceInfo.TYPE_BUILTIN_EARPIECE))
SelectButtonType.PHONE
}
}
}
}
resetAudioType으로 초기화 시켜준 뒤에 변경 하였습니다.
/**
* AudioManager 설정 Reset
*/
private fun resetAudioType() {
AudioHelper.audioManager?.apply {
changeSco(this, false)
isSpeakerphoneOn = false
mode = AudioManager.MODE_NORMAL
currentAudioType = SelectButtonType.PHONE
}
}
4-1. setAudioOutDevice
/**
* 가져온 AudioDeviceInfo로 CommunicationDevice Set해준다. (OS 12이상)
*/
private fun setAudioOutDevice(audioManager: AudioManager, target: AudioDeviceInfo?) {
//need some delay for bluetooth sco
var audioChangeDelay: Long = 15
if (System.currentTimeMillis() - lastAudioChangeTime <= 300) audioChangeDelay = 300
lastAudioChangeTime = System.currentTimeMillis()
Handler(Looper.getMainLooper()).postDelayed({
audioManager.run {
var targetDevice = target
//no device selected. scan all output devices and select one automatically
if (targetDevice == null) {
var wiredHeadsetDevice: AudioDeviceInfo? =
getAudioDevice(this, AudioDeviceInfo.TYPE_WIRED_HEADSET)
if (wiredHeadsetDevice == null) wiredHeadsetDevice =
getAudioDevice(this, AudioDeviceInfo.TYPE_WIRED_HEADPHONES)
val bluetoothDevice: AudioDeviceInfo? =
getAudioDevice(this, AudioDeviceInfo.TYPE_BLUETOOTH_SCO)
val speakerDevice: AudioDeviceInfo? =
getAudioDevice(this, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
val earpieceDevice: AudioDeviceInfo? =
getAudioDevice(this, AudioDeviceInfo.TYPE_BUILTIN_EARPIECE)
//update global variables
var isBluetoothAvailable = bluetoothDevice != null
//disable BT if wired headset connected
if (wiredHeadsetDevice != null) isBluetoothAvailable = false
//choose an output device
targetDevice =
if (
isRingPlaying &&
!isOutGoingCall &&
bluetoothDevice == null &&
wiredHeadsetDevice == null
) {
speakerDevice
} else if (
isRingPlaying &&
!isOutGoingCall &&
wiredHeadsetDevice == null &&
!isBluetoothEnabled
) {
speakerDevice
} else {
wiredHeadsetDevice ?: if (
isBluetoothAvailable &&
isBluetoothEnabled &&
bluetoothDevice != null
) {
bluetoothDevice
} else {
earpieceDevice ?: speakerDevice
}
}
}
//set output device
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
clearCommunicationDevice()
if (targetDevice != null) {
setCommunicationDevice(targetDevice)
}
} catch (e: Exception) {
ExLog.d(TAG, e.message.toString())
}
}
isSpeakerphoneOn = if (targetDevice?.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
changeSco(this, false)
true
} else {
if (targetDevice?.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
changeSco(this, true)
} else {
changeSco(this, false)
}
false
}
mode = if (
isRingPlaying &&
!isOutGoingCall
) {
AudioManager.MODE_RINGTONE
} else {
AudioManager.MODE_IN_COMMUNICATION
}
}
}, audioChangeDelay)
}
4-2. 원하는 타입의 디바이스 가져오기
/**
* 원하는 타입의 디바이스를 가져온다.
*/
private fun getAudioDevice(audioManager: AudioManager, type: Int): AudioDeviceInfo? {
audioManager.run {
val audioDevices = getDevices(AudioManager.GET_DEVICES_OUTPUTS)
if (audioDevices != null) {
for (deviceInfo in audioDevices) {
if (type == deviceInfo.type) return deviceInfo
}
}
}
return null
}
화면단에서는 뷰모델에서
EventBus로 값을 받고, Ui만 업데이트 시켜줍니다.
실제 뷰에서 오디오 타입을 변경할 때에는 EventBus로 다시 AudioSet할 수 있도록 전달합니다.
정답은 아니겠지만..
힘들게 로직 작업을 한 내용이라 잊지 않기 위해 메모!
BroadcastReceiver - ACTION_AUDIO_STATE_CHANGED - STATE_AUDIO_DISCONNECTED
가 블루투스 연결시 or 다른 오디오로 송출할때 같이 호출이되어 문제가 발생하여
STATE_AUDIO_DISCONNECTED에서 연결 끊어주던 로직은 제거하였습니다.
한 파일안에서 너무 많은것을 작업하여
RingMediaManager와 RingBluetoothManager로 소스 분리하였습니다.
갤럭시 왓치도 블루투스 기기 연결 되었다고 판단이되어
왓치와 블루투스 이어폰(?) 판단하는 함수 추가
/**
* 블루투스 SCO 기기가 연결 되어있는지 판단
*/
fun isBluetoothDevice(): Boolean? {
return (context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.run {
val bluetoothDevice: AudioDeviceInfo? =
getAudioDevice(this, AudioDeviceInfo.TYPE_BLUETOOTH_SCO)
ExLog.d(TAG, "************* bluetooth device isBluetoothDevice (${bluetoothDevice?.productName})")
return@run when {
isBluetoothEnabled &&
bluetoothDevice != null -> {
true
}
else -> {
null
}
}
}
}
'안드로이드' 카테고리의 다른 글
[Android] 푸시 원하는 화면이동 parentActivityName (0) | 2023.04.19 |
---|---|
웹뷰 shouldOverrideUrlLoading 호출되지 않을 때. (0) | 2023.04.04 |
Google Cloud Speech (STT) 설정 (0) | 2022.11.24 |
ScopeStorage 저장공간에 대한 정의 (0) | 2022.11.14 |
Bitrise-android (0) | 2022.11.14 |