Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +243 −43 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 @@ -129,7 +131,7 @@ public class LeAudioService extends ProfileService { private BluetoothDevice mExposedActiveDevice; private LeAudioCodecConfig mLeAudioCodecConfig; private final Object mGroupLock = new Object(); private FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); private final FeatureFlags mFeatureFlags; ServiceFactory mServiceFactory = new ServiceFactory(); LeAudioNativeInterface mLeAudioNativeInterface; 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 @@ -248,6 +252,17 @@ public class LeAudioService extends ProfileService { private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); LeAudioService() { mFeatureFlags = new FeatureFlagsImpl(); } @VisibleForTesting LeAudioService(Context ctx, FeatureFlags featureFlags) { attachBaseContext(ctx); mFeatureFlags = featureFlags; onCreate(); } @Override protected IProfileServiceBinder initBinder() { return new BluetoothLeAudioBinder(this); Loading Loading @@ -379,6 +394,7 @@ public class LeAudioService extends ProfileService { return true; } mQueuedInCallValue = Optional.empty(); mCreateBroadcastQueue.clear(); mAwaitingBroadcastCreateResponse = false; Loading @@ -405,7 +421,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 @@ -502,11 +518,6 @@ public class LeAudioService extends ProfileService { sLeAudioService = instance; } @VisibleForTesting void setFeatureFlags(FeatureFlags featureFlags) { mFeatureFlags = featureFlags; } VolumeControlService getVolumeControlService() { if (mVolumeControlService == null) { mVolumeControlService = mServiceFactory.getVolumeControlService(); Loading Loading @@ -859,6 +870,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 @@ -948,6 +963,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 @@ -989,6 +1025,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 @@ -1046,6 +1085,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 @@ -1453,35 +1518,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 @@ -1514,11 +1624,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 @@ -1882,7 +1993,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 @@ -1900,13 +2011,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 @@ -1916,6 +2040,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 @@ -2091,6 +2241,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 @@ -2272,7 +2432,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 @@ -2323,6 +2483,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 @@ -2382,14 +2551,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 @@ -2403,6 +2568,7 @@ public class LeAudioService extends ProfileService { mLeAudioBroadcasterNativeInterface.getBroadcastMetadata(broadcastId); descriptor.mRequestedForDetails = true; } previousState = descriptor.mState; descriptor.mState = state; switch (descriptor.mState) { Loading @@ -2419,15 +2585,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 @@ -2436,9 +2601,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 @@ -2457,11 +2633,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 @@ -2494,6 +2666,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 @@ -2699,17 +2877,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 @@ -2791,7 +2972,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 @@ -4179,6 +4377,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 +269 −28 File changed.Preview size limit exceeded, changes collapsed. Show changes android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +11 −16 Original line number Diff line number Diff line Loading @@ -194,8 +194,17 @@ public class LeAudioServiceTest { doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when( mAdapterService).getBondedDevices(); 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); LeAudioNativeInterface.setInstance(mNativeInterface); startService(); mService = new LeAudioService(mTargetContext, mFakeFlagsImpl); mService.doStart(); mService.mAudioManager = mAudioManager; mService.mMcpService = mMcpService; mService.mTbsService = mTbsService; Loading Loading @@ -256,7 +265,7 @@ public class LeAudioServiceTest { mBondedDevices.clear(); mGroupIntentQueue.clear(); stopService(); mService.doStop(); if (mDeviceQueueMap != null) { mDeviceQueueMap.clear(); } Loading @@ -264,18 +273,6 @@ public class LeAudioServiceTest { LeAudioNativeInterface.setInstance(null); } private void startService() throws TimeoutException { TestUtils.startService(mServiceRule, LeAudioService.class); mService = LeAudioService.getLeAudioService(); assertThat(mService).isNotNull(); } private void stopService() throws TimeoutException { TestUtils.stopService(mServiceRule, LeAudioService.class); mService = LeAudioService.getLeAudioService(); assertThat(mService).isNull(); } private class LeAudioIntentReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading Loading @@ -1542,10 +1539,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 +243 −43 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 @@ -129,7 +131,7 @@ public class LeAudioService extends ProfileService { private BluetoothDevice mExposedActiveDevice; private LeAudioCodecConfig mLeAudioCodecConfig; private final Object mGroupLock = new Object(); private FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); private final FeatureFlags mFeatureFlags; ServiceFactory mServiceFactory = new ServiceFactory(); LeAudioNativeInterface mLeAudioNativeInterface; 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 @@ -248,6 +252,17 @@ public class LeAudioService extends ProfileService { private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); LeAudioService() { mFeatureFlags = new FeatureFlagsImpl(); } @VisibleForTesting LeAudioService(Context ctx, FeatureFlags featureFlags) { attachBaseContext(ctx); mFeatureFlags = featureFlags; onCreate(); } @Override protected IProfileServiceBinder initBinder() { return new BluetoothLeAudioBinder(this); Loading Loading @@ -379,6 +394,7 @@ public class LeAudioService extends ProfileService { return true; } mQueuedInCallValue = Optional.empty(); mCreateBroadcastQueue.clear(); mAwaitingBroadcastCreateResponse = false; Loading @@ -405,7 +421,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 @@ -502,11 +518,6 @@ public class LeAudioService extends ProfileService { sLeAudioService = instance; } @VisibleForTesting void setFeatureFlags(FeatureFlags featureFlags) { mFeatureFlags = featureFlags; } VolumeControlService getVolumeControlService() { if (mVolumeControlService == null) { mVolumeControlService = mServiceFactory.getVolumeControlService(); Loading Loading @@ -859,6 +870,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 @@ -948,6 +963,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 @@ -989,6 +1025,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 @@ -1046,6 +1085,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 @@ -1453,35 +1518,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 @@ -1514,11 +1624,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 @@ -1882,7 +1993,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 @@ -1900,13 +2011,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 @@ -1916,6 +2040,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 @@ -2091,6 +2241,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 @@ -2272,7 +2432,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 @@ -2323,6 +2483,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 @@ -2382,14 +2551,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 @@ -2403,6 +2568,7 @@ public class LeAudioService extends ProfileService { mLeAudioBroadcasterNativeInterface.getBroadcastMetadata(broadcastId); descriptor.mRequestedForDetails = true; } previousState = descriptor.mState; descriptor.mState = state; switch (descriptor.mState) { Loading @@ -2419,15 +2585,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 @@ -2436,9 +2601,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 @@ -2457,11 +2633,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 @@ -2494,6 +2666,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 @@ -2699,17 +2877,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 @@ -2791,7 +2972,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 @@ -4179,6 +4377,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 +269 −28 File changed.Preview size limit exceeded, changes collapsed. Show changes
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +11 −16 Original line number Diff line number Diff line Loading @@ -194,8 +194,17 @@ public class LeAudioServiceTest { doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when( mAdapterService).getBondedDevices(); 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); LeAudioNativeInterface.setInstance(mNativeInterface); startService(); mService = new LeAudioService(mTargetContext, mFakeFlagsImpl); mService.doStart(); mService.mAudioManager = mAudioManager; mService.mMcpService = mMcpService; mService.mTbsService = mTbsService; Loading Loading @@ -256,7 +265,7 @@ public class LeAudioServiceTest { mBondedDevices.clear(); mGroupIntentQueue.clear(); stopService(); mService.doStop(); if (mDeviceQueueMap != null) { mDeviceQueueMap.clear(); } Loading @@ -264,18 +273,6 @@ public class LeAudioServiceTest { LeAudioNativeInterface.setInstance(null); } private void startService() throws TimeoutException { TestUtils.startService(mServiceRule, LeAudioService.class); mService = LeAudioService.getLeAudioService(); assertThat(mService).isNotNull(); } private void stopService() throws TimeoutException { TestUtils.stopService(mServiceRule, LeAudioService.class); mService = LeAudioService.getLeAudioService(); assertThat(mService).isNull(); } private class LeAudioIntentReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading Loading @@ -1542,10 +1539,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