Loading android/app/src/com/android/bluetooth/hfp/HeadsetService.java +39 −41 Original line number Diff line number Diff line Loading @@ -80,7 +80,6 @@ import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.FutureTask; /** * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application. Loading Loading @@ -1153,31 +1152,40 @@ public class HeadsetService extends ProfileService { } else { stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device); } if (!Utils.isScoManagedByAudioEnabled()) { stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device); } } if (Utils.isScoManagedByAudioEnabled()) { BluetoothDevice voiceRecognitionDevice = device; // when isScoManagedByAudio is on, tell AudioManager to connect SCO AudioManager am = mSystemInterface.getAudioManager(); BluetoothDevice finalDevice = device; Optional<AudioDeviceInfo> audioDeviceInfo = am.getAvailableCommunicationDevices().stream() .filter( x -> x.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO && x.getAddress() .equals(finalDevice.getAddress())) .equals( voiceRecognitionDevice .getAddress())) .findFirst(); if (audioDeviceInfo.isPresent()) { mHandler.post( () -> { am.setCommunicationDevice(audioDeviceInfo.get()); Log.i(TAG, "Audio Manager will initiate the SCO connection"); Log.i(TAG, "Audio Manager will initiate the SCO for Voice Recognition"); }); return true; } } else { Log.w( TAG, "Cannot find audioDeviceInfo that matches device=" + device + voiceRecognitionDevice + " to create the SCO"); return false; } stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device); } enableSwbCodec(HeadsetHalConstants.BTHF_SWB_CODEC_VENDOR_APTX, true, device); return true; Loading Loading @@ -1213,12 +1221,19 @@ public class HeadsetService extends ProfileService { } mVoiceRecognitionStarted = false; stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device); if (!Utils.isScoManagedByAudioEnabled()) { stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device); } } if (Utils.isScoManagedByAudioEnabled()) { // do the task outside synchronized to avoid deadlock with Audio Fwk mHandler.post( () -> { mSystemInterface.getAudioManager().clearCommunicationDevice(); }); return true; } stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device); } enableSwbCodec(HeadsetHalConstants.BTHF_SWB_CODEC_VENDOR_APTX, false, device); return true; } Loading Loading @@ -2004,23 +2019,6 @@ public class HeadsetService extends ProfileService { HeadsetStateMachine.CALL_STATE_CHANGED, new HeadsetCallState( numActive, numHeld, callState, number, type, name))); if (Utils.isScoManagedByAudioEnabled()) { if (mActiveDevice == null) { Log.i(TAG, "HeadsetService's active device is null"); } else { // wait until mActiveDevice's state machine processed CALL_STATE_CHANGED message, // then Audio Framework starts the SCO connection FutureTask task = new FutureTask(() -> {}, null); mStateMachines.get(mActiveDevice).getHandler().post(task); try { task.get(); } catch (Exception e) { Log.e( TAG, "Exception when waiting for CALL_STATE_CHANGED message" + e.toString()); } } } mStateMachinesThreadHandler.post( () -> { if (callState == HeadsetHalConstants.CALL_STATE_IDLE Loading system/audio_hal_interface/aidl/hfp_client_interface_aidl.cc +8 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,14 @@ BluetoothAudioCtrlAck HfpTransport::StartRequest() { /* Post start SCO event and wait for sco to open */ hfp_pending_cmd_ = HFP_CTRL_CMD_START; bool is_call_idle = bluetooth::headset::IsCallIdle(); bool is_during_vr = bluetooth::headset::IsDuringVoiceRecognition(&(cb->peer_addr)); if (is_call_idle && !is_during_vr) { log::warn("Call ongoing={}, voice recognition ongoing={}, wait for retry", !is_call_idle, is_during_vr); hfp_pending_cmd_ = HFP_CTRL_CMD_NONE; return BluetoothAudioCtrlAck::PENDING; } // as ConnectAudio only queues the command into main thread, keep PENDING // status auto status = bluetooth::headset::GetInterface()->ConnectAudio(&cb->peer_addr, 0); Loading system/btif/include/btif_hf.h +2 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,8 @@ Interface* GetInterface(); */ bool IsCallIdle(); bool IsDuringVoiceRecognition(RawAddress* bd_addr); /** * Start up or shutdown the service * Loading system/btif/src/btif_hf.cc +27 −0 Original line number Diff line number Diff line Loading @@ -115,6 +115,7 @@ struct btif_hf_cb_t { tBTA_AG_PEER_FEAT peer_feat; int num_active; int num_held; bool is_during_voice_recognition; bthf_call_state_t call_setup_state; }; Loading @@ -135,6 +136,8 @@ static const char* dump_hf_call_state(bthf_call_state_t call_state) { } } static int btif_hf_idx_by_bdaddr(RawAddress* bd_addr); /** * Check if bd_addr is the current active device. * Loading Loading @@ -821,6 +824,28 @@ bool IsCallIdle() { return true; } bool IsDuringVoiceRecognition(RawAddress* bd_addr) { if (!bt_hf_callbacks) { return false; } if (bd_addr == nullptr) { log::error("null address"); return false; } int idx = btif_hf_idx_by_bdaddr(bd_addr); if ((idx < 0) || (idx >= BTA_AG_MAX_NUM_CLIENTS)) { log::error("Invalid index {}", idx); return false; } if (!is_connected(bd_addr)) { log::error("{} is not connected", *bd_addr); return false; } bool in_vr = btif_hf_cb[idx].is_during_voice_recognition; log::debug("IsDuringVoiceRecognition={}", in_vr); return in_vr; } class HeadsetInterface : Interface { public: static Interface* GetInstance() { Loading Loading @@ -992,6 +1017,7 @@ bt_status_t HeadsetInterface::StartVoiceRecognition(RawAddress* bd_addr) { log::error("voice recognition not supported, features=0x{:x}", btif_hf_cb[idx].peer_feat); return BT_STATUS_UNSUPPORTED; } btif_hf_cb[idx].is_during_voice_recognition = true; tBTA_AG_RES_DATA ag_res = {}; ag_res.state = true; BTA_AgResult(btif_hf_cb[idx].handle, BTA_AG_BVRA_RES, ag_res); Loading @@ -1014,6 +1040,7 @@ bt_status_t HeadsetInterface::StopVoiceRecognition(RawAddress* bd_addr) { log::error("voice recognition not supported, features=0x{:x}", btif_hf_cb[idx].peer_feat); return BT_STATUS_UNSUPPORTED; } btif_hf_cb[idx].is_during_voice_recognition = false; tBTA_AG_RES_DATA ag_res = {}; ag_res.state = false; BTA_AgResult(btif_hf_cb[idx].handle, BTA_AG_BVRA_RES, ag_res); Loading system/test/mock/mock_btif_hf.cc +12 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,8 @@ namespace btif_hf { // Function state capture and return values, if needed struct GetInterface GetInterface; struct IsCallIdle IsCallIdle; struct IsDuringVoiceRecognition IsDuringVoiceRecognition; } // namespace btif_hf } // namespace mock } // namespace test Loading @@ -49,6 +51,16 @@ Interface* GetInterface() { inc_func_call_count(__func__); return test::mock::btif_hf::GetInterface(); } bool IsCallIdle() { inc_func_call_count(__func__); return test::mock::btif_hf::IsCallIdle(); } bool IsDuringVoiceRecognition(RawAddress* bd_addr) { inc_func_call_count(__func__); return test::mock::btif_hf::IsDuringVoiceRecognition(bd_addr); } } // namespace headset } // namespace bluetooth Loading Loading
android/app/src/com/android/bluetooth/hfp/HeadsetService.java +39 −41 Original line number Diff line number Diff line Loading @@ -80,7 +80,6 @@ import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.concurrent.FutureTask; /** * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application. Loading Loading @@ -1153,31 +1152,40 @@ public class HeadsetService extends ProfileService { } else { stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device); } if (!Utils.isScoManagedByAudioEnabled()) { stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device); } } if (Utils.isScoManagedByAudioEnabled()) { BluetoothDevice voiceRecognitionDevice = device; // when isScoManagedByAudio is on, tell AudioManager to connect SCO AudioManager am = mSystemInterface.getAudioManager(); BluetoothDevice finalDevice = device; Optional<AudioDeviceInfo> audioDeviceInfo = am.getAvailableCommunicationDevices().stream() .filter( x -> x.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO && x.getAddress() .equals(finalDevice.getAddress())) .equals( voiceRecognitionDevice .getAddress())) .findFirst(); if (audioDeviceInfo.isPresent()) { mHandler.post( () -> { am.setCommunicationDevice(audioDeviceInfo.get()); Log.i(TAG, "Audio Manager will initiate the SCO connection"); Log.i(TAG, "Audio Manager will initiate the SCO for Voice Recognition"); }); return true; } } else { Log.w( TAG, "Cannot find audioDeviceInfo that matches device=" + device + voiceRecognitionDevice + " to create the SCO"); return false; } stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device); } enableSwbCodec(HeadsetHalConstants.BTHF_SWB_CODEC_VENDOR_APTX, true, device); return true; Loading Loading @@ -1213,12 +1221,19 @@ public class HeadsetService extends ProfileService { } mVoiceRecognitionStarted = false; stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device); if (!Utils.isScoManagedByAudioEnabled()) { stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device); } } if (Utils.isScoManagedByAudioEnabled()) { // do the task outside synchronized to avoid deadlock with Audio Fwk mHandler.post( () -> { mSystemInterface.getAudioManager().clearCommunicationDevice(); }); return true; } stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device); } enableSwbCodec(HeadsetHalConstants.BTHF_SWB_CODEC_VENDOR_APTX, false, device); return true; } Loading Loading @@ -2004,23 +2019,6 @@ public class HeadsetService extends ProfileService { HeadsetStateMachine.CALL_STATE_CHANGED, new HeadsetCallState( numActive, numHeld, callState, number, type, name))); if (Utils.isScoManagedByAudioEnabled()) { if (mActiveDevice == null) { Log.i(TAG, "HeadsetService's active device is null"); } else { // wait until mActiveDevice's state machine processed CALL_STATE_CHANGED message, // then Audio Framework starts the SCO connection FutureTask task = new FutureTask(() -> {}, null); mStateMachines.get(mActiveDevice).getHandler().post(task); try { task.get(); } catch (Exception e) { Log.e( TAG, "Exception when waiting for CALL_STATE_CHANGED message" + e.toString()); } } } mStateMachinesThreadHandler.post( () -> { if (callState == HeadsetHalConstants.CALL_STATE_IDLE Loading
system/audio_hal_interface/aidl/hfp_client_interface_aidl.cc +8 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,14 @@ BluetoothAudioCtrlAck HfpTransport::StartRequest() { /* Post start SCO event and wait for sco to open */ hfp_pending_cmd_ = HFP_CTRL_CMD_START; bool is_call_idle = bluetooth::headset::IsCallIdle(); bool is_during_vr = bluetooth::headset::IsDuringVoiceRecognition(&(cb->peer_addr)); if (is_call_idle && !is_during_vr) { log::warn("Call ongoing={}, voice recognition ongoing={}, wait for retry", !is_call_idle, is_during_vr); hfp_pending_cmd_ = HFP_CTRL_CMD_NONE; return BluetoothAudioCtrlAck::PENDING; } // as ConnectAudio only queues the command into main thread, keep PENDING // status auto status = bluetooth::headset::GetInterface()->ConnectAudio(&cb->peer_addr, 0); Loading
system/btif/include/btif_hf.h +2 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,8 @@ Interface* GetInterface(); */ bool IsCallIdle(); bool IsDuringVoiceRecognition(RawAddress* bd_addr); /** * Start up or shutdown the service * Loading
system/btif/src/btif_hf.cc +27 −0 Original line number Diff line number Diff line Loading @@ -115,6 +115,7 @@ struct btif_hf_cb_t { tBTA_AG_PEER_FEAT peer_feat; int num_active; int num_held; bool is_during_voice_recognition; bthf_call_state_t call_setup_state; }; Loading @@ -135,6 +136,8 @@ static const char* dump_hf_call_state(bthf_call_state_t call_state) { } } static int btif_hf_idx_by_bdaddr(RawAddress* bd_addr); /** * Check if bd_addr is the current active device. * Loading Loading @@ -821,6 +824,28 @@ bool IsCallIdle() { return true; } bool IsDuringVoiceRecognition(RawAddress* bd_addr) { if (!bt_hf_callbacks) { return false; } if (bd_addr == nullptr) { log::error("null address"); return false; } int idx = btif_hf_idx_by_bdaddr(bd_addr); if ((idx < 0) || (idx >= BTA_AG_MAX_NUM_CLIENTS)) { log::error("Invalid index {}", idx); return false; } if (!is_connected(bd_addr)) { log::error("{} is not connected", *bd_addr); return false; } bool in_vr = btif_hf_cb[idx].is_during_voice_recognition; log::debug("IsDuringVoiceRecognition={}", in_vr); return in_vr; } class HeadsetInterface : Interface { public: static Interface* GetInstance() { Loading Loading @@ -992,6 +1017,7 @@ bt_status_t HeadsetInterface::StartVoiceRecognition(RawAddress* bd_addr) { log::error("voice recognition not supported, features=0x{:x}", btif_hf_cb[idx].peer_feat); return BT_STATUS_UNSUPPORTED; } btif_hf_cb[idx].is_during_voice_recognition = true; tBTA_AG_RES_DATA ag_res = {}; ag_res.state = true; BTA_AgResult(btif_hf_cb[idx].handle, BTA_AG_BVRA_RES, ag_res); Loading @@ -1014,6 +1040,7 @@ bt_status_t HeadsetInterface::StopVoiceRecognition(RawAddress* bd_addr) { log::error("voice recognition not supported, features=0x{:x}", btif_hf_cb[idx].peer_feat); return BT_STATUS_UNSUPPORTED; } btif_hf_cb[idx].is_during_voice_recognition = false; tBTA_AG_RES_DATA ag_res = {}; ag_res.state = false; BTA_AgResult(btif_hf_cb[idx].handle, BTA_AG_BVRA_RES, ag_res); Loading
system/test/mock/mock_btif_hf.cc +12 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,8 @@ namespace btif_hf { // Function state capture and return values, if needed struct GetInterface GetInterface; struct IsCallIdle IsCallIdle; struct IsDuringVoiceRecognition IsDuringVoiceRecognition; } // namespace btif_hf } // namespace mock } // namespace test Loading @@ -49,6 +51,16 @@ Interface* GetInterface() { inc_func_call_count(__func__); return test::mock::btif_hf::GetInterface(); } bool IsCallIdle() { inc_func_call_count(__func__); return test::mock::btif_hf::IsCallIdle(); } bool IsDuringVoiceRecognition(RawAddress* bd_addr) { inc_func_call_count(__func__); return test::mock::btif_hf::IsDuringVoiceRecognition(bd_addr); } } // namespace headset } // namespace bluetooth Loading