Loading android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +8 −1 Original line number Diff line number Diff line Loading @@ -634,8 +634,15 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac && mAdapterService.isAllSupportedClassicAudioProfilesActive(device)) { setLeAudioActiveDevice(device); } else { if (Flags.leaudioResumeActiveAfterHfpHandover()) { if (device != null) { // remove LE audio active device when it is not null, and not dual mode setLeAudioActiveDevice(null, true); } } else { setLeAudioActiveDevice(null, true); } } } // Just assign locally the new value mHfpActiveDevice = device; Loading android/app/src/com/android/bluetooth/hfp/HeadsetService.java +11 −1 Original line number Diff line number Diff line Loading @@ -158,7 +158,7 @@ public class HeadsetService extends ProfileService { @VisibleForTesting boolean mIsAptXSwbEnabled = false; @VisibleForTesting boolean mIsAptXSwbPmEnabled = false; private final ServiceFactory mFactory = new ServiceFactory(); @VisibleForTesting ServiceFactory mFactory = new ServiceFactory(); public HeadsetService(Context ctx) { super(ctx); Loading Loading @@ -2143,6 +2143,16 @@ public class HeadsetService extends ProfileService { mSystemInterface.getAudioManager().setA2dpSuspended(false); if (isAtLeastU()) { mSystemInterface.getAudioManager().setLeAudioSuspended(false); // Resumes LE audio previous active device if HFP handover happened before. // Do it here because some controllers cannot handle SCO and CIS // co-existence see {@link LeAudioService#setInactiveForHfpHandover} if (Flags.leaudioResumeActiveAfterHfpHandover()) { LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService != null) { leAudioService.setActiveAfterHfpHandover(); } } } } } Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +42 −3 Original line number Diff line number Diff line Loading @@ -180,7 +180,16 @@ public class LeAudioService extends ProfileService { boolean mLeAudioNativeIsInitialized = false; boolean mLeAudioInbandRingtoneSupportedByPlatform = true; boolean mBluetoothEnabled = false; /** * During a call that has LE Audio -> HFP handover, the HFP device that is going to connect SCO * after LE Audio group becomes idle */ BluetoothDevice mHfpHandoverDevice = null; /** LE audio active device that was removed from active because of HFP handover */ BluetoothDevice mLeAudioDeviceInactivatedForHfpHandover = null; LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null; private DialingOutTimeoutEvent mDialingOutTimeoutEvent = null; @VisibleForTesting Loading Loading @@ -554,6 +563,7 @@ public class LeAudioService extends ProfileService { mLeAudioNativeIsInitialized = false; mBluetoothEnabled = false; mHfpHandoverDevice = null; mLeAudioDeviceInactivatedForHfpHandover = null; mActiveAudioOutDevice = null; mActiveAudioInDevice = null; Loading Loading @@ -3603,7 +3613,11 @@ public class LeAudioService extends ProfileService { } /** * Set Inactive by HFP during handover * Set Inactive by HFP during handover This is a work around to handle controllers that cannot * have SCO and CIS at the same time. So remove active device to tear down CIS, and re-connect * the SCO in {@link LeAudioService#handleGroupIdleDuringCall()} * * @param hfpHandoverDevice is the hfp device that was set to active */ public void setInactiveForHfpHandover(BluetoothDevice hfpHandoverDevice) { if (!mLeAudioNativeIsInitialized) { Loading @@ -3612,10 +3626,29 @@ public class LeAudioService extends ProfileService { } if (getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID) { mHfpHandoverDevice = hfpHandoverDevice; if (Flags.leaudioResumeActiveAfterHfpHandover()) { // record the lead device mLeAudioDeviceInactivatedForHfpHandover = mExposedActiveDevice; } removeActiveDevice(true); } } /** Resume prior active device after HFP phone call hand over */ public void setActiveAfterHfpHandover() { if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return; } if (mLeAudioDeviceInactivatedForHfpHandover != null) { Log.i(TAG, "handover to LE audio device=" + mLeAudioDeviceInactivatedForHfpHandover); setActiveDevice(mLeAudioDeviceInactivatedForHfpHandover); mLeAudioDeviceInactivatedForHfpHandover = null; } else { Log.d(TAG, "nothing to hand over back"); } } /** * Set connection policy of the profile and connects it if connectionPolicy is * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is Loading Loading @@ -4880,10 +4913,16 @@ public class LeAudioService extends ProfileService { ProfileService.println(sb, " mActiveAudioInDevice: " + mActiveAudioInDevice); ProfileService.println(sb, " mUnicastGroupIdDeactivatedForBroadcastTransition: " + mUnicastGroupIdDeactivatedForBroadcastTransition); ProfileService.println(sb, " mBroadcastIdDeactivatedForUnicastTransition: " ProfileService.println( sb, " mBroadcastIdDeactivatedForUnicastTransition: " + mBroadcastIdDeactivatedForUnicastTransition); ProfileService.println(sb, " mExposedActiveDevice: " + mExposedActiveDevice); ProfileService.println(sb, " mHfpHandoverDevice:" + mHfpHandoverDevice); ProfileService.println( sb, " mLeAudioDeviceInactivatedForHfpHandover:" + mLeAudioDeviceInactivatedForHfpHandover); ProfileService.println(sb, " mLeAudioIsInbandRingtoneSupported:" + mLeAudioInbandRingtoneSupportedByPlatform); Loading android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -60,9 +60,11 @@ import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.RemoteDevices; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.SilenceDeviceManager; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; import com.android.bluetooth.le_audio.LeAudioService; import org.hamcrest.Matchers; import org.junit.After; Loading Loading @@ -123,6 +125,10 @@ public class HeadsetServiceAndStateMachineTest { @Mock private HeadsetNativeInterface mNativeInterface; @Mock private LeAudioService mLeAudioService; @Mock private ServiceFactory mServiceFactory; private class HeadsetIntentReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading Loading @@ -1596,6 +1602,41 @@ public class HeadsetServiceAndStateMachineTest { configureHeadsetServiceForAptxVoice(false); } @Test public void testHfpHandoverToLeAudioAfterScoDisconnect() { mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_RESUME_ACTIVE_AFTER_HFP_HANDOVER); Assert.assertNotNull(mHeadsetService.mFactory); doReturn(mLeAudioService).when(mServiceFactory).getLeAudioService(); mHeadsetService.mFactory = mServiceFactory; doReturn(true).when(mSystemInterface).isCallIdle(); // 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)); mHeadsetService.messageFromNative( new HeadsetStackEvent( HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, HeadsetHalConstants.AUDIO_STATE_CONNECTED, device)); TestUtils.waitForLooperToFinishScheduledTask( mHeadsetService.getStateMachinesThreadLooper()); // Audio disconnected mHeadsetService.messageFromNative( new HeadsetStackEvent( HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, device)); verify(mLeAudioService, timeout(1000).atLeastOnce()).setActiveAfterHfpHandover(); } private void startVoiceRecognitionFromHf(BluetoothDevice device) { // Start voice recognition HeadsetStackEvent startVrEvent = Loading Loading
android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +8 −1 Original line number Diff line number Diff line Loading @@ -634,8 +634,15 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac && mAdapterService.isAllSupportedClassicAudioProfilesActive(device)) { setLeAudioActiveDevice(device); } else { if (Flags.leaudioResumeActiveAfterHfpHandover()) { if (device != null) { // remove LE audio active device when it is not null, and not dual mode setLeAudioActiveDevice(null, true); } } else { setLeAudioActiveDevice(null, true); } } } // Just assign locally the new value mHfpActiveDevice = device; Loading
android/app/src/com/android/bluetooth/hfp/HeadsetService.java +11 −1 Original line number Diff line number Diff line Loading @@ -158,7 +158,7 @@ public class HeadsetService extends ProfileService { @VisibleForTesting boolean mIsAptXSwbEnabled = false; @VisibleForTesting boolean mIsAptXSwbPmEnabled = false; private final ServiceFactory mFactory = new ServiceFactory(); @VisibleForTesting ServiceFactory mFactory = new ServiceFactory(); public HeadsetService(Context ctx) { super(ctx); Loading Loading @@ -2143,6 +2143,16 @@ public class HeadsetService extends ProfileService { mSystemInterface.getAudioManager().setA2dpSuspended(false); if (isAtLeastU()) { mSystemInterface.getAudioManager().setLeAudioSuspended(false); // Resumes LE audio previous active device if HFP handover happened before. // Do it here because some controllers cannot handle SCO and CIS // co-existence see {@link LeAudioService#setInactiveForHfpHandover} if (Flags.leaudioResumeActiveAfterHfpHandover()) { LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService != null) { leAudioService.setActiveAfterHfpHandover(); } } } } } Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +42 −3 Original line number Diff line number Diff line Loading @@ -180,7 +180,16 @@ public class LeAudioService extends ProfileService { boolean mLeAudioNativeIsInitialized = false; boolean mLeAudioInbandRingtoneSupportedByPlatform = true; boolean mBluetoothEnabled = false; /** * During a call that has LE Audio -> HFP handover, the HFP device that is going to connect SCO * after LE Audio group becomes idle */ BluetoothDevice mHfpHandoverDevice = null; /** LE audio active device that was removed from active because of HFP handover */ BluetoothDevice mLeAudioDeviceInactivatedForHfpHandover = null; LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null; private DialingOutTimeoutEvent mDialingOutTimeoutEvent = null; @VisibleForTesting Loading Loading @@ -554,6 +563,7 @@ public class LeAudioService extends ProfileService { mLeAudioNativeIsInitialized = false; mBluetoothEnabled = false; mHfpHandoverDevice = null; mLeAudioDeviceInactivatedForHfpHandover = null; mActiveAudioOutDevice = null; mActiveAudioInDevice = null; Loading Loading @@ -3603,7 +3613,11 @@ public class LeAudioService extends ProfileService { } /** * Set Inactive by HFP during handover * Set Inactive by HFP during handover This is a work around to handle controllers that cannot * have SCO and CIS at the same time. So remove active device to tear down CIS, and re-connect * the SCO in {@link LeAudioService#handleGroupIdleDuringCall()} * * @param hfpHandoverDevice is the hfp device that was set to active */ public void setInactiveForHfpHandover(BluetoothDevice hfpHandoverDevice) { if (!mLeAudioNativeIsInitialized) { Loading @@ -3612,10 +3626,29 @@ public class LeAudioService extends ProfileService { } if (getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID) { mHfpHandoverDevice = hfpHandoverDevice; if (Flags.leaudioResumeActiveAfterHfpHandover()) { // record the lead device mLeAudioDeviceInactivatedForHfpHandover = mExposedActiveDevice; } removeActiveDevice(true); } } /** Resume prior active device after HFP phone call hand over */ public void setActiveAfterHfpHandover() { if (!mLeAudioNativeIsInitialized) { Log.e(TAG, "Le Audio not initialized properly."); return; } if (mLeAudioDeviceInactivatedForHfpHandover != null) { Log.i(TAG, "handover to LE audio device=" + mLeAudioDeviceInactivatedForHfpHandover); setActiveDevice(mLeAudioDeviceInactivatedForHfpHandover); mLeAudioDeviceInactivatedForHfpHandover = null; } else { Log.d(TAG, "nothing to hand over back"); } } /** * Set connection policy of the profile and connects it if connectionPolicy is * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is Loading Loading @@ -4880,10 +4913,16 @@ public class LeAudioService extends ProfileService { ProfileService.println(sb, " mActiveAudioInDevice: " + mActiveAudioInDevice); ProfileService.println(sb, " mUnicastGroupIdDeactivatedForBroadcastTransition: " + mUnicastGroupIdDeactivatedForBroadcastTransition); ProfileService.println(sb, " mBroadcastIdDeactivatedForUnicastTransition: " ProfileService.println( sb, " mBroadcastIdDeactivatedForUnicastTransition: " + mBroadcastIdDeactivatedForUnicastTransition); ProfileService.println(sb, " mExposedActiveDevice: " + mExposedActiveDevice); ProfileService.println(sb, " mHfpHandoverDevice:" + mHfpHandoverDevice); ProfileService.println( sb, " mLeAudioDeviceInactivatedForHfpHandover:" + mLeAudioDeviceInactivatedForHfpHandover); ProfileService.println(sb, " mLeAudioIsInbandRingtoneSupported:" + mLeAudioInbandRingtoneSupportedByPlatform); Loading
android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -60,9 +60,11 @@ import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.RemoteDevices; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.SilenceDeviceManager; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.flags.Flags; import com.android.bluetooth.le_audio.LeAudioService; import org.hamcrest.Matchers; import org.junit.After; Loading Loading @@ -123,6 +125,10 @@ public class HeadsetServiceAndStateMachineTest { @Mock private HeadsetNativeInterface mNativeInterface; @Mock private LeAudioService mLeAudioService; @Mock private ServiceFactory mServiceFactory; private class HeadsetIntentReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading Loading @@ -1596,6 +1602,41 @@ public class HeadsetServiceAndStateMachineTest { configureHeadsetServiceForAptxVoice(false); } @Test public void testHfpHandoverToLeAudioAfterScoDisconnect() { mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_RESUME_ACTIVE_AFTER_HFP_HANDOVER); Assert.assertNotNull(mHeadsetService.mFactory); doReturn(mLeAudioService).when(mServiceFactory).getLeAudioService(); mHeadsetService.mFactory = mServiceFactory; doReturn(true).when(mSystemInterface).isCallIdle(); // 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)); mHeadsetService.messageFromNative( new HeadsetStackEvent( HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, HeadsetHalConstants.AUDIO_STATE_CONNECTED, device)); TestUtils.waitForLooperToFinishScheduledTask( mHeadsetService.getStateMachinesThreadLooper()); // Audio disconnected mHeadsetService.messageFromNative( new HeadsetStackEvent( HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, device)); verify(mLeAudioService, timeout(1000).atLeastOnce()).setActiveAfterHfpHandover(); } private void startVoiceRecognitionFromHf(BluetoothDevice device) { // Start voice recognition HeadsetStackEvent startVrEvent = Loading