Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +231 −37 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.AttributionSource; import android.content.Context; import android.content.Intent; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; Loading Loading @@ -89,6 +90,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; /** Loading Loading @@ -143,6 +145,8 @@ public class LeAudioService extends ProfileService { LeAudioTmapGattServer mTmapGattServer; int mTmapRoleMask; int mUnicastGroupIdDeactivatedForBroadcastTransition = LE_AUDIO_GROUP_ID_INVALID; Optional<Integer> mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); Optional<Boolean> mQueuedInCallValue = Optional.empty(); boolean mTmapStarted = false; private boolean mAwaitingBroadcastCreateResponse = false; private final LinkedList<BluetoothLeBroadcastSettings> mCreateBroadcastQueue = Loading Loading @@ -374,6 +378,7 @@ public class LeAudioService extends ProfileService { return true; } mQueuedInCallValue = Optional.empty(); mCreateBroadcastQueue.clear(); mAwaitingBroadcastCreateResponse = false; Loading @@ -400,7 +405,7 @@ public class LeAudioService extends ProfileService { if (descriptor.mIsActive) { descriptor.mIsActive = false; updateActiveDevices(group_id, descriptor.mDirection, AUDIO_DIRECTION_NONE, descriptor.mIsActive, false); descriptor.mIsActive, false, false); break; } } Loading Loading @@ -854,6 +859,10 @@ public class LeAudioService extends ProfileService { Log.i(TAG, "Unicast group is active, queueing Broadcast creation, while the Unicast" + " group is deactivated."); mCreateBroadcastQueue.add(broadcastSettings); if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()) { mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, true); } removeActiveDevice(true); return; Loading Loading @@ -943,6 +952,27 @@ public class LeAudioService extends ProfileService { .toArray(byte[][]::new)); } /** * Pause LeAudio Broadcast instance. * * @param broadcastId broadcast instance identifier */ public void pauseBroadcast(Integer broadcastId) { if (mLeAudioBroadcasterNativeInterface == null) { Log.w(TAG, "Native interface not available."); return; } LeAudioBroadcastDescriptor descriptor = mBroadcastDescriptors.get(broadcastId); if (descriptor == null) { Log.e(TAG, "pauseBroadcast: No valid descriptor for broadcastId: " + broadcastId); return; } if (DBG) Log.d(TAG, "pauseBroadcast"); mLeAudioBroadcasterNativeInterface.pauseBroadcast(broadcastId); } /** * Stop LeAudio Broadcast instance. * @param broadcastId broadcast instance identifier Loading Loading @@ -984,6 +1014,9 @@ public class LeAudioService extends ProfileService { } if (DBG) Log.d(TAG, "destroyBroadcast"); if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()) { mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false); } mLeAudioBroadcasterNativeInterface.destroyBroadcast(broadcastId); } Loading Loading @@ -1041,6 +1074,32 @@ public class LeAudioService extends ProfileService { return 1; } private boolean areBroadcastsAllStopped() { if (mBroadcastDescriptors == null) { Log.e(TAG, "areBroadcastsAllStopped: Invalid Broadcast Descriptors"); return false; } return mBroadcastDescriptors.values().stream() .allMatch(d -> d.mState.equals(LeAudioStackEvent.BROADCAST_STATE_STOPPED)); } private Optional<Integer> getFirstNotStoppedBroadcastId() { if (mBroadcastDescriptors == null) { Log.e(TAG, "getFirstNotStoppedBroadcastId: Invalid Broadcast Descriptors"); return Optional.empty(); } for (Map.Entry<Integer, LeAudioBroadcastDescriptor> entry : mBroadcastDescriptors.entrySet()) { if (!entry.getValue().mState.equals(LeAudioStackEvent.BROADCAST_STATE_STOPPED)) { return Optional.of(entry.getKey()); } } return Optional.empty(); } private BluetoothDevice getLeadDeviceForTheGroup(Integer groupId) { if (groupId == LE_AUDIO_GROUP_ID_INVALID) { return null; Loading Loading @@ -1448,35 +1507,80 @@ public class LeAudioService extends ProfileService { } } /* * Report the active broadcast device change to the active device manager and the media * framework. * @param newDevice new supported broadcast audio device * @param previousDevice previous no longer supported broadcast audio device */ private void updateBroadcastActiveDevice( BluetoothDevice newDevice, BluetoothDevice previousDevice) { mActiveAudioOutDevice = newDevice; mAudioManager.handleBluetoothActiveDeviceChanged( newDevice, previousDevice, getBroadcastProfile(true)); } /* * Listen mode is set when broadcast is queued, waiting for create response notification or * descriptor was created - idicate that create notification was received. */ private boolean wasSetSinkListeningMode() { return !mCreateBroadcastQueue.isEmpty() || mAwaitingBroadcastCreateResponse || !mBroadcastDescriptors.isEmpty(); } /** * Report the active devices change to the active device manager and the media framework. * * @param groupId id of group which devices should be updated * @param newSupportedAudioDirections new supported audio directions for group of devices * @param oldSupportedAudioDirections old supported audio directions for group of devices * @param isActive if there is new active group * @param hasFallbackDevice whether any fallback device exists when deactivating * the current active device. * @param hasFallbackDevice whether any fallback device exists when deactivating the current * active device. * @param notifyAndUpdateInactiveOutDeviceOnly if only output device should be updated to * inactive devices (if new out device would be null device). * @return true if group is active after change false otherwise. */ private boolean updateActiveDevices(Integer groupId, Integer oldSupportedAudioDirections, Integer newSupportedAudioDirections, boolean isActive, boolean hasFallbackDevice) { BluetoothDevice device = null; private boolean updateActiveDevices( Integer groupId, Integer oldSupportedAudioDirections, Integer newSupportedAudioDirections, boolean isActive, boolean hasFallbackDevice, boolean notifyAndUpdateInactiveOutDeviceOnly) { BluetoothDevice newOutDevice = null; BluetoothDevice newInDevice = null; BluetoothDevice previousActiveOutDevice = mActiveAudioOutDevice; BluetoothDevice previousActiveInDevice = mActiveAudioInDevice; if (isActive) { device = getLeadDeviceForTheGroup(groupId); newOutDevice = getLeadDeviceForTheGroup(groupId); newInDevice = newOutDevice; } else { /* While broadcasting a input device needs to be connected to track Audio Framework * streaming requests. This would allow native to make a fallback to Unicast decision. */ if (notifyAndUpdateInactiveOutDeviceOnly && ((newSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0)) { newInDevice = getLeadDeviceForTheGroup(groupId); } else if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && wasSetSinkListeningMode()) { mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false); } } boolean isNewActiveOutDevice = updateActiveOutDevice(device, groupId, boolean isNewActiveOutDevice = updateActiveOutDevice(newOutDevice, groupId, oldSupportedAudioDirections, newSupportedAudioDirections); boolean isNewActiveInDevice = updateActiveInDevice(device, groupId, boolean isNewActiveInDevice = updateActiveInDevice(newInDevice, groupId, oldSupportedAudioDirections, newSupportedAudioDirections); if (DBG) { Log.d(TAG, " isNewActiveOutDevice: " + isNewActiveOutDevice + ", " + mActiveAudioOutDevice + ", isNewActiveInDevice: " + isNewActiveInDevice + ", " + mActiveAudioInDevice); + ", " + mActiveAudioInDevice + ", notifyAndUpdateInactiveOutDeviceOnly: " + notifyAndUpdateInactiveOutDeviceOnly); } if (isNewActiveOutDevice) { Loading Loading @@ -1509,11 +1613,12 @@ public class LeAudioService extends ProfileService { if (isNewActiveInDevice) { mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioInDevice, previousActiveInDevice, BluetoothProfileConnectionInfo.createLeAudioInfo(false, false)); previousActiveInDevice, BluetoothProfileConnectionInfo.createLeAudioInfo( false, false)); } if ((mActiveAudioOutDevice == null) && (mActiveAudioInDevice == null)) { if ((mActiveAudioOutDevice == null) && (notifyAndUpdateInactiveOutDeviceOnly || (mActiveAudioInDevice == null))) { /* Notify about inactive device as soon as possible. * When adding new device, wait with notification until AudioManager is ready * with adding the device. Loading Loading @@ -1877,7 +1982,7 @@ public class LeAudioService extends ProfileService { } descriptor.mIsActive = updateActiveDevices(groupId, AUDIO_DIRECTION_NONE, descriptor.mDirection, true, false); descriptor.mDirection, true, false, false); if (descriptor.mIsActive) { notifyGroupStatusChanged(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE); Loading @@ -1895,13 +2000,26 @@ public class LeAudioService extends ProfileService { return; } /* Group became inactive due to broadcast creation, check if input device should remain * connected to track streaming request on Unicast */ boolean leaveConnectedInputDevice = false; Integer newDirections = AUDIO_DIRECTION_NONE; if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && (!mCreateBroadcastQueue.isEmpty() || mBroadcastIdDeactivatedForUnicastTransition.isPresent())) { leaveConnectedInputDevice = true; newDirections |= AUDIO_DIRECTION_INPUT_BIT; } descriptor.mIsActive = false; updateActiveDevices( groupId, descriptor.mDirection, AUDIO_DIRECTION_NONE, newDirections, descriptor.mIsActive, descriptor.mHasFallbackDeviceWhenGettingInactive); descriptor.mHasFallbackDeviceWhenGettingInactive, leaveConnectedInputDevice); /* Clear lost devices */ if (DBG) Log.d(TAG, "Clear for group: " + groupId); descriptor.mHasFallbackDeviceWhenGettingInactive = false; Loading @@ -1911,6 +2029,32 @@ public class LeAudioService extends ProfileService { } } private void handleUnicastStreamStatusChange(int status) { if (DBG) { Log.d(TAG, "status: " + status); } /* Straming request of Unicast Sink stream should result in pausing broadcast and activating * Unicast group. * * When stream is suspended there should be a reverse handover. Active Unicast group should * become inactive and broadcast should be resumed grom paused state. */ if (status == LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED) { Optional<Integer> broadcastId = getFirstNotStoppedBroadcastId(); if (broadcastId.isEmpty() || (mBroadcastDescriptors.get(broadcastId.get()) == null)) { Log.e(TAG, "handleUnicastStreamStatusChange: Broadcast to Unicast handover not" + " possible"); return; } mBroadcastIdDeactivatedForUnicastTransition = Optional.of(broadcastId.get()); pauseBroadcast(broadcastId.get()); } else if (status == LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED) { removeActiveDevice(true); } } @VisibleForTesting void handleGroupIdleDuringCall() { if (mHfpHandoverDevice == null) { Loading Loading @@ -2086,6 +2230,16 @@ public class LeAudioService extends ProfileService { } void transitionFromBroadcastToUnicast() { if (mUnicastGroupIdDeactivatedForBroadcastTransition == LE_AUDIO_GROUP_ID_INVALID) { Log.d(TAG, "No deactivated group due for broadcast transmission"); return; } if (mQueuedInCallValue.isPresent()) { mLeAudioNativeInterface.setInCall(mQueuedInCallValue.get()); mQueuedInCallValue = Optional.empty(); } BluetoothDevice unicastDevice = getLeadDeviceForTheGroup(mUnicastGroupIdDeactivatedForBroadcastTransition); if (unicastDevice == null) { Loading Loading @@ -2267,7 +2421,7 @@ public class LeAudioService extends ProfileService { if (descriptor.mIsActive) { descriptor.mIsActive = updateActiveDevices(groupId, descriptor.mDirection, direction, descriptor.mIsActive, false); descriptor.mIsActive, false, false); if (!descriptor.mIsActive) { notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE); Loading Loading @@ -2318,6 +2472,15 @@ public class LeAudioService extends ProfileService { } case LeAudioStackEvent.GROUP_STATUS_INACTIVE: { handleGroupTransitToInactive(groupId); /* Check if broadcast was deactivated due to unicast */ if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { mUnicastGroupIdDeactivatedForBroadcastTransition = groupId; mQueuedInCallValue = Optional.empty(); startBroadcast(mBroadcastIdDeactivatedForUnicastTransition.get()); mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); } if (!mCreateBroadcastQueue.isEmpty()) { mUnicastGroupIdDeactivatedForBroadcastTransition = groupId; BluetoothLeBroadcastSettings settings = mCreateBroadcastQueue.remove(); Loading Loading @@ -2377,14 +2540,10 @@ public class LeAudioService extends ProfileService { } mBroadcastDescriptors.remove(broadcastId); /* Restore the Unicast stream from before the Broadcast was started. */ if (mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { transitionFromBroadcastToUnicast(); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE) { int broadcastId = stackEvent.valueInt1; int state = stackEvent.valueInt2; int previousState; LeAudioBroadcastDescriptor descriptor = mBroadcastDescriptors.get(broadcastId); if (descriptor == null) { Loading @@ -2398,6 +2557,7 @@ public class LeAudioService extends ProfileService { mLeAudioBroadcasterNativeInterface.getBroadcastMetadata(broadcastId); descriptor.mRequestedForDetails = true; } previousState = descriptor.mState; descriptor.mState = state; switch (descriptor.mState) { Loading @@ -2414,15 +2574,14 @@ public class LeAudioService extends ProfileService { d -> d.mState.equals( LeAudioStackEvent.BROADCAST_STATE_STREAMING))) { if (Objects.equals(device, mActiveAudioOutDevice)) { BluetoothDevice previousDevice = mActiveAudioOutDevice; mActiveAudioOutDevice = null; mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice, previousDevice, getBroadcastProfile(true)); } updateBroadcastActiveDevice(null, mActiveAudioOutDevice); } /* Restore the Unicast stream from before the Broadcast was started. */ if (mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { transitionFromBroadcastToUnicast(); } destroyBroadcast(broadcastId); break; case LeAudioStackEvent.BROADCAST_STATE_CONFIGURING: Loading @@ -2431,9 +2590,20 @@ public class LeAudioService extends ProfileService { case LeAudioStackEvent.BROADCAST_STATE_PAUSED: if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " paused."); /* Stop here if Broadcast was not in Streaming state before */ if (previousState != LeAudioStackEvent.BROADCAST_STATE_STREAMING) { return; } // Playback paused notifyPlaybackStopped(broadcastId, BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST); // Notify audio manager updateBroadcastActiveDevice(null, mActiveAudioOutDevice); /* Restore the Unicast stream from before the Broadcast was started. */ transitionFromBroadcastToUnicast(); break; case LeAudioStackEvent.BROADCAST_STATE_STOPPING: if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " stopping."); Loading @@ -2452,11 +2622,7 @@ public class LeAudioService extends ProfileService { d.mState.equals( LeAudioStackEvent.BROADCAST_STATE_STREAMING))) { if (!Objects.equals(device, mActiveAudioOutDevice)) { BluetoothDevice previousDevice = mActiveAudioOutDevice; mActiveAudioOutDevice = device; mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice, previousDevice, getBroadcastProfile(false)); updateBroadcastActiveDevice(device, mActiveAudioOutDevice); } } break; Loading Loading @@ -2489,6 +2655,12 @@ public class LeAudioService extends ProfileService { if (!mTmapStarted) { mTmapStarted = registerTmap(); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS) { if (stackEvent.valueInt1 == LeAudioStackEvent.DIRECTION_SINK) { handleUnicastStreamStatusChange(stackEvent.valueInt2); } else { Log.e(TAG, "Invalid direction: " + stackEvent.valueInt2); } } } Loading Loading @@ -2694,17 +2866,20 @@ public class LeAudioService extends ProfileService { descriptor.mDirection, descriptor.mDirection, descriptor.mIsActive, hasFallbackDevice); hasFallbackDevice, false); return; } } if (descriptor.mIsActive) { if (descriptor.mIsActive || Objects.equals(mActiveAudioOutDevice, device) || Objects.equals(mActiveAudioInDevice, device)) { updateActiveDevices(deviceDescriptor.mGroupId, descriptor.mDirection, descriptor.mDirection, descriptor.mIsActive, hasFallbackDevice); hasFallbackDevice, false); } } } Loading Loading @@ -2786,7 +2961,24 @@ public class LeAudioService extends ProfileService { Log.e(TAG, "Le Audio not initialized properly."); return; } /* For setting inCall mode */ if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && inCall && !areBroadcastsAllStopped()) { mQueuedInCallValue = Optional.of(true); /* Request activation of unicast group */ handleUnicastStreamStatusChange(LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED); return; } mLeAudioNativeInterface.setInCall(inCall); /* For clearing inCall mode */ if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && !inCall && mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { handleUnicastStreamStatusChange(LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED); } } /** Loading Loading @@ -4174,6 +4366,8 @@ public class LeAudioService extends ProfileService { ProfileService.println(sb, " mActiveAudioInDevice: " + mActiveAudioInDevice); ProfileService.println(sb, " mUnicastGroupIdDeactivatedForBroadcastTransition: " + mUnicastGroupIdDeactivatedForBroadcastTransition); ProfileService.println(sb, " mBroadcastIdDeactivatedForUnicastTransition: " + mBroadcastIdDeactivatedForUnicastTransition); ProfileService.println(sb, " mExposedActiveDevice: " + mExposedActiveDevice); ProfileService.println(sb, " mHfpHandoverDevice:" + mHfpHandoverDevice); ProfileService.println(sb, " mLeAudioIsInbandRingtoneSupported:" Loading android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +275 −12 File changed.Preview size limit exceeded, changes collapsed. Show changes android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +7 −3 Original line number Diff line number Diff line Loading @@ -196,6 +196,13 @@ public class LeAudioServiceTest { LeAudioNativeInterface.setInstance(mNativeInterface); startService(); mFakeFlagsImpl = new FakeFeatureFlagsImpl(); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT, false); mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, false); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, false); mService.setFeatureFlags(mFakeFlagsImpl); mService.mAudioManager = mAudioManager; mService.mMcpService = mMcpService; mService.mTbsService = mTbsService; Loading Loading @@ -1541,11 +1548,8 @@ public class LeAudioServiceTest { @Test public void testMediaContextUnavailableForAWhile() { mFakeFlagsImpl = new FakeFeatureFlagsImpl(); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT, true); mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, true); mService.setFeatureFlags(mFakeFlagsImpl); doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); Loading Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +231 −37 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.AttributionSource; import android.content.Context; import android.content.Intent; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; Loading Loading @@ -89,6 +90,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; /** Loading Loading @@ -143,6 +145,8 @@ public class LeAudioService extends ProfileService { LeAudioTmapGattServer mTmapGattServer; int mTmapRoleMask; int mUnicastGroupIdDeactivatedForBroadcastTransition = LE_AUDIO_GROUP_ID_INVALID; Optional<Integer> mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); Optional<Boolean> mQueuedInCallValue = Optional.empty(); boolean mTmapStarted = false; private boolean mAwaitingBroadcastCreateResponse = false; private final LinkedList<BluetoothLeBroadcastSettings> mCreateBroadcastQueue = Loading Loading @@ -374,6 +378,7 @@ public class LeAudioService extends ProfileService { return true; } mQueuedInCallValue = Optional.empty(); mCreateBroadcastQueue.clear(); mAwaitingBroadcastCreateResponse = false; Loading @@ -400,7 +405,7 @@ public class LeAudioService extends ProfileService { if (descriptor.mIsActive) { descriptor.mIsActive = false; updateActiveDevices(group_id, descriptor.mDirection, AUDIO_DIRECTION_NONE, descriptor.mIsActive, false); descriptor.mIsActive, false, false); break; } } Loading Loading @@ -854,6 +859,10 @@ public class LeAudioService extends ProfileService { Log.i(TAG, "Unicast group is active, queueing Broadcast creation, while the Unicast" + " group is deactivated."); mCreateBroadcastQueue.add(broadcastSettings); if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()) { mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, true); } removeActiveDevice(true); return; Loading Loading @@ -943,6 +952,27 @@ public class LeAudioService extends ProfileService { .toArray(byte[][]::new)); } /** * Pause LeAudio Broadcast instance. * * @param broadcastId broadcast instance identifier */ public void pauseBroadcast(Integer broadcastId) { if (mLeAudioBroadcasterNativeInterface == null) { Log.w(TAG, "Native interface not available."); return; } LeAudioBroadcastDescriptor descriptor = mBroadcastDescriptors.get(broadcastId); if (descriptor == null) { Log.e(TAG, "pauseBroadcast: No valid descriptor for broadcastId: " + broadcastId); return; } if (DBG) Log.d(TAG, "pauseBroadcast"); mLeAudioBroadcasterNativeInterface.pauseBroadcast(broadcastId); } /** * Stop LeAudio Broadcast instance. * @param broadcastId broadcast instance identifier Loading Loading @@ -984,6 +1014,9 @@ public class LeAudioService extends ProfileService { } if (DBG) Log.d(TAG, "destroyBroadcast"); if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()) { mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false); } mLeAudioBroadcasterNativeInterface.destroyBroadcast(broadcastId); } Loading Loading @@ -1041,6 +1074,32 @@ public class LeAudioService extends ProfileService { return 1; } private boolean areBroadcastsAllStopped() { if (mBroadcastDescriptors == null) { Log.e(TAG, "areBroadcastsAllStopped: Invalid Broadcast Descriptors"); return false; } return mBroadcastDescriptors.values().stream() .allMatch(d -> d.mState.equals(LeAudioStackEvent.BROADCAST_STATE_STOPPED)); } private Optional<Integer> getFirstNotStoppedBroadcastId() { if (mBroadcastDescriptors == null) { Log.e(TAG, "getFirstNotStoppedBroadcastId: Invalid Broadcast Descriptors"); return Optional.empty(); } for (Map.Entry<Integer, LeAudioBroadcastDescriptor> entry : mBroadcastDescriptors.entrySet()) { if (!entry.getValue().mState.equals(LeAudioStackEvent.BROADCAST_STATE_STOPPED)) { return Optional.of(entry.getKey()); } } return Optional.empty(); } private BluetoothDevice getLeadDeviceForTheGroup(Integer groupId) { if (groupId == LE_AUDIO_GROUP_ID_INVALID) { return null; Loading Loading @@ -1448,35 +1507,80 @@ public class LeAudioService extends ProfileService { } } /* * Report the active broadcast device change to the active device manager and the media * framework. * @param newDevice new supported broadcast audio device * @param previousDevice previous no longer supported broadcast audio device */ private void updateBroadcastActiveDevice( BluetoothDevice newDevice, BluetoothDevice previousDevice) { mActiveAudioOutDevice = newDevice; mAudioManager.handleBluetoothActiveDeviceChanged( newDevice, previousDevice, getBroadcastProfile(true)); } /* * Listen mode is set when broadcast is queued, waiting for create response notification or * descriptor was created - idicate that create notification was received. */ private boolean wasSetSinkListeningMode() { return !mCreateBroadcastQueue.isEmpty() || mAwaitingBroadcastCreateResponse || !mBroadcastDescriptors.isEmpty(); } /** * Report the active devices change to the active device manager and the media framework. * * @param groupId id of group which devices should be updated * @param newSupportedAudioDirections new supported audio directions for group of devices * @param oldSupportedAudioDirections old supported audio directions for group of devices * @param isActive if there is new active group * @param hasFallbackDevice whether any fallback device exists when deactivating * the current active device. * @param hasFallbackDevice whether any fallback device exists when deactivating the current * active device. * @param notifyAndUpdateInactiveOutDeviceOnly if only output device should be updated to * inactive devices (if new out device would be null device). * @return true if group is active after change false otherwise. */ private boolean updateActiveDevices(Integer groupId, Integer oldSupportedAudioDirections, Integer newSupportedAudioDirections, boolean isActive, boolean hasFallbackDevice) { BluetoothDevice device = null; private boolean updateActiveDevices( Integer groupId, Integer oldSupportedAudioDirections, Integer newSupportedAudioDirections, boolean isActive, boolean hasFallbackDevice, boolean notifyAndUpdateInactiveOutDeviceOnly) { BluetoothDevice newOutDevice = null; BluetoothDevice newInDevice = null; BluetoothDevice previousActiveOutDevice = mActiveAudioOutDevice; BluetoothDevice previousActiveInDevice = mActiveAudioInDevice; if (isActive) { device = getLeadDeviceForTheGroup(groupId); newOutDevice = getLeadDeviceForTheGroup(groupId); newInDevice = newOutDevice; } else { /* While broadcasting a input device needs to be connected to track Audio Framework * streaming requests. This would allow native to make a fallback to Unicast decision. */ if (notifyAndUpdateInactiveOutDeviceOnly && ((newSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0)) { newInDevice = getLeadDeviceForTheGroup(groupId); } else if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && wasSetSinkListeningMode()) { mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false); } } boolean isNewActiveOutDevice = updateActiveOutDevice(device, groupId, boolean isNewActiveOutDevice = updateActiveOutDevice(newOutDevice, groupId, oldSupportedAudioDirections, newSupportedAudioDirections); boolean isNewActiveInDevice = updateActiveInDevice(device, groupId, boolean isNewActiveInDevice = updateActiveInDevice(newInDevice, groupId, oldSupportedAudioDirections, newSupportedAudioDirections); if (DBG) { Log.d(TAG, " isNewActiveOutDevice: " + isNewActiveOutDevice + ", " + mActiveAudioOutDevice + ", isNewActiveInDevice: " + isNewActiveInDevice + ", " + mActiveAudioInDevice); + ", " + mActiveAudioInDevice + ", notifyAndUpdateInactiveOutDeviceOnly: " + notifyAndUpdateInactiveOutDeviceOnly); } if (isNewActiveOutDevice) { Loading Loading @@ -1509,11 +1613,12 @@ public class LeAudioService extends ProfileService { if (isNewActiveInDevice) { mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioInDevice, previousActiveInDevice, BluetoothProfileConnectionInfo.createLeAudioInfo(false, false)); previousActiveInDevice, BluetoothProfileConnectionInfo.createLeAudioInfo( false, false)); } if ((mActiveAudioOutDevice == null) && (mActiveAudioInDevice == null)) { if ((mActiveAudioOutDevice == null) && (notifyAndUpdateInactiveOutDeviceOnly || (mActiveAudioInDevice == null))) { /* Notify about inactive device as soon as possible. * When adding new device, wait with notification until AudioManager is ready * with adding the device. Loading Loading @@ -1877,7 +1982,7 @@ public class LeAudioService extends ProfileService { } descriptor.mIsActive = updateActiveDevices(groupId, AUDIO_DIRECTION_NONE, descriptor.mDirection, true, false); descriptor.mDirection, true, false, false); if (descriptor.mIsActive) { notifyGroupStatusChanged(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE); Loading @@ -1895,13 +2000,26 @@ public class LeAudioService extends ProfileService { return; } /* Group became inactive due to broadcast creation, check if input device should remain * connected to track streaming request on Unicast */ boolean leaveConnectedInputDevice = false; Integer newDirections = AUDIO_DIRECTION_NONE; if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && (!mCreateBroadcastQueue.isEmpty() || mBroadcastIdDeactivatedForUnicastTransition.isPresent())) { leaveConnectedInputDevice = true; newDirections |= AUDIO_DIRECTION_INPUT_BIT; } descriptor.mIsActive = false; updateActiveDevices( groupId, descriptor.mDirection, AUDIO_DIRECTION_NONE, newDirections, descriptor.mIsActive, descriptor.mHasFallbackDeviceWhenGettingInactive); descriptor.mHasFallbackDeviceWhenGettingInactive, leaveConnectedInputDevice); /* Clear lost devices */ if (DBG) Log.d(TAG, "Clear for group: " + groupId); descriptor.mHasFallbackDeviceWhenGettingInactive = false; Loading @@ -1911,6 +2029,32 @@ public class LeAudioService extends ProfileService { } } private void handleUnicastStreamStatusChange(int status) { if (DBG) { Log.d(TAG, "status: " + status); } /* Straming request of Unicast Sink stream should result in pausing broadcast and activating * Unicast group. * * When stream is suspended there should be a reverse handover. Active Unicast group should * become inactive and broadcast should be resumed grom paused state. */ if (status == LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED) { Optional<Integer> broadcastId = getFirstNotStoppedBroadcastId(); if (broadcastId.isEmpty() || (mBroadcastDescriptors.get(broadcastId.get()) == null)) { Log.e(TAG, "handleUnicastStreamStatusChange: Broadcast to Unicast handover not" + " possible"); return; } mBroadcastIdDeactivatedForUnicastTransition = Optional.of(broadcastId.get()); pauseBroadcast(broadcastId.get()); } else if (status == LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED) { removeActiveDevice(true); } } @VisibleForTesting void handleGroupIdleDuringCall() { if (mHfpHandoverDevice == null) { Loading Loading @@ -2086,6 +2230,16 @@ public class LeAudioService extends ProfileService { } void transitionFromBroadcastToUnicast() { if (mUnicastGroupIdDeactivatedForBroadcastTransition == LE_AUDIO_GROUP_ID_INVALID) { Log.d(TAG, "No deactivated group due for broadcast transmission"); return; } if (mQueuedInCallValue.isPresent()) { mLeAudioNativeInterface.setInCall(mQueuedInCallValue.get()); mQueuedInCallValue = Optional.empty(); } BluetoothDevice unicastDevice = getLeadDeviceForTheGroup(mUnicastGroupIdDeactivatedForBroadcastTransition); if (unicastDevice == null) { Loading Loading @@ -2267,7 +2421,7 @@ public class LeAudioService extends ProfileService { if (descriptor.mIsActive) { descriptor.mIsActive = updateActiveDevices(groupId, descriptor.mDirection, direction, descriptor.mIsActive, false); descriptor.mIsActive, false, false); if (!descriptor.mIsActive) { notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE); Loading Loading @@ -2318,6 +2472,15 @@ public class LeAudioService extends ProfileService { } case LeAudioStackEvent.GROUP_STATUS_INACTIVE: { handleGroupTransitToInactive(groupId); /* Check if broadcast was deactivated due to unicast */ if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { mUnicastGroupIdDeactivatedForBroadcastTransition = groupId; mQueuedInCallValue = Optional.empty(); startBroadcast(mBroadcastIdDeactivatedForUnicastTransition.get()); mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); } if (!mCreateBroadcastQueue.isEmpty()) { mUnicastGroupIdDeactivatedForBroadcastTransition = groupId; BluetoothLeBroadcastSettings settings = mCreateBroadcastQueue.remove(); Loading Loading @@ -2377,14 +2540,10 @@ public class LeAudioService extends ProfileService { } mBroadcastDescriptors.remove(broadcastId); /* Restore the Unicast stream from before the Broadcast was started. */ if (mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { transitionFromBroadcastToUnicast(); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE) { int broadcastId = stackEvent.valueInt1; int state = stackEvent.valueInt2; int previousState; LeAudioBroadcastDescriptor descriptor = mBroadcastDescriptors.get(broadcastId); if (descriptor == null) { Loading @@ -2398,6 +2557,7 @@ public class LeAudioService extends ProfileService { mLeAudioBroadcasterNativeInterface.getBroadcastMetadata(broadcastId); descriptor.mRequestedForDetails = true; } previousState = descriptor.mState; descriptor.mState = state; switch (descriptor.mState) { Loading @@ -2414,15 +2574,14 @@ public class LeAudioService extends ProfileService { d -> d.mState.equals( LeAudioStackEvent.BROADCAST_STATE_STREAMING))) { if (Objects.equals(device, mActiveAudioOutDevice)) { BluetoothDevice previousDevice = mActiveAudioOutDevice; mActiveAudioOutDevice = null; mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice, previousDevice, getBroadcastProfile(true)); } updateBroadcastActiveDevice(null, mActiveAudioOutDevice); } /* Restore the Unicast stream from before the Broadcast was started. */ if (mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { transitionFromBroadcastToUnicast(); } destroyBroadcast(broadcastId); break; case LeAudioStackEvent.BROADCAST_STATE_CONFIGURING: Loading @@ -2431,9 +2590,20 @@ public class LeAudioService extends ProfileService { case LeAudioStackEvent.BROADCAST_STATE_PAUSED: if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " paused."); /* Stop here if Broadcast was not in Streaming state before */ if (previousState != LeAudioStackEvent.BROADCAST_STATE_STREAMING) { return; } // Playback paused notifyPlaybackStopped(broadcastId, BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST); // Notify audio manager updateBroadcastActiveDevice(null, mActiveAudioOutDevice); /* Restore the Unicast stream from before the Broadcast was started. */ transitionFromBroadcastToUnicast(); break; case LeAudioStackEvent.BROADCAST_STATE_STOPPING: if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " stopping."); Loading @@ -2452,11 +2622,7 @@ public class LeAudioService extends ProfileService { d.mState.equals( LeAudioStackEvent.BROADCAST_STATE_STREAMING))) { if (!Objects.equals(device, mActiveAudioOutDevice)) { BluetoothDevice previousDevice = mActiveAudioOutDevice; mActiveAudioOutDevice = device; mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice, previousDevice, getBroadcastProfile(false)); updateBroadcastActiveDevice(device, mActiveAudioOutDevice); } } break; Loading Loading @@ -2489,6 +2655,12 @@ public class LeAudioService extends ProfileService { if (!mTmapStarted) { mTmapStarted = registerTmap(); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS) { if (stackEvent.valueInt1 == LeAudioStackEvent.DIRECTION_SINK) { handleUnicastStreamStatusChange(stackEvent.valueInt2); } else { Log.e(TAG, "Invalid direction: " + stackEvent.valueInt2); } } } Loading Loading @@ -2694,17 +2866,20 @@ public class LeAudioService extends ProfileService { descriptor.mDirection, descriptor.mDirection, descriptor.mIsActive, hasFallbackDevice); hasFallbackDevice, false); return; } } if (descriptor.mIsActive) { if (descriptor.mIsActive || Objects.equals(mActiveAudioOutDevice, device) || Objects.equals(mActiveAudioInDevice, device)) { updateActiveDevices(deviceDescriptor.mGroupId, descriptor.mDirection, descriptor.mDirection, descriptor.mIsActive, hasFallbackDevice); hasFallbackDevice, false); } } } Loading Loading @@ -2786,7 +2961,24 @@ public class LeAudioService extends ProfileService { Log.e(TAG, "Le Audio not initialized properly."); return; } /* For setting inCall mode */ if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && inCall && !areBroadcastsAllStopped()) { mQueuedInCallValue = Optional.of(true); /* Request activation of unicast group */ handleUnicastStreamStatusChange(LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED); return; } mLeAudioNativeInterface.setInCall(inCall); /* For clearing inCall mode */ if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && !inCall && mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { handleUnicastStreamStatusChange(LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED); } } /** Loading Loading @@ -4174,6 +4366,8 @@ public class LeAudioService extends ProfileService { ProfileService.println(sb, " mActiveAudioInDevice: " + mActiveAudioInDevice); ProfileService.println(sb, " mUnicastGroupIdDeactivatedForBroadcastTransition: " + mUnicastGroupIdDeactivatedForBroadcastTransition); ProfileService.println(sb, " mBroadcastIdDeactivatedForUnicastTransition: " + mBroadcastIdDeactivatedForUnicastTransition); ProfileService.println(sb, " mExposedActiveDevice: " + mExposedActiveDevice); ProfileService.println(sb, " mHfpHandoverDevice:" + mHfpHandoverDevice); ProfileService.println(sb, " mLeAudioIsInbandRingtoneSupported:" Loading
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +275 −12 File changed.Preview size limit exceeded, changes collapsed. Show changes
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +7 −3 Original line number Diff line number Diff line Loading @@ -196,6 +196,13 @@ public class LeAudioServiceTest { LeAudioNativeInterface.setInstance(mNativeInterface); startService(); mFakeFlagsImpl = new FakeFeatureFlagsImpl(); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT, false); mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, false); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, false); mService.setFeatureFlags(mFakeFlagsImpl); mService.mAudioManager = mAudioManager; mService.mMcpService = mMcpService; mService.mTbsService = mTbsService; Loading Loading @@ -1541,11 +1548,8 @@ public class LeAudioServiceTest { @Test public void testMediaContextUnavailableForAWhile() { mFakeFlagsImpl = new FakeFeatureFlagsImpl(); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT, true); mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, true); mService.setFeatureFlags(mFakeFlagsImpl); doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); connectTestDevice(mSingleDevice, testGroupId); Loading