Loading android/app/src/com/android/bluetooth/hfp/HeadsetService.java +30 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.Uri; import android.os.BatteryManager; Loading Loading @@ -74,6 +75,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Optional; /** * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application. Loading Loading @@ -1179,6 +1181,30 @@ public class HeadsetService extends ProfileService { } else { stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device); } if (Flags.isScoManagedByAudio()) { // 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())) .findFirst(); if (audioDeviceInfo.isPresent()) { am.setCommunicationDevice(audioDeviceInfo.get()); Log.i(TAG, "Audio Manager will initiate the SCO connection"); return true; } Log.w( TAG, "Cannot find audioDeviceInfo that matches device=" + device + " to create the SCO"); return false; } stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device); } return true; Loading Loading @@ -1209,6 +1235,10 @@ public class HeadsetService extends ProfileService { } mVoiceRecognitionStarted = false; stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device); if (Flags.isScoManagedByAudio()) { mSystemInterface.getAudioManager().clearCommunicationDevice(); return true; } stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device); } return true; Loading android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java +76 −1 Original line number Diff line number Diff line Loading @@ -41,7 +41,9 @@ import android.os.Looper; import android.os.ParcelUuid; import android.os.PowerManager; import android.os.RemoteException; import android.platform.test.flag.junit.SetFlagsRule; import android.telecom.PhoneAccount; import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.espresso.intent.Intents; Loading @@ -55,11 +57,13 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.RemoteDevices; import com.android.bluetooth.btservice.SilenceDeviceManager; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; Loading Loading @@ -92,7 +96,7 @@ public class HeadsetServiceAndStateMachineTest { private static final String TEST_PHONE_NUMBER = "1234567890"; private static final String TEST_CALLER_ID = "Test Name"; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Context mTargetContext; private HeadsetService mHeadsetService; private BluetoothAdapter mAdapter; Loading Loading @@ -686,6 +690,26 @@ public class HeadsetServiceAndStateMachineTest { startVoiceRecognitionFromHf(device); } /** * Same process as {@link * HeadsetServiceAndStateMachineTest#testVoiceRecognition_SingleHfInitiatedSuccess()} except the * SCO connection is handled by the Audio Framework */ @Test public void testVoiceRecognition_SingleHfInitiatedSuccess_ScoManagedByAudio() { mSetFlagsRule.enableFlags(Flags.FLAG_IS_SCO_MANAGED_BY_AUDIO); // Connect HF BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); connectTestDevice(device); // Make device active Assert.assertTrue(mHeadsetService.setActiveDevice(device)); verify(mNativeInterface).setActiveDevice(device); Assert.assertEquals(device, mHeadsetService.getActiveDevice()); verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(eq(device), eq(true)); // Start voice recognition startVoiceRecognitionFromHf_ScoManagedByAudio(device); } /** * Test to verify the following behavior regarding active HF stop voice recognition * in the successful scenario Loading Loading @@ -811,6 +835,26 @@ public class HeadsetServiceAndStateMachineTest { startVoiceRecognitionFromAg(); } /** * Same process as {@link * HeadsetServiceAndStateMachineTest#testVoiceRecognition_SingleAgInitiatedSuccess()} except the * SCO connection is handled by the Audio Framework */ @Test public void testVoiceRecognition_SingleAgInitiatedSuccess_ScoManagedByAudio() { mSetFlagsRule.enableFlags(Flags.FLAG_IS_SCO_MANAGED_BY_AUDIO); // Connect HF BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); connectTestDevice(device); // Make device active Assert.assertTrue(mHeadsetService.setActiveDevice(device)); verify(mNativeInterface).setActiveDevice(device); Assert.assertEquals(device, mHeadsetService.getActiveDevice()); verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(eq(device), eq(true)); // Start voice recognition startVoiceRecognitionFromAg_ScoManagedByAudio(); } /** * Test to verify the following behavior regarding AG initiated voice recognition * in the successful scenario Loading Loading @@ -1167,6 +1211,26 @@ public class HeadsetServiceAndStateMachineTest { verifyNoMoreInteractions(mNativeInterface); } private void startVoiceRecognitionFromHf_ScoManagedByAudio(BluetoothDevice device) { if (!Flags.isScoManagedByAudio()) { Log.i(TAG, "isScoManagedByAudio is disabled"); return; } // Start voice recognition HeadsetStackEvent startVrEvent = new HeadsetStackEvent( HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, HeadsetHalConstants.VR_STATE_STARTED, device); mHeadsetService.messageFromNative(startVrEvent); verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition(); // has not add verification AudioDeviceInfo because it is final, unless add a wrapper mHeadsetService.startVoiceRecognition(device); verify(mAudioManager, times(0)).setA2dpSuspended(true); verify(mAudioManager, times(0)).setLeAudioSuspended(true); verify(mNativeInterface, times(0)).connectAudio(device); } private void startVoiceRecognitionFromAg() { BluetoothDevice device = mHeadsetService.getActiveDevice(); Assert.assertNotNull(device); Loading @@ -1185,6 +1249,17 @@ public class HeadsetServiceAndStateMachineTest { verifyNoMoreInteractions(mNativeInterface); } private void startVoiceRecognitionFromAg_ScoManagedByAudio() { BluetoothDevice device = mHeadsetService.getActiveDevice(); Assert.assertNotNull(device); mHeadsetService.startVoiceRecognition(device); // has not add verification AudioDeviceInfo because it is final, unless add a wrapper verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).startVoiceRecognition(device); verify(mAudioManager, times(0)).setA2dpSuspended(true); verify(mAudioManager, times(0)).setLeAudioSuspended(true); verify(mNativeInterface, times(0)).connectAudio(device); } private void connectTestDevice(BluetoothDevice device) { when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); Loading Loading
android/app/src/com/android/bluetooth/hfp/HeadsetService.java +30 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.Uri; import android.os.BatteryManager; Loading Loading @@ -74,6 +75,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Optional; /** * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application. Loading Loading @@ -1179,6 +1181,30 @@ public class HeadsetService extends ProfileService { } else { stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device); } if (Flags.isScoManagedByAudio()) { // 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())) .findFirst(); if (audioDeviceInfo.isPresent()) { am.setCommunicationDevice(audioDeviceInfo.get()); Log.i(TAG, "Audio Manager will initiate the SCO connection"); return true; } Log.w( TAG, "Cannot find audioDeviceInfo that matches device=" + device + " to create the SCO"); return false; } stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device); } return true; Loading Loading @@ -1209,6 +1235,10 @@ public class HeadsetService extends ProfileService { } mVoiceRecognitionStarted = false; stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device); if (Flags.isScoManagedByAudio()) { mSystemInterface.getAudioManager().clearCommunicationDevice(); return true; } stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device); } return true; Loading
android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java +76 −1 Original line number Diff line number Diff line Loading @@ -41,7 +41,9 @@ import android.os.Looper; import android.os.ParcelUuid; import android.os.PowerManager; import android.os.RemoteException; import android.platform.test.flag.junit.SetFlagsRule; import android.telecom.PhoneAccount; import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.espresso.intent.Intents; Loading @@ -55,11 +57,13 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.RemoteDevices; import com.android.bluetooth.btservice.SilenceDeviceManager; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; Loading Loading @@ -92,7 +96,7 @@ public class HeadsetServiceAndStateMachineTest { private static final String TEST_PHONE_NUMBER = "1234567890"; private static final String TEST_CALLER_ID = "Test Name"; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Context mTargetContext; private HeadsetService mHeadsetService; private BluetoothAdapter mAdapter; Loading Loading @@ -686,6 +690,26 @@ public class HeadsetServiceAndStateMachineTest { startVoiceRecognitionFromHf(device); } /** * Same process as {@link * HeadsetServiceAndStateMachineTest#testVoiceRecognition_SingleHfInitiatedSuccess()} except the * SCO connection is handled by the Audio Framework */ @Test public void testVoiceRecognition_SingleHfInitiatedSuccess_ScoManagedByAudio() { mSetFlagsRule.enableFlags(Flags.FLAG_IS_SCO_MANAGED_BY_AUDIO); // Connect HF BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); connectTestDevice(device); // Make device active Assert.assertTrue(mHeadsetService.setActiveDevice(device)); verify(mNativeInterface).setActiveDevice(device); Assert.assertEquals(device, mHeadsetService.getActiveDevice()); verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(eq(device), eq(true)); // Start voice recognition startVoiceRecognitionFromHf_ScoManagedByAudio(device); } /** * Test to verify the following behavior regarding active HF stop voice recognition * in the successful scenario Loading Loading @@ -811,6 +835,26 @@ public class HeadsetServiceAndStateMachineTest { startVoiceRecognitionFromAg(); } /** * Same process as {@link * HeadsetServiceAndStateMachineTest#testVoiceRecognition_SingleAgInitiatedSuccess()} except the * SCO connection is handled by the Audio Framework */ @Test public void testVoiceRecognition_SingleAgInitiatedSuccess_ScoManagedByAudio() { mSetFlagsRule.enableFlags(Flags.FLAG_IS_SCO_MANAGED_BY_AUDIO); // Connect HF BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); connectTestDevice(device); // Make device active Assert.assertTrue(mHeadsetService.setActiveDevice(device)); verify(mNativeInterface).setActiveDevice(device); Assert.assertEquals(device, mHeadsetService.getActiveDevice()); verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(eq(device), eq(true)); // Start voice recognition startVoiceRecognitionFromAg_ScoManagedByAudio(); } /** * Test to verify the following behavior regarding AG initiated voice recognition * in the successful scenario Loading Loading @@ -1167,6 +1211,26 @@ public class HeadsetServiceAndStateMachineTest { verifyNoMoreInteractions(mNativeInterface); } private void startVoiceRecognitionFromHf_ScoManagedByAudio(BluetoothDevice device) { if (!Flags.isScoManagedByAudio()) { Log.i(TAG, "isScoManagedByAudio is disabled"); return; } // Start voice recognition HeadsetStackEvent startVrEvent = new HeadsetStackEvent( HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, HeadsetHalConstants.VR_STATE_STARTED, device); mHeadsetService.messageFromNative(startVrEvent); verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition(); // has not add verification AudioDeviceInfo because it is final, unless add a wrapper mHeadsetService.startVoiceRecognition(device); verify(mAudioManager, times(0)).setA2dpSuspended(true); verify(mAudioManager, times(0)).setLeAudioSuspended(true); verify(mNativeInterface, times(0)).connectAudio(device); } private void startVoiceRecognitionFromAg() { BluetoothDevice device = mHeadsetService.getActiveDevice(); Assert.assertNotNull(device); Loading @@ -1185,6 +1249,17 @@ public class HeadsetServiceAndStateMachineTest { verifyNoMoreInteractions(mNativeInterface); } private void startVoiceRecognitionFromAg_ScoManagedByAudio() { BluetoothDevice device = mHeadsetService.getActiveDevice(); Assert.assertNotNull(device); mHeadsetService.startVoiceRecognition(device); // has not add verification AudioDeviceInfo because it is final, unless add a wrapper verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).startVoiceRecognition(device); verify(mAudioManager, times(0)).setA2dpSuspended(true); verify(mAudioManager, times(0)).setLeAudioSuspended(true); verify(mNativeInterface, times(0)).connectAudio(device); } private void connectTestDevice(BluetoothDevice device) { when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)) .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); Loading