Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +97 −7 Original line number Diff line number Diff line Loading @@ -147,6 +147,9 @@ public class LeAudioService extends ProfileService { .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000) .build(); /* 5 seconds timeout for Broadcast streaming state transition */ private static final int DIALING_OUT_TIMEOUT_MS = 5000; private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; Loading @@ -167,6 +170,7 @@ public class LeAudioService extends ProfileService { boolean mBluetoothEnabled = false; BluetoothDevice mHfpHandoverDevice = null; LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null; private DialingOutTimeoutEvent mDialingOutTimeoutEvent = null; @VisibleForTesting AudioManager mAudioManager; LeAudioTmapGattServer mTmapGattServer; Loading Loading @@ -428,6 +432,8 @@ public class LeAudioService extends ProfileService { mAwaitingBroadcastCreateResponse = false; mIsSourceStreamMonitorModeEnabled = false; clearBroadcastTimeoutCallback(); mHandler.removeCallbacks(this::init); removeActiveDevice(false); Loading Loading @@ -1084,6 +1090,11 @@ public class LeAudioService extends ProfileService { return; } if (DBG) Log.d(TAG, "startBroadcast"); /* Start timeout to recover from stucked/error start Broadcast operation */ mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(); mHandler.postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS); mLeAudioBroadcasterNativeInterface.startBroadcast(broadcastId); } Loading Loading @@ -1750,12 +1761,19 @@ public class LeAudioService extends ProfileService { * @param newDevice new supported broadcast audio device * @param previousDevice previous no longer supported broadcast audio device */ /* TODO implement unicast overlap with connected unicast device */ private void updateBroadcastActiveDevice( BluetoothDevice newDevice, BluetoothDevice previousDevice, boolean suppressNoisyIntent) { mActiveBroadcastAudioDevice = newDevice; if (DBG) { Log.d( TAG, "updateBroadcastActiveDevice: newDevice: " + newDevice + ", previousDevice: " + previousDevice); } mAudioManager.handleBluetoothActiveDeviceChanged( newDevice, previousDevice, getBroadcastProfile(suppressNoisyIntent)); } Loading Loading @@ -2272,6 +2290,16 @@ public class LeAudioService extends ProfileService { || mBroadcastIdDeactivatedForUnicastTransition.isPresent())) { leaveConnectedInputDevice = true; newDirections |= AUDIO_DIRECTION_INPUT_BIT; /* Update Broadcast device before streaming state in handover case to avoid switch * to non LE Audio device in Audio Manager e.g. Phone Speaker. */ BluetoothDevice device = mAdapterService.getDeviceFromByte( Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF")); if (!device.equals(mActiveBroadcastAudioDevice)) { updateBroadcastActiveDevice(device, mActiveBroadcastAudioDevice, true); } } descriptor.mIsActive = false; Loading Loading @@ -2558,8 +2586,6 @@ public class LeAudioService extends ProfileService { updateFallbackUnicastGroupIdForBroadcast(LE_AUDIO_GROUP_ID_INVALID); updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); return; } else { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true); } if (DBG) { Loading @@ -2573,6 +2599,21 @@ public class LeAudioService extends ProfileService { setActiveDevice(unicastDevice); } void clearBroadcastTimeoutCallback() { if (mHandler == null) { Log.e(TAG, "No callback handler"); return; } /* Timeout callback already cleared */ if (mDialingOutTimeoutEvent == null) { return; } mHandler.removeCallbacks(mDialingOutTimeoutEvent); mDialingOutTimeoutEvent = null; } // Suppressed since this is part of a local process @SuppressLint("AndroidFrameworkRequiresPermission") void messageFromNative(LeAudioStackEvent stackEvent) { Loading Loading @@ -2786,6 +2827,11 @@ public class LeAudioService extends ProfileService { switch (groupStatus) { case LeAudioStackEvent.GROUP_STATUS_ACTIVE: { handleGroupTransitToActive(groupId); /* Clear possible exposed broadcast device after activating unicast */ if (mActiveBroadcastAudioDevice != null) { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true); } break; } case LeAudioStackEvent.GROUP_STATUS_INACTIVE: { Loading Loading @@ -2831,6 +2877,20 @@ public class LeAudioService extends ProfileService { } else { // TODO: Improve reason reporting or extend the native stack event with reason code Log.e( TAG, "EVENT_TYPE_BROADCAST_CREATED: Failed to create broadcast: " + broadcastId); /* Disconnect Broadcast device which was connected to avoid non LE Audio sound * leak in handover scenario. */ if ((mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) && mCreateBroadcastQueue.isEmpty() && (!Objects.equals(device, mActiveBroadcastAudioDevice))) { clearBroadcastTimeoutCallback(); updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); } notifyBroadcastStartFailed(broadcastId, BluetoothStatusCodes.ERROR_UNKNOWN); } Loading Loading @@ -2926,11 +2986,21 @@ public class LeAudioService extends ProfileService { bassClientService.suspendReceiversSourceSynchronization(broadcastId); } // Notify audio manager updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true); /* Restore the Unicast stream from before the Broadcast was started. */ if (mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { transitionFromBroadcastToUnicast(); } else { // Notify audio manager if (mBroadcastDescriptors.values().stream() .noneMatch( d -> d.mState.equals( LeAudioStackEvent .BROADCAST_STATE_STREAMING))) { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); } } break; case LeAudioStackEvent.BROADCAST_STATE_STOPPING: if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " stopping."); Loading @@ -2942,6 +3012,8 @@ public class LeAudioService extends ProfileService { notifyPlaybackStarted(broadcastId, BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST); clearBroadcastTimeoutCallback(); if (previousState == LeAudioStackEvent.BROADCAST_STATE_PAUSED) { if (bassClientService != null) { bassClientService.resumeReceiversSourceSynchronization(); Loading Loading @@ -4250,6 +4322,24 @@ public class LeAudioService extends ProfileService { return audioFrameworkCalls; } class DialingOutTimeoutEvent implements Runnable { @Override public void run() { Log.w(TAG, "Failed to start Broadcast in time"); mDialingOutTimeoutEvent = null; if (getLeAudioService() == null) { Log.e(TAG, "DialingOutTimeoutEvent: No LE Audio service"); return; } if (mActiveBroadcastAudioDevice != null) { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); } } } /** * 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 +75 −81 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.media.BluetoothProfileConnectionInfo; import android.os.Looper; import android.os.ParcelUuid; import android.platform.test.flag.junit.SetFlagsRule; Loading @@ -37,6 +38,7 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.Utils; import com.android.bluetooth.bass_client.BassClientService; import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; Loading @@ -51,6 +53,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; Loading @@ -67,6 +70,8 @@ public class LeAudioBroadcastServiceTest { private BluetoothAdapter mAdapter; private BluetoothDevice mDevice; private BluetoothDevice mBroadcastDevice; private Context mTargetContext; private LeAudioService mService; private LeAudioIntentReceiver mLeAudioIntentReceiver; Loading Loading @@ -228,7 +233,9 @@ public class LeAudioBroadcastServiceTest { mTargetContext.registerReceiver(mLeAudioIntentReceiver, filter); mDevice = TestUtils.getTestDevice(mAdapter, 0); when(mLeAudioBroadcasterNativeInterface.getDevice(any(byte[].class))).thenReturn(mDevice); mBroadcastDevice = TestUtils.getTestDevice(mAdapter, 1); when(mAdapterService.getDeviceFromByte(Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF"))) .thenReturn(mBroadcastDevice); mIntentQueue = new LinkedBlockingQueue<Intent>(); } Loading Loading @@ -794,12 +801,7 @@ public class LeAudioBroadcastServiceTest { mOnBroadcastStartFailedReason); } @Test public void testInCallDrivenBroadcastSwitch() { int groupId = 1; int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; private void prepareHandoverStreamingBroadcast(int groupId, int broadcastId, byte[] code) { mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION); mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES); Loading @@ -813,6 +815,14 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); /* Verify Unicast input and output devices changed from null to mDevice */ verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Mockito.clearInvocations(mAudioManager); mService.notifyActiveDeviceChanged(mDevice); /* Prepare create broadcast */ BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); Loading @@ -823,6 +833,10 @@ public class LeAudioBroadcastServiceTest { BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1); mService.createBroadcast(settings); verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); /* Active group should become inactive */ int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, LE_AUDIO_GROUP_ID_INVALID); Loading @@ -833,6 +847,11 @@ public class LeAudioBroadcastServiceTest { 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)); Mockito.clearInvocations(mAudioManager); List<BluetoothLeBroadcastSubgroupSettings> settingsList = settings.getSubgroupSettings(); int[] expectedQualityArray = Loading @@ -850,6 +869,8 @@ public class LeAudioBroadcastServiceTest { eq(settings.getPublicBroadcastMetadata().getRawMetadata()), eq(expectedQualityArray), eq(expectedDataArray)); verify(mLeAudioNativeInterface, times(1)) .setUnicastMonitorMode(eq(LeAudioStackEvent.DIRECTION_SINK), eq(true)); activeGroup = mService.getActiveGroupId(); Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup); Loading @@ -865,9 +886,19 @@ public class LeAudioBroadcastServiceTest { /* Switch to active streaming */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); create_event.device = mBroadcastDevice; create_event.valueInt1 = broadcastId; create_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STREAMING; mService.messageFromNative(create_event); } @Test public void testInCallDrivenBroadcastSwitch() { int groupId = 1; int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; prepareHandoverStreamingBroadcast(groupId, broadcastId, code); /* Imitate setting device in call */ mService.setInCall(true); Loading @@ -883,13 +914,21 @@ public class LeAudioBroadcastServiceTest { verify(mLeAudioNativeInterface, times(1)).setInCall(eq(true)); create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); 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 */ activeGroup = mService.getActiveGroupId(); int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, groupId); /* Imitate setting device not in call */ Loading @@ -903,6 +942,14 @@ public class LeAudioBroadcastServiceTest { 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)); } Loading @@ -913,82 +960,14 @@ public class LeAudioBroadcastServiceTest { int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION); mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES); mService.mBroadcastCallbacks.register(mCallbacks); prepareConnectedUnicastDevice(groupId); 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); /* Prepare create broadcast */ BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); meta_builder.setLanguage("ENG"); meta_builder.setProgramInfo("Public broadcast info"); BluetoothLeAudioContentMetadata meta = meta_builder.build(); BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1); mService.createBroadcast(settings); /* Active group should become inactive */ int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, LE_AUDIO_GROUP_ID_INVALID); /* Imitate group inactivity to cause create 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); List<BluetoothLeBroadcastSubgroupSettings> settingsList = settings.getSubgroupSettings(); int[] expectedQualityArray = settingsList.stream().mapToInt(setting -> setting.getPreferredQuality()).toArray(); byte[][] expectedDataArray = settingsList.stream() .map(setting -> setting.getContentMetadata().getRawMetadata()) .toArray(byte[][]::new); verify(mLeAudioBroadcasterNativeInterface, times(1)) .createBroadcast( eq(true), eq(TEST_BROADCAST_NAME), eq(settings.getBroadcastCode()), eq(settings.getPublicBroadcastMetadata().getRawMetadata()), eq(expectedQualityArray), eq(expectedDataArray)); verify(mLeAudioNativeInterface, times(1)) .setUnicastMonitorMode(eq(LeAudioStackEvent.DIRECTION_SINK),eq(true)); activeGroup = mService.getActiveGroupId(); Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup); /* Check if broadcast is started automatically when created */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); create_event.valueInt1 = broadcastId; create_event.valueBool1 = true; mService.messageFromNative(create_event); /* Verify if broadcast is auto-started on start */ verify(mLeAudioBroadcasterNativeInterface, times(1)).startBroadcast(eq(broadcastId)); /* Switch to active streaming */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); create_event.valueInt1 = broadcastId; create_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STREAMING; mService.messageFromNative(create_event); prepareHandoverStreamingBroadcast(groupId, broadcastId, code); /* Verify if broadcast is auto-started on start */ verify(mLeAudioBroadcasterNativeInterface, times(1)).startBroadcast(eq(broadcastId)); /* Imitate group change request by Bluetooth Sink HAL resume request */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS); LeAudioStackEvent create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS); create_event.valueInt1 = LeAudioStackEvent.DIRECTION_SINK; create_event.valueInt2 = LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED; mService.messageFromNative(create_event); Loading @@ -1007,8 +986,15 @@ public class LeAudioBroadcastServiceTest { 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 */ activeGroup = mService.getActiveGroupId(); int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, groupId); /* Imitate group change request by Bluetooth Sink HAL suspend request */ Loading @@ -1025,6 +1011,14 @@ public class LeAudioBroadcastServiceTest { 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)); } Loading Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +97 −7 Original line number Diff line number Diff line Loading @@ -147,6 +147,9 @@ public class LeAudioService extends ProfileService { .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000) .build(); /* 5 seconds timeout for Broadcast streaming state transition */ private static final int DIALING_OUT_TIMEOUT_MS = 5000; private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private HandlerThread mStateMachinesThread; Loading @@ -167,6 +170,7 @@ public class LeAudioService extends ProfileService { boolean mBluetoothEnabled = false; BluetoothDevice mHfpHandoverDevice = null; LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null; private DialingOutTimeoutEvent mDialingOutTimeoutEvent = null; @VisibleForTesting AudioManager mAudioManager; LeAudioTmapGattServer mTmapGattServer; Loading Loading @@ -428,6 +432,8 @@ public class LeAudioService extends ProfileService { mAwaitingBroadcastCreateResponse = false; mIsSourceStreamMonitorModeEnabled = false; clearBroadcastTimeoutCallback(); mHandler.removeCallbacks(this::init); removeActiveDevice(false); Loading Loading @@ -1084,6 +1090,11 @@ public class LeAudioService extends ProfileService { return; } if (DBG) Log.d(TAG, "startBroadcast"); /* Start timeout to recover from stucked/error start Broadcast operation */ mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(); mHandler.postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS); mLeAudioBroadcasterNativeInterface.startBroadcast(broadcastId); } Loading Loading @@ -1750,12 +1761,19 @@ public class LeAudioService extends ProfileService { * @param newDevice new supported broadcast audio device * @param previousDevice previous no longer supported broadcast audio device */ /* TODO implement unicast overlap with connected unicast device */ private void updateBroadcastActiveDevice( BluetoothDevice newDevice, BluetoothDevice previousDevice, boolean suppressNoisyIntent) { mActiveBroadcastAudioDevice = newDevice; if (DBG) { Log.d( TAG, "updateBroadcastActiveDevice: newDevice: " + newDevice + ", previousDevice: " + previousDevice); } mAudioManager.handleBluetoothActiveDeviceChanged( newDevice, previousDevice, getBroadcastProfile(suppressNoisyIntent)); } Loading Loading @@ -2272,6 +2290,16 @@ public class LeAudioService extends ProfileService { || mBroadcastIdDeactivatedForUnicastTransition.isPresent())) { leaveConnectedInputDevice = true; newDirections |= AUDIO_DIRECTION_INPUT_BIT; /* Update Broadcast device before streaming state in handover case to avoid switch * to non LE Audio device in Audio Manager e.g. Phone Speaker. */ BluetoothDevice device = mAdapterService.getDeviceFromByte( Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF")); if (!device.equals(mActiveBroadcastAudioDevice)) { updateBroadcastActiveDevice(device, mActiveBroadcastAudioDevice, true); } } descriptor.mIsActive = false; Loading Loading @@ -2558,8 +2586,6 @@ public class LeAudioService extends ProfileService { updateFallbackUnicastGroupIdForBroadcast(LE_AUDIO_GROUP_ID_INVALID); updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); return; } else { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true); } if (DBG) { Loading @@ -2573,6 +2599,21 @@ public class LeAudioService extends ProfileService { setActiveDevice(unicastDevice); } void clearBroadcastTimeoutCallback() { if (mHandler == null) { Log.e(TAG, "No callback handler"); return; } /* Timeout callback already cleared */ if (mDialingOutTimeoutEvent == null) { return; } mHandler.removeCallbacks(mDialingOutTimeoutEvent); mDialingOutTimeoutEvent = null; } // Suppressed since this is part of a local process @SuppressLint("AndroidFrameworkRequiresPermission") void messageFromNative(LeAudioStackEvent stackEvent) { Loading Loading @@ -2786,6 +2827,11 @@ public class LeAudioService extends ProfileService { switch (groupStatus) { case LeAudioStackEvent.GROUP_STATUS_ACTIVE: { handleGroupTransitToActive(groupId); /* Clear possible exposed broadcast device after activating unicast */ if (mActiveBroadcastAudioDevice != null) { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true); } break; } case LeAudioStackEvent.GROUP_STATUS_INACTIVE: { Loading Loading @@ -2831,6 +2877,20 @@ public class LeAudioService extends ProfileService { } else { // TODO: Improve reason reporting or extend the native stack event with reason code Log.e( TAG, "EVENT_TYPE_BROADCAST_CREATED: Failed to create broadcast: " + broadcastId); /* Disconnect Broadcast device which was connected to avoid non LE Audio sound * leak in handover scenario. */ if ((mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) && mCreateBroadcastQueue.isEmpty() && (!Objects.equals(device, mActiveBroadcastAudioDevice))) { clearBroadcastTimeoutCallback(); updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); } notifyBroadcastStartFailed(broadcastId, BluetoothStatusCodes.ERROR_UNKNOWN); } Loading Loading @@ -2926,11 +2986,21 @@ public class LeAudioService extends ProfileService { bassClientService.suspendReceiversSourceSynchronization(broadcastId); } // Notify audio manager updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true); /* Restore the Unicast stream from before the Broadcast was started. */ if (mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { transitionFromBroadcastToUnicast(); } else { // Notify audio manager if (mBroadcastDescriptors.values().stream() .noneMatch( d -> d.mState.equals( LeAudioStackEvent .BROADCAST_STATE_STREAMING))) { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); } } break; case LeAudioStackEvent.BROADCAST_STATE_STOPPING: if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " stopping."); Loading @@ -2942,6 +3012,8 @@ public class LeAudioService extends ProfileService { notifyPlaybackStarted(broadcastId, BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST); clearBroadcastTimeoutCallback(); if (previousState == LeAudioStackEvent.BROADCAST_STATE_PAUSED) { if (bassClientService != null) { bassClientService.resumeReceiversSourceSynchronization(); Loading Loading @@ -4250,6 +4322,24 @@ public class LeAudioService extends ProfileService { return audioFrameworkCalls; } class DialingOutTimeoutEvent implements Runnable { @Override public void run() { Log.w(TAG, "Failed to start Broadcast in time"); mDialingOutTimeoutEvent = null; if (getLeAudioService() == null) { Log.e(TAG, "DialingOutTimeoutEvent: No LE Audio service"); return; } if (mActiveBroadcastAudioDevice != null) { updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false); } } } /** * 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 +75 −81 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.media.BluetoothProfileConnectionInfo; import android.os.Looper; import android.os.ParcelUuid; import android.platform.test.flag.junit.SetFlagsRule; Loading @@ -37,6 +38,7 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.Utils; import com.android.bluetooth.bass_client.BassClientService; import com.android.bluetooth.btservice.ActiveDeviceManager; import com.android.bluetooth.btservice.AdapterService; Loading @@ -51,6 +53,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; Loading @@ -67,6 +70,8 @@ public class LeAudioBroadcastServiceTest { private BluetoothAdapter mAdapter; private BluetoothDevice mDevice; private BluetoothDevice mBroadcastDevice; private Context mTargetContext; private LeAudioService mService; private LeAudioIntentReceiver mLeAudioIntentReceiver; Loading Loading @@ -228,7 +233,9 @@ public class LeAudioBroadcastServiceTest { mTargetContext.registerReceiver(mLeAudioIntentReceiver, filter); mDevice = TestUtils.getTestDevice(mAdapter, 0); when(mLeAudioBroadcasterNativeInterface.getDevice(any(byte[].class))).thenReturn(mDevice); mBroadcastDevice = TestUtils.getTestDevice(mAdapter, 1); when(mAdapterService.getDeviceFromByte(Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF"))) .thenReturn(mBroadcastDevice); mIntentQueue = new LinkedBlockingQueue<Intent>(); } Loading Loading @@ -794,12 +801,7 @@ public class LeAudioBroadcastServiceTest { mOnBroadcastStartFailedReason); } @Test public void testInCallDrivenBroadcastSwitch() { int groupId = 1; int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; private void prepareHandoverStreamingBroadcast(int groupId, int broadcastId, byte[] code) { mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION); mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES); Loading @@ -813,6 +815,14 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); /* Verify Unicast input and output devices changed from null to mDevice */ verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Mockito.clearInvocations(mAudioManager); mService.notifyActiveDeviceChanged(mDevice); /* Prepare create broadcast */ BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); Loading @@ -823,6 +833,10 @@ public class LeAudioBroadcastServiceTest { BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1); mService.createBroadcast(settings); verify(mAudioManager, times(1)) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); /* Active group should become inactive */ int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, LE_AUDIO_GROUP_ID_INVALID); Loading @@ -833,6 +847,11 @@ public class LeAudioBroadcastServiceTest { 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)); Mockito.clearInvocations(mAudioManager); List<BluetoothLeBroadcastSubgroupSettings> settingsList = settings.getSubgroupSettings(); int[] expectedQualityArray = Loading @@ -850,6 +869,8 @@ public class LeAudioBroadcastServiceTest { eq(settings.getPublicBroadcastMetadata().getRawMetadata()), eq(expectedQualityArray), eq(expectedDataArray)); verify(mLeAudioNativeInterface, times(1)) .setUnicastMonitorMode(eq(LeAudioStackEvent.DIRECTION_SINK), eq(true)); activeGroup = mService.getActiveGroupId(); Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup); Loading @@ -865,9 +886,19 @@ public class LeAudioBroadcastServiceTest { /* Switch to active streaming */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); create_event.device = mBroadcastDevice; create_event.valueInt1 = broadcastId; create_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STREAMING; mService.messageFromNative(create_event); } @Test public void testInCallDrivenBroadcastSwitch() { int groupId = 1; int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; prepareHandoverStreamingBroadcast(groupId, broadcastId, code); /* Imitate setting device in call */ mService.setInCall(true); Loading @@ -883,13 +914,21 @@ public class LeAudioBroadcastServiceTest { verify(mLeAudioNativeInterface, times(1)).setInCall(eq(true)); create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); 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 */ activeGroup = mService.getActiveGroupId(); int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, groupId); /* Imitate setting device not in call */ Loading @@ -903,6 +942,14 @@ public class LeAudioBroadcastServiceTest { 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)); } Loading @@ -913,82 +960,14 @@ public class LeAudioBroadcastServiceTest { int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION); mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES); mService.mBroadcastCallbacks.register(mCallbacks); prepareConnectedUnicastDevice(groupId); 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); /* Prepare create broadcast */ BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); meta_builder.setLanguage("ENG"); meta_builder.setProgramInfo("Public broadcast info"); BluetoothLeAudioContentMetadata meta = meta_builder.build(); BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1); mService.createBroadcast(settings); /* Active group should become inactive */ int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, LE_AUDIO_GROUP_ID_INVALID); /* Imitate group inactivity to cause create 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); List<BluetoothLeBroadcastSubgroupSettings> settingsList = settings.getSubgroupSettings(); int[] expectedQualityArray = settingsList.stream().mapToInt(setting -> setting.getPreferredQuality()).toArray(); byte[][] expectedDataArray = settingsList.stream() .map(setting -> setting.getContentMetadata().getRawMetadata()) .toArray(byte[][]::new); verify(mLeAudioBroadcasterNativeInterface, times(1)) .createBroadcast( eq(true), eq(TEST_BROADCAST_NAME), eq(settings.getBroadcastCode()), eq(settings.getPublicBroadcastMetadata().getRawMetadata()), eq(expectedQualityArray), eq(expectedDataArray)); verify(mLeAudioNativeInterface, times(1)) .setUnicastMonitorMode(eq(LeAudioStackEvent.DIRECTION_SINK),eq(true)); activeGroup = mService.getActiveGroupId(); Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup); /* Check if broadcast is started automatically when created */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); create_event.valueInt1 = broadcastId; create_event.valueBool1 = true; mService.messageFromNative(create_event); /* Verify if broadcast is auto-started on start */ verify(mLeAudioBroadcasterNativeInterface, times(1)).startBroadcast(eq(broadcastId)); /* Switch to active streaming */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); create_event.valueInt1 = broadcastId; create_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STREAMING; mService.messageFromNative(create_event); prepareHandoverStreamingBroadcast(groupId, broadcastId, code); /* Verify if broadcast is auto-started on start */ verify(mLeAudioBroadcasterNativeInterface, times(1)).startBroadcast(eq(broadcastId)); /* Imitate group change request by Bluetooth Sink HAL resume request */ create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS); LeAudioStackEvent create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS); create_event.valueInt1 = LeAudioStackEvent.DIRECTION_SINK; create_event.valueInt2 = LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED; mService.messageFromNative(create_event); Loading @@ -1007,8 +986,15 @@ public class LeAudioBroadcastServiceTest { 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 */ activeGroup = mService.getActiveGroupId(); int activeGroup = mService.getActiveGroupId(); Assert.assertEquals(activeGroup, groupId); /* Imitate group change request by Bluetooth Sink HAL suspend request */ Loading @@ -1025,6 +1011,14 @@ public class LeAudioBroadcastServiceTest { 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)); } Loading