Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +82 −20 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import static com.android.bluetooth.flags.Flags.leaudioBroadcastFeatureSupport; import static com.android.bluetooth.flags.Flags.leaudioApiSynchronizedBlockFix; import static com.android.bluetooth.flags.Flags.leaudioAllowedContextMask; import static com.android.bluetooth.flags.Flags.leaudioUseAudioModeListener; import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; import static com.android.modules.utils.build.SdkLevel.isAtLeastU; Loading Loading @@ -175,6 +176,7 @@ public class LeAudioService extends ProfileService { leaudioApiSynchronizedBlockFix() ? mGroupReadWriteLock.readLock() : mGroupLock; private final Lock mGroupWriteLock = leaudioApiSynchronizedBlockFix() ? mGroupReadWriteLock.writeLock() : mGroupLock; private final Context mContext; ServiceFactory mServiceFactory = new ServiceFactory(); LeAudioNativeInterface mLeAudioNativeInterface; Loading @@ -198,6 +200,7 @@ public class LeAudioService extends ProfileService { LeAudioTmapGattServer mTmapGattServer; int mTmapRoleMask; int mUnicastGroupIdDeactivatedForBroadcastTransition = LE_AUDIO_GROUP_ID_INVALID; int mCurrentAudioMode = AudioManager.MODE_INVALID; Optional<Integer> mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); Optional<Boolean> mQueuedInCallValue = Optional.empty(); boolean mTmapStarted = false; Loading Loading @@ -235,12 +238,14 @@ public class LeAudioService extends ProfileService { public LeAudioService(Context ctx) { super(ctx); mContext = ctx; } @VisibleForTesting LeAudioService(Context ctx, LeAudioNativeInterface nativeInterface) { super(ctx); mLeAudioNativeInterface = nativeInterface; mContext = ctx; } private class LeAudioGroupDescriptor { Loading Loading @@ -393,6 +398,7 @@ public class LeAudioService extends ProfileService { private Handler mHandler = new Handler(Looper.getMainLooper()); private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); private final AudioModeChangeListener mAudioModeChangeListener = new AudioModeChangeListener(); @Override protected IProfileServiceBinder initBinder() { Loading Loading @@ -516,6 +522,11 @@ public class LeAudioService extends ProfileService { return; } nativeInterface.init(mLeAudioCodecConfig.getCodecConfigOffloading()); if (leaudioUseAudioModeListener()) { mAudioManager.addOnModeChangedListener( mContext.getMainExecutor(), mAudioModeChangeListener); } } @Override Loading @@ -527,6 +538,10 @@ public class LeAudioService extends ProfileService { } mQueuedInCallValue = Optional.empty(); if (leaudioUseAudioModeListener()) { mAudioManager.removeOnModeChangedListener(mAudioModeChangeListener); } mCreateBroadcastQueue.clear(); mAwaitingBroadcastCreateResponse = false; mIsSourceStreamMonitorModeEnabled = false; Loading Loading @@ -2776,10 +2791,12 @@ public class LeAudioService extends ProfileService { return; } if (!leaudioUseAudioModeListener()) { if (mQueuedInCallValue.isPresent()) { mLeAudioNativeInterface.setInCall(mQueuedInCallValue.get()); mQueuedInCallValue = Optional.empty(); } } BluetoothDevice unicastDevice = getLeadDeviceForTheGroup(mUnicastGroupIdDeactivatedForBroadcastTransition); Loading Loading @@ -3165,7 +3182,9 @@ public class LeAudioService extends ProfileService { /* Check if broadcast was deactivated due to unicast */ if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { updateFallbackUnicastGroupIdForBroadcast(groupId); if (!leaudioUseAudioModeListener()) { mQueuedInCallValue = Optional.empty(); } startBroadcast(mBroadcastIdDeactivatedForUnicastTransition.get()); mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); } Loading Loading @@ -3805,8 +3824,11 @@ public class LeAudioService extends ProfileService { return; } if (!leaudioUseAudioModeListener()) { /* For setting inCall mode */ if (Flags.leaudioBroadcastAudioHandoverPolicies() && inCall && !areBroadcastsAllStopped()) { if (Flags.leaudioBroadcastAudioHandoverPolicies() && inCall && !areBroadcastsAllStopped()) { mQueuedInCallValue = Optional.of(true); /* Request activation of unicast group */ Loading @@ -3815,9 +3837,11 @@ public class LeAudioService extends ProfileService { LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED); return; } } mLeAudioNativeInterface.setInCall(inCall); if (!leaudioUseAudioModeListener()) { /* For clearing inCall mode */ if (Flags.leaudioBroadcastAudioHandoverPolicies() && !inCall Loading @@ -3827,6 +3851,7 @@ public class LeAudioService extends ProfileService { LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED); } } } /** * Sends the preferred audio profiles for a dual mode audio device to the native stack. Loading Loading @@ -4185,6 +4210,36 @@ public class LeAudioService extends ProfileService { startAudioServersBackgroundScan(/* retry = */ false); } @VisibleForTesting void handleAudioModeChange(int mode) { Log.d(TAG, "Audio mode changed: " + mCurrentAudioMode + " -> " + mode); mCurrentAudioMode = mode; switch (mode) { case AudioManager.MODE_RINGTONE: case AudioManager.MODE_IN_CALL: case AudioManager.MODE_IN_COMMUNICATION: if (!areBroadcastsAllStopped()) { /* Request activation of unicast group */ handleUnicastStreamStatusChange( LeAudioStackEvent.DIRECTION_SINK, LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED); } break; case AudioManager.MODE_NORMAL: if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { handleUnicastStreamStatusChange( LeAudioStackEvent.DIRECTION_SINK, LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED); } break; default: Log.d(TAG, "Not handled audio mode set: " + mode); break; } } private LeAudioGroupDescriptor getGroupDescriptor(int groupId) { mGroupReadLock.lock(); try { Loading Loading @@ -4717,6 +4772,13 @@ public class LeAudioService extends ProfileService { } } class AudioModeChangeListener implements AudioManager.OnModeChangedListener { @Override public void onModeChanged(int mode) { handleAudioModeChange(mode); } } /** * Binder object: must be a static class or memory leak may occur */ Loading android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +62 −0 Original line number Diff line number Diff line Loading @@ -1033,6 +1033,68 @@ public class LeAudioBroadcastServiceTest { verify(mLeAudioBroadcasterNativeInterface, times(2)).startBroadcast(eq(broadcastId)); } @Test public void testAudioModeDrivenBroadcastSwitch() { mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_USE_AUDIO_MODE_LISTENER); int groupId = 1; int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; prepareHandoverStreamingBroadcast(groupId, broadcastId, code); /* Imitate setting device in call */ mService.handleAudioModeChange(AudioManager.MODE_IN_CALL); /* Check if broadcast is paused by AudioMode handling */ verify(mLeAudioBroadcasterNativeInterface, times(1)).pauseBroadcast(eq(broadcastId)); LeAudioStackEvent state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); state_event.valueInt1 = broadcastId; state_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_PAUSED; mService.messageFromNative(state_event); LeAudioStackEvent create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); create_event.valueInt1 = groupId; create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); /* Active group should become the one that was active before broadcasting */ int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, groupId); /* Imitate setting device not in call */ mService.handleAudioModeChange(AudioManager.MODE_NORMAL); verify(mLeAudioNativeInterface, times(2)).groupSetActive(eq(LE_AUDIO_GROUP_ID_INVALID)); /* Imitate group inactivity to cause start broadcast */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); create_event.valueInt1 = groupId; create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_INACTIVE; mService.messageFromNative(create_event); /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); /* Verify if broadcast is auto-started on start */ verify(mLeAudioBroadcasterNativeInterface, times(2)).startBroadcast(eq(broadcastId)); } @Test public void testBroadcastResumeUnicastGroupChangeRequestDriven() { int groupId = 1; Loading Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +82 −20 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import static com.android.bluetooth.flags.Flags.leaudioBroadcastFeatureSupport; import static com.android.bluetooth.flags.Flags.leaudioApiSynchronizedBlockFix; import static com.android.bluetooth.flags.Flags.leaudioAllowedContextMask; import static com.android.bluetooth.flags.Flags.leaudioUseAudioModeListener; import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; import static com.android.modules.utils.build.SdkLevel.isAtLeastU; Loading Loading @@ -175,6 +176,7 @@ public class LeAudioService extends ProfileService { leaudioApiSynchronizedBlockFix() ? mGroupReadWriteLock.readLock() : mGroupLock; private final Lock mGroupWriteLock = leaudioApiSynchronizedBlockFix() ? mGroupReadWriteLock.writeLock() : mGroupLock; private final Context mContext; ServiceFactory mServiceFactory = new ServiceFactory(); LeAudioNativeInterface mLeAudioNativeInterface; Loading @@ -198,6 +200,7 @@ public class LeAudioService extends ProfileService { LeAudioTmapGattServer mTmapGattServer; int mTmapRoleMask; int mUnicastGroupIdDeactivatedForBroadcastTransition = LE_AUDIO_GROUP_ID_INVALID; int mCurrentAudioMode = AudioManager.MODE_INVALID; Optional<Integer> mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); Optional<Boolean> mQueuedInCallValue = Optional.empty(); boolean mTmapStarted = false; Loading Loading @@ -235,12 +238,14 @@ public class LeAudioService extends ProfileService { public LeAudioService(Context ctx) { super(ctx); mContext = ctx; } @VisibleForTesting LeAudioService(Context ctx, LeAudioNativeInterface nativeInterface) { super(ctx); mLeAudioNativeInterface = nativeInterface; mContext = ctx; } private class LeAudioGroupDescriptor { Loading Loading @@ -393,6 +398,7 @@ public class LeAudioService extends ProfileService { private Handler mHandler = new Handler(Looper.getMainLooper()); private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); private final AudioModeChangeListener mAudioModeChangeListener = new AudioModeChangeListener(); @Override protected IProfileServiceBinder initBinder() { Loading Loading @@ -516,6 +522,11 @@ public class LeAudioService extends ProfileService { return; } nativeInterface.init(mLeAudioCodecConfig.getCodecConfigOffloading()); if (leaudioUseAudioModeListener()) { mAudioManager.addOnModeChangedListener( mContext.getMainExecutor(), mAudioModeChangeListener); } } @Override Loading @@ -527,6 +538,10 @@ public class LeAudioService extends ProfileService { } mQueuedInCallValue = Optional.empty(); if (leaudioUseAudioModeListener()) { mAudioManager.removeOnModeChangedListener(mAudioModeChangeListener); } mCreateBroadcastQueue.clear(); mAwaitingBroadcastCreateResponse = false; mIsSourceStreamMonitorModeEnabled = false; Loading Loading @@ -2776,10 +2791,12 @@ public class LeAudioService extends ProfileService { return; } if (!leaudioUseAudioModeListener()) { if (mQueuedInCallValue.isPresent()) { mLeAudioNativeInterface.setInCall(mQueuedInCallValue.get()); mQueuedInCallValue = Optional.empty(); } } BluetoothDevice unicastDevice = getLeadDeviceForTheGroup(mUnicastGroupIdDeactivatedForBroadcastTransition); Loading Loading @@ -3165,7 +3182,9 @@ public class LeAudioService extends ProfileService { /* Check if broadcast was deactivated due to unicast */ if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { updateFallbackUnicastGroupIdForBroadcast(groupId); if (!leaudioUseAudioModeListener()) { mQueuedInCallValue = Optional.empty(); } startBroadcast(mBroadcastIdDeactivatedForUnicastTransition.get()); mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); } Loading Loading @@ -3805,8 +3824,11 @@ public class LeAudioService extends ProfileService { return; } if (!leaudioUseAudioModeListener()) { /* For setting inCall mode */ if (Flags.leaudioBroadcastAudioHandoverPolicies() && inCall && !areBroadcastsAllStopped()) { if (Flags.leaudioBroadcastAudioHandoverPolicies() && inCall && !areBroadcastsAllStopped()) { mQueuedInCallValue = Optional.of(true); /* Request activation of unicast group */ Loading @@ -3815,9 +3837,11 @@ public class LeAudioService extends ProfileService { LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED); return; } } mLeAudioNativeInterface.setInCall(inCall); if (!leaudioUseAudioModeListener()) { /* For clearing inCall mode */ if (Flags.leaudioBroadcastAudioHandoverPolicies() && !inCall Loading @@ -3827,6 +3851,7 @@ public class LeAudioService extends ProfileService { LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED); } } } /** * Sends the preferred audio profiles for a dual mode audio device to the native stack. Loading Loading @@ -4185,6 +4210,36 @@ public class LeAudioService extends ProfileService { startAudioServersBackgroundScan(/* retry = */ false); } @VisibleForTesting void handleAudioModeChange(int mode) { Log.d(TAG, "Audio mode changed: " + mCurrentAudioMode + " -> " + mode); mCurrentAudioMode = mode; switch (mode) { case AudioManager.MODE_RINGTONE: case AudioManager.MODE_IN_CALL: case AudioManager.MODE_IN_COMMUNICATION: if (!areBroadcastsAllStopped()) { /* Request activation of unicast group */ handleUnicastStreamStatusChange( LeAudioStackEvent.DIRECTION_SINK, LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED); } break; case AudioManager.MODE_NORMAL: if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { handleUnicastStreamStatusChange( LeAudioStackEvent.DIRECTION_SINK, LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED); } break; default: Log.d(TAG, "Not handled audio mode set: " + mode); break; } } private LeAudioGroupDescriptor getGroupDescriptor(int groupId) { mGroupReadLock.lock(); try { Loading Loading @@ -4717,6 +4772,13 @@ public class LeAudioService extends ProfileService { } } class AudioModeChangeListener implements AudioManager.OnModeChangedListener { @Override public void onModeChanged(int mode) { handleAudioModeChange(mode); } } /** * Binder object: must be a static class or memory leak may occur */ Loading
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +62 −0 Original line number Diff line number Diff line Loading @@ -1033,6 +1033,68 @@ public class LeAudioBroadcastServiceTest { verify(mLeAudioBroadcasterNativeInterface, times(2)).startBroadcast(eq(broadcastId)); } @Test public void testAudioModeDrivenBroadcastSwitch() { mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_USE_AUDIO_MODE_LISTENER); int groupId = 1; int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; prepareHandoverStreamingBroadcast(groupId, broadcastId, code); /* Imitate setting device in call */ mService.handleAudioModeChange(AudioManager.MODE_IN_CALL); /* Check if broadcast is paused by AudioMode handling */ verify(mLeAudioBroadcasterNativeInterface, times(1)).pauseBroadcast(eq(broadcastId)); LeAudioStackEvent state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); state_event.valueInt1 = broadcastId; state_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_PAUSED; mService.messageFromNative(state_event); LeAudioStackEvent create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); create_event.valueInt1 = groupId; create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); /* Active group should become the one that was active before broadcasting */ int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, groupId); /* Imitate setting device not in call */ mService.handleAudioModeChange(AudioManager.MODE_NORMAL); verify(mLeAudioNativeInterface, times(2)).groupSetActive(eq(LE_AUDIO_GROUP_ID_INVALID)); /* Imitate group inactivity to cause start broadcast */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); create_event.valueInt1 = groupId; create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_INACTIVE; mService.messageFromNative(create_event); /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); /* Verify if broadcast is auto-started on start */ verify(mLeAudioBroadcasterNativeInterface, times(2)).startBroadcast(eq(broadcastId)); } @Test public void testBroadcastResumeUnicastGroupChangeRequestDriven() { int groupId = 1; Loading