Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +103 −28 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static com.android.bluetooth.flags.Flags.leaudioBroadcastApiManagePrimary import static com.android.bluetooth.flags.Flags.leaudioBroadcastAssistantPeripheralEntrustment; import static com.android.bluetooth.flags.Flags.leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator; import static com.android.bluetooth.flags.Flags.leaudioUseAudioModeListener; import static com.android.bluetooth.flags.Flags.leaudioUseAudioRecordingListener; import static com.android.modules.utils.build.SdkLevel.isAtLeastU; import android.annotation.RequiresPermission; Loading Loading @@ -61,6 +62,7 @@ import android.content.Intent; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioRecordingConfiguration; import android.media.BluetoothProfileConnectionInfo; import android.os.Binder; import android.os.Handler; Loading Loading @@ -189,6 +191,7 @@ public class LeAudioService extends ProfileService { int mTmapRoleMask; int mUnicastGroupIdDeactivatedForBroadcastTransition = LE_AUDIO_GROUP_ID_INVALID; int mCurrentAudioMode = AudioManager.MODE_NORMAL; boolean mCurrentRecordingMode = false; Optional<Integer> mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); Optional<Boolean> mQueuedInCallValue = Optional.empty(); boolean mTmapStarted = false; Loading Loading @@ -536,6 +539,53 @@ public class LeAudioService extends ProfileService { return true; } void handleRecordingModeChange(boolean isRecording) { Log.d(TAG, "Recording mode changed: " + mCurrentRecordingMode + " -> " + isRecording); boolean previousRecordingMode = mCurrentRecordingMode; mCurrentRecordingMode = isRecording; if (isRecording) { if (!areBroadcastsAllStopped()) { /* Request activation of unicast group */ handleUnicastStreamStatusChange( LeAudioStackEvent.DIRECTION_SINK, LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED); } } else { /* Remove broadcast if during handover active LE Audio device disappears * (switch to primary device or non LE Audio device) */ if (isBroadcastReadyToBeReActivated() && previousRecordingMode && (getActiveGroupId() == LE_AUDIO_GROUP_ID_INVALID)) { stopBroadcast(mBroadcastIdDeactivatedForUnicastTransition.get()); mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); return; } if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { handleUnicastStreamStatusChange( LeAudioStackEvent.DIRECTION_SINK, LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED); } } } private AudioManager.AudioRecordingCallback mAudioRecordingCallback = new AudioManager.AudioRecordingCallback() { /* Audio Framework uses this callback to notify listeners of recording configuration * changes. When a recording scenario starts or its configuration changes, this * callback provides the updated configuration. * When the scenario ends, an empty configs list indicates that recording has * stopped. */ @Override public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { handleRecordingModeChange(configs.size() != 0); } }; @Override public void start() { Log.i(TAG, "start()"); Loading Loading @@ -607,6 +657,10 @@ public class LeAudioService extends ProfileService { if (leaudioUseAudioModeListener()) { mAudioManager.addOnModeChangedListener(getMainExecutor(), mAudioModeChangeListener); } if (leaudioUseAudioRecordingListener()) { mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, null); } } @Override Loading @@ -622,10 +676,16 @@ public class LeAudioService extends ProfileService { mAudioManager.removeOnModeChangedListener(mAudioModeChangeListener); } if (leaudioUseAudioRecordingListener()) { mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); } mCreateBroadcastQueue.clear(); mAwaitingBroadcastCreateResponse = false; mIsSourceStreamMonitorModeEnabled = false; if (!leaudioUseAudioRecordingListener()) { mIsSinkStreamMonitorModeEnabled = false; } mIsBroadcastPausedFromOutside = false; clearCreateBroadcastTimeoutCallback(); Loading Loading @@ -1458,8 +1518,12 @@ public class LeAudioService extends ProfileService { } Log.d(TAG, "destroyBroadcast"); if (!leaudioUseAudioRecordingListener()) { mIsSinkStreamMonitorModeEnabled = false; mNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false); } mLeAudioBroadcasterNativeInterface.destroyBroadcast(broadcastId); } Loading Loading @@ -2247,6 +2311,7 @@ public class LeAudioService extends ProfileService { /* 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 (!leaudioUseAudioRecordingListener()) { if (notifyAndUpdateInactiveOutDeviceOnly && ((newSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0)) { newInDevice = getLeadDeviceForTheGroup(groupId); Loading @@ -2255,6 +2320,7 @@ public class LeAudioService extends ProfileService { mNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false); } } } boolean isNewActiveOutDevice = updateActiveOutDevice( Loading Loading @@ -2400,18 +2466,19 @@ public class LeAudioService extends ProfileService { // we need to update the cached group id and skip changing the active device updateFallbackUnicastGroupIdForBroadcast(groupId); if (!leaudioUseAudioRecordingListener()) { if (fallbackGroupDescriptor != null) { if (groupId == LE_AUDIO_GROUP_ID_INVALID) { /* In case of removing fallback unicast group, monitoring input device should be * removed from active devices. /* In case of removing fallback unicast group, monitoring input device * should be removed from active devices. */ updateActiveDevices( groupId, fallbackGroupDescriptor.mDirection, AUDIO_DIRECTION_NONE, AUDIO_DIRECTION_INPUT_BIT, false, fallbackGroupDescriptor.mHasFallbackDeviceWhenGettingInactive, false); true); } else { if (mActiveAudioInDevice != null) { updateActiveDevices( Loading @@ -2424,6 +2491,7 @@ public class LeAudioService extends ProfileService { } } } } return true; } Loading Loading @@ -2803,8 +2871,10 @@ public class LeAudioService extends ProfileService { boolean leaveConnectedInputDevice = false; Integer newDirections = AUDIO_DIRECTION_NONE; if (isBroadcastReadyToBeReActivated()) { if (!leaudioUseAudioRecordingListener()) { 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. Loading Loading @@ -3898,8 +3968,12 @@ public class LeAudioService extends ProfileService { if (mAwaitingBroadcastCreateResponse && !areAllGroupsInNotActiveState()) { /* Broadcast would be created once unicast group became inactive */ Log.i(TAG, "Unicast group is active, deactivate due to pending broadcast"); if (!leaudioUseAudioRecordingListener()) { mIsSinkStreamMonitorModeEnabled = true; mNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, true); } removeActiveDevice(true); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED) { Loading Loading @@ -5196,7 +5270,8 @@ public class LeAudioService extends ProfileService { } /* Update exposed monitoring input device while being in Broadcast mode */ if (isBroadcastActive() if (!leaudioUseAudioRecordingListener() && isBroadcastActive() && getActiveGroupId() == LE_AUDIO_GROUP_ID_INVALID && mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { Loading android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +161 −69 Original line number Diff line number Diff line Loading @@ -1054,10 +1054,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } Mockito.clearInvocations(mAudioManager); List<BluetoothLeBroadcastSubgroupSettings> settingsList = settings.getSubgroupSettings(); Loading @@ -1076,8 +1082,10 @@ public class LeAudioBroadcastServiceTest { eq(settings.getPublicBroadcastMetadata().getRawMetadata()), eq(expectedQualityArray), eq(expectedDataArray)); if (!Flags.leaudioUseAudioRecordingListener()) { verify(mLeAudioNativeInterface) .setUnicastMonitorMode(eq(LeAudioStackEvent.DIRECTION_SINK), eq(true)); } activeGroup = mService.getActiveGroupId(); Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup); Loading Loading @@ -1129,9 +1137,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading @@ -1151,10 +1166,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1193,9 +1214,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1225,10 +1253,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1275,9 +1309,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading @@ -1301,10 +1342,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1350,9 +1397,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading @@ -1372,10 +1426,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1415,9 +1475,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1447,10 +1514,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1492,9 +1565,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading @@ -1518,10 +1598,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1575,12 +1661,14 @@ public class LeAudioBroadcastServiceTest { reset(mAudioManager); Assert.assertTrue(mService.setActiveDevice(mDevice2)); if (!Flags.leaudioUseAudioRecordingListener()) { /* Update fallback active device (only input is active) */ ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor = ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class); assertThat(mService.setActiveDevice(mDevice2)).isTrue(); verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice2), eq(mDevice), connectionInfoArgumentCaptor.capture()); Loading @@ -1588,6 +1676,7 @@ public class LeAudioBroadcastServiceTest { connectionInfoArgumentCaptor.getAllValues(); Assert.assertEquals(connInfos.size(), 1); assertThat(connInfos.get(0).isLeOutput()).isFalse(); } Assert.assertEquals(mService.mUnicastGroupIdDeactivatedForBroadcastTransition, groupId2); } Loading Loading @@ -1681,12 +1770,13 @@ public class LeAudioBroadcastServiceTest { reset(mAudioManager); mService.setBroadcastToUnicastFallbackGroup(groupId2); /* Update fallback active device (only input is active) */ ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor = ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class); mService.setBroadcastToUnicastFallbackGroup(groupId2); if (!Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice2), eq(mDevice), connectionInfoArgumentCaptor.capture()); Loading @@ -1694,6 +1784,8 @@ public class LeAudioBroadcastServiceTest { connectionInfoArgumentCaptor.getAllValues(); Assert.assertEquals(connInfos.size(), 1); assertThat(connInfos.get(0).isLeOutput()).isFalse(); } Assert.assertEquals(mService.getBroadcastToUnicastFallbackGroup(), groupId2); } Loading system/bta/le_audio/client.cc +20 −14 Original line number Diff line number Diff line Loading @@ -1376,12 +1376,14 @@ public: * before group activation (active group context would take care of * Sink HAL client cleanup). */ if (!com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { if (sink_monitor_mode_ && !enable && le_audio_sink_hal_client_ && active_group_id_ == bluetooth::groups::kGroupUnknown) { local_metadata_context_types_.sink.clear(); le_audio_sink_hal_client_->Stop(); le_audio_sink_hal_client_.reset(); } } log::debug("enable: {}", enable); sink_monitor_mode_ = enable; Loading Loading @@ -4652,6 +4654,7 @@ public: "r_state: " + ToString(audio_receiver_state_) + ", s_state: " + ToString(audio_sender_state_)); if (!com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { if (sink_monitor_mode_ && active_group_id_ == bluetooth::groups::kGroupUnknown) { if (sink_monitor_notified_status_ != UnicastMonitorModeStatus::STREAMING_REQUESTED) { notifyAudioLocalSink(UnicastMonitorModeStatus::STREAMING_REQUESTED); Loading @@ -4659,6 +4662,7 @@ public: CancelLocalAudioSinkStreamingRequest(); return; } } /* Stop the VBC close watchdog if needed */ StopVbcCloseTimeout(); Loading Loading @@ -6240,12 +6244,14 @@ private: * the session callbacks special action from this Module would be * required e.g. to Unicast handover. */ if (!com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { if (!sink_monitor_mode_) { local_metadata_context_types_.sink.clear(); le_audio_sink_hal_client_->Stop(); le_audio_sink_hal_client_.reset(); } } } local_metadata_context_types_.source.clear(); configuration_context_type_ = LeAudioContextType::UNINITIALIZED; Loading Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +103 −28 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static com.android.bluetooth.flags.Flags.leaudioBroadcastApiManagePrimary import static com.android.bluetooth.flags.Flags.leaudioBroadcastAssistantPeripheralEntrustment; import static com.android.bluetooth.flags.Flags.leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator; import static com.android.bluetooth.flags.Flags.leaudioUseAudioModeListener; import static com.android.bluetooth.flags.Flags.leaudioUseAudioRecordingListener; import static com.android.modules.utils.build.SdkLevel.isAtLeastU; import android.annotation.RequiresPermission; Loading Loading @@ -61,6 +62,7 @@ import android.content.Intent; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioRecordingConfiguration; import android.media.BluetoothProfileConnectionInfo; import android.os.Binder; import android.os.Handler; Loading Loading @@ -189,6 +191,7 @@ public class LeAudioService extends ProfileService { int mTmapRoleMask; int mUnicastGroupIdDeactivatedForBroadcastTransition = LE_AUDIO_GROUP_ID_INVALID; int mCurrentAudioMode = AudioManager.MODE_NORMAL; boolean mCurrentRecordingMode = false; Optional<Integer> mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); Optional<Boolean> mQueuedInCallValue = Optional.empty(); boolean mTmapStarted = false; Loading Loading @@ -536,6 +539,53 @@ public class LeAudioService extends ProfileService { return true; } void handleRecordingModeChange(boolean isRecording) { Log.d(TAG, "Recording mode changed: " + mCurrentRecordingMode + " -> " + isRecording); boolean previousRecordingMode = mCurrentRecordingMode; mCurrentRecordingMode = isRecording; if (isRecording) { if (!areBroadcastsAllStopped()) { /* Request activation of unicast group */ handleUnicastStreamStatusChange( LeAudioStackEvent.DIRECTION_SINK, LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED); } } else { /* Remove broadcast if during handover active LE Audio device disappears * (switch to primary device or non LE Audio device) */ if (isBroadcastReadyToBeReActivated() && previousRecordingMode && (getActiveGroupId() == LE_AUDIO_GROUP_ID_INVALID)) { stopBroadcast(mBroadcastIdDeactivatedForUnicastTransition.get()); mBroadcastIdDeactivatedForUnicastTransition = Optional.empty(); return; } if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) { handleUnicastStreamStatusChange( LeAudioStackEvent.DIRECTION_SINK, LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED); } } } private AudioManager.AudioRecordingCallback mAudioRecordingCallback = new AudioManager.AudioRecordingCallback() { /* Audio Framework uses this callback to notify listeners of recording configuration * changes. When a recording scenario starts or its configuration changes, this * callback provides the updated configuration. * When the scenario ends, an empty configs list indicates that recording has * stopped. */ @Override public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { handleRecordingModeChange(configs.size() != 0); } }; @Override public void start() { Log.i(TAG, "start()"); Loading Loading @@ -607,6 +657,10 @@ public class LeAudioService extends ProfileService { if (leaudioUseAudioModeListener()) { mAudioManager.addOnModeChangedListener(getMainExecutor(), mAudioModeChangeListener); } if (leaudioUseAudioRecordingListener()) { mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, null); } } @Override Loading @@ -622,10 +676,16 @@ public class LeAudioService extends ProfileService { mAudioManager.removeOnModeChangedListener(mAudioModeChangeListener); } if (leaudioUseAudioRecordingListener()) { mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); } mCreateBroadcastQueue.clear(); mAwaitingBroadcastCreateResponse = false; mIsSourceStreamMonitorModeEnabled = false; if (!leaudioUseAudioRecordingListener()) { mIsSinkStreamMonitorModeEnabled = false; } mIsBroadcastPausedFromOutside = false; clearCreateBroadcastTimeoutCallback(); Loading Loading @@ -1458,8 +1518,12 @@ public class LeAudioService extends ProfileService { } Log.d(TAG, "destroyBroadcast"); if (!leaudioUseAudioRecordingListener()) { mIsSinkStreamMonitorModeEnabled = false; mNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false); } mLeAudioBroadcasterNativeInterface.destroyBroadcast(broadcastId); } Loading Loading @@ -2247,6 +2311,7 @@ public class LeAudioService extends ProfileService { /* 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 (!leaudioUseAudioRecordingListener()) { if (notifyAndUpdateInactiveOutDeviceOnly && ((newSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0)) { newInDevice = getLeadDeviceForTheGroup(groupId); Loading @@ -2255,6 +2320,7 @@ public class LeAudioService extends ProfileService { mNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false); } } } boolean isNewActiveOutDevice = updateActiveOutDevice( Loading Loading @@ -2400,18 +2466,19 @@ public class LeAudioService extends ProfileService { // we need to update the cached group id and skip changing the active device updateFallbackUnicastGroupIdForBroadcast(groupId); if (!leaudioUseAudioRecordingListener()) { if (fallbackGroupDescriptor != null) { if (groupId == LE_AUDIO_GROUP_ID_INVALID) { /* In case of removing fallback unicast group, monitoring input device should be * removed from active devices. /* In case of removing fallback unicast group, monitoring input device * should be removed from active devices. */ updateActiveDevices( groupId, fallbackGroupDescriptor.mDirection, AUDIO_DIRECTION_NONE, AUDIO_DIRECTION_INPUT_BIT, false, fallbackGroupDescriptor.mHasFallbackDeviceWhenGettingInactive, false); true); } else { if (mActiveAudioInDevice != null) { updateActiveDevices( Loading @@ -2424,6 +2491,7 @@ public class LeAudioService extends ProfileService { } } } } return true; } Loading Loading @@ -2803,8 +2871,10 @@ public class LeAudioService extends ProfileService { boolean leaveConnectedInputDevice = false; Integer newDirections = AUDIO_DIRECTION_NONE; if (isBroadcastReadyToBeReActivated()) { if (!leaudioUseAudioRecordingListener()) { 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. Loading Loading @@ -3898,8 +3968,12 @@ public class LeAudioService extends ProfileService { if (mAwaitingBroadcastCreateResponse && !areAllGroupsInNotActiveState()) { /* Broadcast would be created once unicast group became inactive */ Log.i(TAG, "Unicast group is active, deactivate due to pending broadcast"); if (!leaudioUseAudioRecordingListener()) { mIsSinkStreamMonitorModeEnabled = true; mNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, true); } removeActiveDevice(true); } } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED) { Loading Loading @@ -5196,7 +5270,8 @@ public class LeAudioService extends ProfileService { } /* Update exposed monitoring input device while being in Broadcast mode */ if (isBroadcastActive() if (!leaudioUseAudioRecordingListener() && isBroadcastActive() && getActiveGroupId() == LE_AUDIO_GROUP_ID_INVALID && mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) { Loading
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +161 −69 Original line number Diff line number Diff line Loading @@ -1054,10 +1054,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } Mockito.clearInvocations(mAudioManager); List<BluetoothLeBroadcastSubgroupSettings> settingsList = settings.getSubgroupSettings(); Loading @@ -1076,8 +1082,10 @@ public class LeAudioBroadcastServiceTest { eq(settings.getPublicBroadcastMetadata().getRawMetadata()), eq(expectedQualityArray), eq(expectedDataArray)); if (!Flags.leaudioUseAudioRecordingListener()) { verify(mLeAudioNativeInterface) .setUnicastMonitorMode(eq(LeAudioStackEvent.DIRECTION_SINK), eq(true)); } activeGroup = mService.getActiveGroupId(); Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup); Loading Loading @@ -1129,9 +1137,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading @@ -1151,10 +1166,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1193,9 +1214,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1225,10 +1253,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1275,9 +1309,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading @@ -1301,10 +1342,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1350,9 +1397,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading @@ -1372,10 +1426,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1415,9 +1475,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1447,10 +1514,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1492,9 +1565,16 @@ public class LeAudioBroadcastServiceTest { create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE; mService.messageFromNative(create_event); if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become inactive due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mBroadcastDevice), any(BluetoothProfileConnectionInfo.class)); Loading @@ -1518,10 +1598,16 @@ 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 */ if (Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager, times(2)) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } else { /* Only one Unicast device should become active due to Sink monitor mode */ verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class)); } verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class)); Loading Loading @@ -1575,12 +1661,14 @@ public class LeAudioBroadcastServiceTest { reset(mAudioManager); Assert.assertTrue(mService.setActiveDevice(mDevice2)); if (!Flags.leaudioUseAudioRecordingListener()) { /* Update fallback active device (only input is active) */ ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor = ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class); assertThat(mService.setActiveDevice(mDevice2)).isTrue(); verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice2), eq(mDevice), connectionInfoArgumentCaptor.capture()); Loading @@ -1588,6 +1676,7 @@ public class LeAudioBroadcastServiceTest { connectionInfoArgumentCaptor.getAllValues(); Assert.assertEquals(connInfos.size(), 1); assertThat(connInfos.get(0).isLeOutput()).isFalse(); } Assert.assertEquals(mService.mUnicastGroupIdDeactivatedForBroadcastTransition, groupId2); } Loading Loading @@ -1681,12 +1770,13 @@ public class LeAudioBroadcastServiceTest { reset(mAudioManager); mService.setBroadcastToUnicastFallbackGroup(groupId2); /* Update fallback active device (only input is active) */ ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor = ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class); mService.setBroadcastToUnicastFallbackGroup(groupId2); if (!Flags.leaudioUseAudioRecordingListener()) { verify(mAudioManager) .handleBluetoothActiveDeviceChanged( eq(mDevice2), eq(mDevice), connectionInfoArgumentCaptor.capture()); Loading @@ -1694,6 +1784,8 @@ public class LeAudioBroadcastServiceTest { connectionInfoArgumentCaptor.getAllValues(); Assert.assertEquals(connInfos.size(), 1); assertThat(connInfos.get(0).isLeOutput()).isFalse(); } Assert.assertEquals(mService.getBroadcastToUnicastFallbackGroup(), groupId2); } Loading
system/bta/le_audio/client.cc +20 −14 Original line number Diff line number Diff line Loading @@ -1376,12 +1376,14 @@ public: * before group activation (active group context would take care of * Sink HAL client cleanup). */ if (!com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { if (sink_monitor_mode_ && !enable && le_audio_sink_hal_client_ && active_group_id_ == bluetooth::groups::kGroupUnknown) { local_metadata_context_types_.sink.clear(); le_audio_sink_hal_client_->Stop(); le_audio_sink_hal_client_.reset(); } } log::debug("enable: {}", enable); sink_monitor_mode_ = enable; Loading Loading @@ -4652,6 +4654,7 @@ public: "r_state: " + ToString(audio_receiver_state_) + ", s_state: " + ToString(audio_sender_state_)); if (!com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { if (sink_monitor_mode_ && active_group_id_ == bluetooth::groups::kGroupUnknown) { if (sink_monitor_notified_status_ != UnicastMonitorModeStatus::STREAMING_REQUESTED) { notifyAudioLocalSink(UnicastMonitorModeStatus::STREAMING_REQUESTED); Loading @@ -4659,6 +4662,7 @@ public: CancelLocalAudioSinkStreamingRequest(); return; } } /* Stop the VBC close watchdog if needed */ StopVbcCloseTimeout(); Loading Loading @@ -6240,12 +6244,14 @@ private: * the session callbacks special action from this Module would be * required e.g. to Unicast handover. */ if (!com::android::bluetooth::flags::leaudio_use_audio_recording_listener()) { if (!sink_monitor_mode_) { local_metadata_context_types_.sink.clear(); le_audio_sink_hal_client_->Stop(); le_audio_sink_hal_client_.reset(); } } } local_metadata_context_types_.source.clear(); configuration_context_type_ = LeAudioContextType::UNINITIALIZED; Loading