diff options
8 files changed, 139 insertions, 5 deletions
diff --git a/android/app/aidl/android/bluetooth/IBluetoothLeBroadcastAssistant.aidl b/android/app/aidl/android/bluetooth/IBluetoothLeBroadcastAssistant.aidl index bf95bb5cee..54b947c061 100644 --- a/android/app/aidl/android/bluetooth/IBluetoothLeBroadcastAssistant.aidl +++ b/android/app/aidl/android/bluetooth/IBluetoothLeBroadcastAssistant.aidl @@ -60,4 +60,6 @@ interface IBluetoothLeBroadcastAssistant { List<BluetoothLeBroadcastReceiveState> getAllSources(in BluetoothDevice sink, in AttributionSource source); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") int getMaximumSourceCapacity(in BluetoothDevice sink, in AttributionSource source); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + BluetoothLeBroadcastMetadata getSourceMetadata(in BluetoothDevice sink, in int sourceId, in AttributionSource source); } diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java index 7a0389ad2c..957b02c322 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java @@ -23,6 +23,7 @@ import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; import static com.android.bluetooth.flags.Flags.leaudioAllowedContextMask; import static com.android.bluetooth.flags.Flags.leaudioBigDependsOnAudioState; +import static com.android.bluetooth.flags.Flags.leaudioBroadcastApiGetLocalMetadata; import static com.android.bluetooth.flags.Flags.leaudioBroadcastAssistantPeripheralEntrustment; import static com.android.bluetooth.flags.Flags.leaudioBroadcastExtractPeriodicScannerFromStateMachine; import static com.android.bluetooth.flags.Flags.leaudioBroadcastResyncHelper; @@ -3273,6 +3274,27 @@ public class BassClientService extends ProfileService { return stateMachine.getMaximumSourceCapacity(); } + /** + * Get metadata of source that stored on this Broadcast Sink + * + * @param sink Broadcast Sink device + * @param sourceId Broadcast source id + * @return metadata of source that stored on this Broadcast Sink + */ + BluetoothLeBroadcastMetadata getSourceMetadata(BluetoothDevice sink, int sourceId) { + if (!leaudioBroadcastApiGetLocalMetadata()) { + return null; + } + + log("getSourceMetadata: device = " + sink + " with source id = " + sourceId); + BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); + if (stateMachine == null) { + log("stateMachine is null"); + return null; + } + return stateMachine.getCurrentBroadcastMetadata(sourceId); + } + private boolean isLocalBroadcast(int broadcastId) { LeAudioService leAudioService = mServiceFactory.getLeAudioService(); if (leAudioService == null) { @@ -4630,5 +4652,16 @@ public class BassClientService extends ProfileService { } return service.getMaximumSourceCapacity(sink); } + + @Override + public BluetoothLeBroadcastMetadata getSourceMetadata( + BluetoothDevice sink, int sourceId, AttributionSource source) { + BassClientService service = getServiceAndEnforceConnect(source); + if (service == null) { + Log.e(TAG, "Service is null"); + return null; + } + return service.getSourceMetadata(sink, sourceId); + } } } diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java index b59ff2fcd2..021b73289b 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java @@ -376,10 +376,6 @@ class BassClientStateMachine extends StateMachine { return mPendingSourceToSwitch != null; } - BluetoothLeBroadcastMetadata getCurrentBroadcastMetadata(Integer sourceId) { - return mCurrentMetadata.getOrDefault(sourceId, null); - } - private void setCurrentBroadcastMetadata( Integer sourceId, BluetoothLeBroadcastMetadata metadata) { if (metadata != null) { @@ -2619,6 +2615,10 @@ class BassClientStateMachine extends StateMachine { return mNumOfBroadcastReceiverStates; } + BluetoothLeBroadcastMetadata getCurrentBroadcastMetadata(Integer sourceId) { + return mCurrentMetadata.getOrDefault(sourceId, null); + } + BluetoothDevice getDevice() { return mDevice; } diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java index 12a0f7d9ef..e6e9f40cae 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java @@ -2039,6 +2039,40 @@ public class BassClientServiceTest { } } + @Test + @EnableFlags(Flags.FLAG_LEAUDIO_BROADCAST_API_GET_LOCAL_METADATA) + public void testGetSourceMetadata() { + prepareConnectedDeviceGroup(); + startSearchingForSources(); + onScanResult(mSourceDevice, TEST_BROADCAST_ID); + onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); + BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); + + for (BassClientStateMachine sm : mStateMachines.values()) { + injectRemoteSourceStateSourceAdded( + sm, + meta, + TEST_SOURCE_ID, + BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED, + meta.isEncrypted() + ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING + : BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, + null); + doReturn(null).when(sm).getCurrentBroadcastMetadata(eq(TEST_SOURCE_ID)); + assertThat(mBassClientService.getSourceMetadata(sm.getDevice(), TEST_SOURCE_ID)) + .isEqualTo(null); + + doReturn(meta).when(sm).getCurrentBroadcastMetadata(eq(TEST_SOURCE_ID)); + doReturn(true).when(sm).isSyncedToTheSource(eq(TEST_SOURCE_ID)); + assertThat(mBassClientService.getSourceMetadata(sm.getDevice(), TEST_SOURCE_ID)) + .isEqualTo(meta); + } + + for (BassClientStateMachine sm : mStateMachines.values()) { + injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID); + } + } + /** Test whether the group operation flag is set on addSource() and removed on removeSource */ @Test public void testGroupStickyFlagSetUnset() { diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index ebc0542421..cd02d1805e 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -597,6 +597,7 @@ package android.bluetooth { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getMaximumSourceCapacity(@NonNull android.bluetooth.BluetoothDevice); + method @FlaggedApi("com.android.bluetooth.flags.leaudio_broadcast_api_get_local_metadata") @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothLeBroadcastMetadata getSourceMetadata(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=0, to=255) int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean isSearchInProgress(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void modifySource(@NonNull android.bluetooth.BluetoothDevice, int, @NonNull android.bluetooth.BluetoothLeBroadcastMetadata); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothLeBroadcastAssistant.Callback); diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java index 28b4e779ee..829ee2f301 100644 --- a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java +++ b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java @@ -21,7 +21,9 @@ import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import static android.Manifest.permission.BLUETOOTH_SCAN; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresNoPermission; @@ -40,6 +42,8 @@ import android.os.RemoteException; import android.util.CloseGuard; import android.util.Log; +import com.android.bluetooth.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -1205,6 +1209,52 @@ public final class BluetoothLeBroadcastAssistant implements BluetoothProfile, Au return defaultValue; } + /** + * Gets the {@link BluetoothLeBroadcastMetadata} of a specified source added to this Broadcast + * Sink. + * + * <p>This method retrieves the {@link BluetoothLeBroadcastMetadata} associated with the + * specified source. The metadata is obtained from the {@link BluetoothLeBroadcastReceiveState} + * maintained by this Broadcast Sink. If no matching metadata is found, this method returns + * {@code null}. + * + * <p>The source's {@link BluetoothLeBroadcastMetadata} is initially set by {@link + * #addSource(BluetoothDevice, BluetoothLeBroadcastMetadata, boolean)} and can be updated with + * {@link #modifySource(BluetoothDevice, int, BluetoothLeBroadcastMetadata)}. + * + * @param sink Broadcast Sink device + * @param sourceId Broadcast source id. Valid range is [0, 0xFF] as defined in the Broadcast + * Audio Scan Service 1.0 specification (section 3.2). + * @return metadata {@link BluetoothLeBroadcastMetadata} associated with the specified source. + * @throws IllegalArgumentException if sourceID is not [0, 0xFF]. + * @hide + */ + @FlaggedApi(Flags.FLAG_LEAUDIO_BROADCAST_API_GET_LOCAL_METADATA) + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) + public @Nullable BluetoothLeBroadcastMetadata getSourceMetadata( + @NonNull BluetoothDevice sink, @IntRange(from = 0x00, to = 0xFF) int sourceId) { + log("getSourceMetadata()"); + Objects.requireNonNull(sink, "sink cannot be null"); + if (sourceId < 0x00 || sourceId > 0xFF) { + throw new IllegalArgumentException( + "sourceId " + sourceId + " does not fall between 0x00 and 0xFF"); + } + final IBluetoothLeBroadcastAssistant service = getService(); + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } else if (mBluetoothAdapter.isEnabled()) { + try { + return service.getSourceMetadata(sink, sourceId, mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); + } + } + return null; + } + private static void log(@NonNull String msg) { if (DBG) { Log.d(TAG, msg); diff --git a/system/btif/include/btif_bqr.h b/system/btif/include/btif_bqr.h index 71105ef903..909335b411 100644 --- a/system/btif/include/btif_bqr.h +++ b/system/btif/include/btif_bqr.h @@ -88,6 +88,8 @@ static constexpr uint32_t kQualityEventMaskLeAudioChoppy = 0x1 << 6; static constexpr uint32_t kQualityEventMaskConnectFail = 0x1 << 7; static constexpr uint32_t kQualityEventMaskAdvRFStatsEvent = 0x1 << 8; static constexpr uint32_t kQualityEventMaskAdvRFStatsMonitor = 0x1 << 9; +static constexpr uint32_t kQualityEventMaskHealthMonitorStatsEvent = 0x1 << 10; +static constexpr uint32_t kQualityEventMaskControllerHealthMonitor = 0x1 << 11; static constexpr uint32_t kQualityEventMaskVendorSpecificQuality = 0x1 << 15; static constexpr uint32_t kQualityEventMaskLmpMessageTrace = 0x1 << 16; static constexpr uint32_t kQualityEventMaskBtSchedulingTrace = 0x1 << 17; @@ -99,6 +101,7 @@ static constexpr uint32_t kQualityEventMaskAll = kQualityEventMaskRootInflammation | kQualityEventMaskEnergyMonitoring | kQualityEventMaskLeAudioChoppy | kQualityEventMaskConnectFail | kQualityEventMaskAdvRFStatsEvent | kQualityEventMaskAdvRFStatsMonitor | + kQualityEventMaskHealthMonitorStatsEvent | kQualityEventMaskControllerHealthMonitor | kQualityEventMaskVendorSpecificQuality | kQualityEventMaskLmpMessageTrace | kQualityEventMaskBtSchedulingTrace | kQualityEventMaskControllerDbgInfo | kQualityEventMaskVendorSpecificTrace; @@ -132,6 +135,7 @@ static constexpr uint8_t kLogDumpParamTotalLen = 3; static constexpr uint8_t kVersion5_0ParamsTotalLen = 7; // Added in BQR V6.0 static constexpr uint8_t kVersion6_0ParamsTotalLen = 6; + // Warning criteria of the RSSI value. static constexpr int8_t kCriWarnRssi = -80; // Warning criteria of the unused AFH channel count. @@ -176,7 +180,7 @@ static constexpr uint16_t kBqrVndLogVersion = 0x102; static constexpr uint16_t kBqrVersion5_0 = 0x103; // The REPORT_ACTION_QUERY and BQR_Report_interval starting v1.04(260) static constexpr uint16_t kBqrVersion6_0 = 0x104; - +static constexpr uint16_t kBqrVersion7_0 = 0x105; // Action definition // // Action to Add, Delete or Clear the reporting of quality event(s). diff --git a/system/gd/hci/acl_manager/classic_impl.h b/system/gd/hci/acl_manager/classic_impl.h index ca0baf7417..08cdcbd231 100644 --- a/system/gd/hci/acl_manager/classic_impl.h +++ b/system/gd/hci/acl_manager/classic_impl.h @@ -624,6 +624,11 @@ public: auto view = ReadRemoteSupportedFeaturesCompleteView::Create(packet); log::assert_that(view.IsValid(), "Read remote supported features packet invalid"); uint16_t handle = view.GetConnectionHandle(); + auto status = view.GetStatus(); + if (status != ErrorCode::SUCCESS) { + log::error("handle:{} status:{}", handle, ErrorCodeText(status)); + return; + } bluetooth::os::LogMetricBluetoothRemoteSupportedFeatures(connections.get_address(handle), 0, view.GetLmpFeatures(), handle); connections.execute(handle, [=](ConnectionManagementCallbacks* callbacks) { @@ -635,6 +640,11 @@ public: auto view = ReadRemoteExtendedFeaturesCompleteView::Create(packet); log::assert_that(view.IsValid(), "Read remote extended features packet invalid"); uint16_t handle = view.GetConnectionHandle(); + auto status = view.GetStatus(); + if (status != ErrorCode::SUCCESS) { + log::error("handle:{} status:{}", handle, ErrorCodeText(status)); + return; + } bluetooth::os::LogMetricBluetoothRemoteSupportedFeatures(connections.get_address(handle), view.GetPageNumber(), view.GetExtendedLmpFeatures(), handle); |