Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +11 −2 Original line number Diff line number Diff line Loading @@ -120,13 +120,16 @@ public class BassClientService extends ProfileService { int syncHandle, int advSid, int advInterval, int bId) { int bId, PublicBroadcastData pbData, String broadcastName) { log("updatePeriodicAdvertisementResultMap: device: " + device); log("updatePeriodicAdvertisementResultMap: syncHandle: " + syncHandle); log("updatePeriodicAdvertisementResultMap: advSid: " + advSid); log("updatePeriodicAdvertisementResultMap: addressType: " + addressType); log("updatePeriodicAdvertisementResultMap: advInterval: " + advInterval); log("updatePeriodicAdvertisementResultMap: broadcastId: " + bId); log("updatePeriodicAdvertisementResultMap: broadcastName: " + broadcastName); log("mDeviceToSyncHandleMap" + mDeviceToSyncHandleMap); log("mPeriodicAdvertisementResultMap" + mPeriodicAdvertisementResultMap); // Cache the SyncHandle Loading @@ -138,7 +141,7 @@ public class BassClientService extends ProfileService { if (paRes == null) { log("PAResmap: add >>>"); paRes = new PeriodicAdvertisementResult(device, addressType, syncHandle, advSid, advInterval, bId); addressType, syncHandle, advSid, advInterval, bId, pbData, broadcastName); if (paRes != null) { paRes.print(); mPeriodicAdvertisementResultMap.put(device, paRes); Loading @@ -159,6 +162,12 @@ public class BassClientService extends ProfileService { if (bId != BassConstants.INVALID_BROADCAST_ID) { paRes.updateBroadcastId(bId); } if (pbData != null) { paRes.updatePublicBroadcastData(pbData); } if (broadcastName != null) { paRes.updateBroadcastName(broadcastName); } log("PAResmap: update >>>"); paRes.print(); mPeriodicAdvertisementResultMap.replace(device, paRes); Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +59 −2 Original line number Diff line number Diff line Loading @@ -35,6 +35,8 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastSubgroup; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothUtils; import android.bluetooth.BluetoothUtils.TypeValueEntry; import android.bluetooth.le.PeriodicAdvertisingCallback; import android.bluetooth.le.PeriodicAdvertisingManager; import android.bluetooth.le.PeriodicAdvertisingReport; Loading @@ -60,6 +62,7 @@ import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; Loading Loading @@ -347,6 +350,32 @@ public class BassClientStateMachine extends StateMachine { } } private String checkAndParseBroadcastName(ScanRecord record) { log("checkAndParseBroadcastName"); byte[] rawBytes = record.getBytes(); List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes); if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) { Log.e(TAG, "Invalid LTV entries in Scan record"); return null; } String broadcastName = null; for (TypeValueEntry entry : entries) { // Only use the first value of each type if (broadcastName == null && entry.getType() == BassConstants.BCAST_NAME_AD_TYPE) { byte[] bytes = entry.getValue(); int len = bytes.length; if (len < BassConstants.BCAST_NAME_LEN_MIN || len > BassConstants.BCAST_NAME_LEN_MAX) { Log.e(TAG, "Invalid broadcast name length in Scan record" + len); return null; } broadcastName = new String(bytes, StandardCharsets.UTF_8); } } return broadcastName; } private boolean selectSource( ScanResult scanRes, boolean autoTriggered) { log("selectSource: ScanResult " + scanRes); Loading Loading @@ -375,19 +404,31 @@ public class BassClientStateMachine extends StateMachine { if (scanRecord != null) { Map<ParcelUuid, byte[]> listOfUuids = scanRecord.getServiceData(); int broadcastId = BassConstants.INVALID_BROADCAST_ID; PublicBroadcastData pbData = null; if (listOfUuids != null) { if (listOfUuids.containsKey(BassConstants.BAAS_UUID)) { byte[] bId = listOfUuids.get(BassConstants.BAAS_UUID); broadcastId = BassUtils.parseBroadcastId(bId); } if (listOfUuids.containsKey(BassConstants.PUBLIC_BROADCAST_UUID)) { byte[] pbAnnouncement = listOfUuids.get(BassConstants.PUBLIC_BROADCAST_UUID); pbData = PublicBroadcastData.parsePublicBroadcastData(pbAnnouncement); } } // Check if broadcast name present in scan record and parse // null if no name present String broadcastName = checkAndParseBroadcastName(scanRecord); mService.updatePeriodicAdvertisementResultMap( scanRes.getDevice(), scanRes.getDevice().getAddressType(), BassConstants.INVALID_SYNC_HANDLE, BassConstants.INVALID_ADV_SID, scanRes.getPeriodicAdvertisingInterval(), broadcastId); broadcastId, pbData, broadcastName); } return true; } Loading Loading @@ -466,6 +507,19 @@ public class BassClientStateMachine extends StateMachine { log("broadcast ID: " + broadcastId); metaData.setBroadcastId(broadcastId); metaData.setSourceAdvertisingSid(result.getAdvSid()); PublicBroadcastData pbData = result.getPublicBroadcastData(); if (pbData != null) { metaData.setPublicBroadcast(true); metaData.setAudioConfigQuality(pbData.getAudioConfigQuality()); metaData.setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata .fromRawBytes(pbData.getMetadata())); } String broadcastName = result.getBroadcastName(); if (broadcastName != null) { metaData.setBroadcastName(broadcastName); } } metaData.setEncrypted(encrypted); return metaData.build(); Loading @@ -490,13 +544,16 @@ public class BassClientStateMachine extends StateMachine { + ", status: " + status); if (status == BluetoothGatt.GATT_SUCCESS) { // updates syncHandle, advSid // set other fields as invalid or null mService.updatePeriodicAdvertisementResultMap( device, BassConstants.INVALID_ADV_ADDRESS_TYPE, syncHandle, advertisingSid, BassConstants.INVALID_ADV_INTERVAL, BassConstants.INVALID_BROADCAST_ID); BassConstants.INVALID_BROADCAST_ID, null, null); sendMessageDelayed(PSYNC_ACTIVE_TIMEOUT, BassConstants.PSYNC_ACTIVE_TIMEOUT_MS); mService.setActiveSyncedSource(mDevice, device); Loading android/app/src/com/android/bluetooth/bass_client/BassConstants.java +6 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ public class BassConstants { UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid BASIC_AUDIO_UUID = ParcelUuid.fromString("00001851-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid PUBLIC_BROADCAST_UUID = ParcelUuid.fromString("00001856-0000-1000-8000-00805F9B34FB"); public static final int INVALID_SYNC_HANDLE = -1; public static final int INVALID_ADV_SID = -1; Loading Loading @@ -71,4 +73,8 @@ public class BassConstants { public static final int PSYNC_TIMEOUT = 200; public static final int PIN_CODE_CMD_LEN = 18; public static final int CONNECT_TIMEOUT_MS = 30000; // broadcast name AD type and length public static final int BCAST_NAME_AD_TYPE = 0x30; public static final int BCAST_NAME_LEN_MIN = 4; public static final int BCAST_NAME_LEN_MAX = 32; } android/app/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResult.java +41 −1 Original line number Diff line number Diff line Loading @@ -32,13 +32,17 @@ public class PeriodicAdvertisementResult { private int mPAInterval; private int mBroadcastId; private boolean mIsNotified; private PublicBroadcastData mPbData; private String mBroadcastName; PeriodicAdvertisementResult(BluetoothDevice device, int addressType, int syncHandle, int advSid, int paInterval, int broadcastId) { int broadcastId, PublicBroadcastData pbData, String broadcastName) { mDevice = device; mAddressType = addressType; mAdvSid = advSid; Loading @@ -46,6 +50,8 @@ public class PeriodicAdvertisementResult { mPAInterval = paInterval; mBroadcastId = broadcastId; mIsNotified = false; mPbData = pbData; mBroadcastName = broadcastName; } /** Loading Loading @@ -133,6 +139,34 @@ public class PeriodicAdvertisementResult { return mBroadcastId; } /** * Update public broadcast data */ public void updatePublicBroadcastData(PublicBroadcastData pbData) { mPbData = pbData; } /** * Get public broadcast data */ public PublicBroadcastData getPublicBroadcastData() { return mPbData; } /** * Update broadcast name */ public void updateBroadcastName(String broadcastName) { mBroadcastName = broadcastName; } /** * Get broadcast name */ public String getBroadcastName() { return mBroadcastName; } /** * print */ Loading @@ -145,7 +179,13 @@ public class PeriodicAdvertisementResult { log("mPAInterval:" + mPAInterval); log("mBroadcastId:" + mBroadcastId); log("mIsNotified: " + mIsNotified); log("mBroadcastName: " + mBroadcastName); log("-- END: PeriodicAdvertisementResult --"); if (mPbData != null) { mPbData.print(); } else { log("no public announcement present"); } } static void log(String msg) { Loading android/app/src/com/android/bluetooth/bass_client/PublicBroadcastData.java 0 → 100644 +134 −0 Original line number Diff line number Diff line /* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.bass_client; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.util.Log; import java.util.Arrays; /** * Helper class to parse the Public Broadcast Announcement data */ class PublicBroadcastData { private static final String TAG = "Bassclient.PublicBroadcastData"; private static final int FEATURES_ENCRYPTION_BIT = 0x01 << 0; private static final int FEATURES_STANDARD_QUALITY_BIT = 0x01 << 1; private static final int FEATURES_HIGH_QUALITY_BIT = 0x01 << 2; // public announcement service data should at least include features and metadata length private static final int PUBLIC_BROADCAST_SERVICE_DATA_LEN_MIN = 2; private final PublicBroadcastInfo mPublicBroadcastInfo; public static class PublicBroadcastInfo { public byte metaDataLength; public byte[] metaData; public boolean isEncrypted; public int audioConfigQuality; PublicBroadcastInfo() { metaDataLength = 0; metaData = new byte[0]; isEncrypted = false; audioConfigQuality = BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_NONE; log("PublicBroadcastInfo is Initialized"); } void print() { log("**BEGIN: Public Broadcast Information**"); log("encrypted: " + isEncrypted); log("audio config quality: " + audioConfigQuality); log("metaDataLength: " + metaDataLength); if (metaDataLength != (byte) 0) { log("metaData: " + Arrays.toString(metaData)); } log("**END: Public Broadcast Information****"); } } PublicBroadcastData(PublicBroadcastInfo publicBroadcastInfo) { mPublicBroadcastInfo = publicBroadcastInfo; } static PublicBroadcastData parsePublicBroadcastData(byte[] serviceData) { if (serviceData == null || serviceData.length < PUBLIC_BROADCAST_SERVICE_DATA_LEN_MIN) { Log.e(TAG, "Invalid service data for PublicBroadcastData construction"); throw new IllegalArgumentException("PublicBroadcastData: serviceData is invalid"); } PublicBroadcastInfo publicBroadcastInfo = new PublicBroadcastInfo(); log("PublicBroadcast input" + Arrays.toString(serviceData)); int offset = 0; // Parse Public broadcast announcement features int features = serviceData[offset++]; publicBroadcastInfo.isEncrypted = ((features & FEATURES_ENCRYPTION_BIT) != 0) ? true : false; publicBroadcastInfo.audioConfigQuality = BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_NONE; if ((features & FEATURES_STANDARD_QUALITY_BIT) != 0) { publicBroadcastInfo.audioConfigQuality |= BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD; } if ((features & FEATURES_HIGH_QUALITY_BIT) != 0) { publicBroadcastInfo.audioConfigQuality |= BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_HIGH; } // Parse Public broadcast announcement metadata publicBroadcastInfo.metaDataLength = serviceData[offset++]; if (serviceData.length != (publicBroadcastInfo.metaDataLength + PUBLIC_BROADCAST_SERVICE_DATA_LEN_MIN)) { Log.e(TAG, "Invalid meta data length for PublicBroadcastData construction"); throw new IllegalArgumentException("PublicBroadcastData: metaData is invalid"); } if (publicBroadcastInfo.metaDataLength != 0) { publicBroadcastInfo.metaData = new byte[(int) publicBroadcastInfo.metaDataLength]; System.arraycopy(serviceData, offset, publicBroadcastInfo.metaData, 0, (int) publicBroadcastInfo.metaDataLength); offset += publicBroadcastInfo.metaDataLength; } publicBroadcastInfo.print(); return new PublicBroadcastData(publicBroadcastInfo); } boolean isEncrypted() { return mPublicBroadcastInfo.isEncrypted; } int getAudioConfigQuality() { return mPublicBroadcastInfo.audioConfigQuality; } int getMetadataLength() { return mPublicBroadcastInfo.metaDataLength; } byte[] getMetadata() { return mPublicBroadcastInfo.metaData; } void print() { mPublicBroadcastInfo.print(); } static void log(String msg) { if (BassConstants.BASS_DBG) { Log.d(TAG, msg); } } } Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +11 −2 Original line number Diff line number Diff line Loading @@ -120,13 +120,16 @@ public class BassClientService extends ProfileService { int syncHandle, int advSid, int advInterval, int bId) { int bId, PublicBroadcastData pbData, String broadcastName) { log("updatePeriodicAdvertisementResultMap: device: " + device); log("updatePeriodicAdvertisementResultMap: syncHandle: " + syncHandle); log("updatePeriodicAdvertisementResultMap: advSid: " + advSid); log("updatePeriodicAdvertisementResultMap: addressType: " + addressType); log("updatePeriodicAdvertisementResultMap: advInterval: " + advInterval); log("updatePeriodicAdvertisementResultMap: broadcastId: " + bId); log("updatePeriodicAdvertisementResultMap: broadcastName: " + broadcastName); log("mDeviceToSyncHandleMap" + mDeviceToSyncHandleMap); log("mPeriodicAdvertisementResultMap" + mPeriodicAdvertisementResultMap); // Cache the SyncHandle Loading @@ -138,7 +141,7 @@ public class BassClientService extends ProfileService { if (paRes == null) { log("PAResmap: add >>>"); paRes = new PeriodicAdvertisementResult(device, addressType, syncHandle, advSid, advInterval, bId); addressType, syncHandle, advSid, advInterval, bId, pbData, broadcastName); if (paRes != null) { paRes.print(); mPeriodicAdvertisementResultMap.put(device, paRes); Loading @@ -159,6 +162,12 @@ public class BassClientService extends ProfileService { if (bId != BassConstants.INVALID_BROADCAST_ID) { paRes.updateBroadcastId(bId); } if (pbData != null) { paRes.updatePublicBroadcastData(pbData); } if (broadcastName != null) { paRes.updateBroadcastName(broadcastName); } log("PAResmap: update >>>"); paRes.print(); mPeriodicAdvertisementResultMap.replace(device, paRes); Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +59 −2 Original line number Diff line number Diff line Loading @@ -35,6 +35,8 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastSubgroup; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothUtils; import android.bluetooth.BluetoothUtils.TypeValueEntry; import android.bluetooth.le.PeriodicAdvertisingCallback; import android.bluetooth.le.PeriodicAdvertisingManager; import android.bluetooth.le.PeriodicAdvertisingReport; Loading @@ -60,6 +62,7 @@ import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; Loading Loading @@ -347,6 +350,32 @@ public class BassClientStateMachine extends StateMachine { } } private String checkAndParseBroadcastName(ScanRecord record) { log("checkAndParseBroadcastName"); byte[] rawBytes = record.getBytes(); List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes); if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) { Log.e(TAG, "Invalid LTV entries in Scan record"); return null; } String broadcastName = null; for (TypeValueEntry entry : entries) { // Only use the first value of each type if (broadcastName == null && entry.getType() == BassConstants.BCAST_NAME_AD_TYPE) { byte[] bytes = entry.getValue(); int len = bytes.length; if (len < BassConstants.BCAST_NAME_LEN_MIN || len > BassConstants.BCAST_NAME_LEN_MAX) { Log.e(TAG, "Invalid broadcast name length in Scan record" + len); return null; } broadcastName = new String(bytes, StandardCharsets.UTF_8); } } return broadcastName; } private boolean selectSource( ScanResult scanRes, boolean autoTriggered) { log("selectSource: ScanResult " + scanRes); Loading Loading @@ -375,19 +404,31 @@ public class BassClientStateMachine extends StateMachine { if (scanRecord != null) { Map<ParcelUuid, byte[]> listOfUuids = scanRecord.getServiceData(); int broadcastId = BassConstants.INVALID_BROADCAST_ID; PublicBroadcastData pbData = null; if (listOfUuids != null) { if (listOfUuids.containsKey(BassConstants.BAAS_UUID)) { byte[] bId = listOfUuids.get(BassConstants.BAAS_UUID); broadcastId = BassUtils.parseBroadcastId(bId); } if (listOfUuids.containsKey(BassConstants.PUBLIC_BROADCAST_UUID)) { byte[] pbAnnouncement = listOfUuids.get(BassConstants.PUBLIC_BROADCAST_UUID); pbData = PublicBroadcastData.parsePublicBroadcastData(pbAnnouncement); } } // Check if broadcast name present in scan record and parse // null if no name present String broadcastName = checkAndParseBroadcastName(scanRecord); mService.updatePeriodicAdvertisementResultMap( scanRes.getDevice(), scanRes.getDevice().getAddressType(), BassConstants.INVALID_SYNC_HANDLE, BassConstants.INVALID_ADV_SID, scanRes.getPeriodicAdvertisingInterval(), broadcastId); broadcastId, pbData, broadcastName); } return true; } Loading Loading @@ -466,6 +507,19 @@ public class BassClientStateMachine extends StateMachine { log("broadcast ID: " + broadcastId); metaData.setBroadcastId(broadcastId); metaData.setSourceAdvertisingSid(result.getAdvSid()); PublicBroadcastData pbData = result.getPublicBroadcastData(); if (pbData != null) { metaData.setPublicBroadcast(true); metaData.setAudioConfigQuality(pbData.getAudioConfigQuality()); metaData.setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata .fromRawBytes(pbData.getMetadata())); } String broadcastName = result.getBroadcastName(); if (broadcastName != null) { metaData.setBroadcastName(broadcastName); } } metaData.setEncrypted(encrypted); return metaData.build(); Loading @@ -490,13 +544,16 @@ public class BassClientStateMachine extends StateMachine { + ", status: " + status); if (status == BluetoothGatt.GATT_SUCCESS) { // updates syncHandle, advSid // set other fields as invalid or null mService.updatePeriodicAdvertisementResultMap( device, BassConstants.INVALID_ADV_ADDRESS_TYPE, syncHandle, advertisingSid, BassConstants.INVALID_ADV_INTERVAL, BassConstants.INVALID_BROADCAST_ID); BassConstants.INVALID_BROADCAST_ID, null, null); sendMessageDelayed(PSYNC_ACTIVE_TIMEOUT, BassConstants.PSYNC_ACTIVE_TIMEOUT_MS); mService.setActiveSyncedSource(mDevice, device); Loading
android/app/src/com/android/bluetooth/bass_client/BassConstants.java +6 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ public class BassConstants { UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid BASIC_AUDIO_UUID = ParcelUuid.fromString("00001851-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid PUBLIC_BROADCAST_UUID = ParcelUuid.fromString("00001856-0000-1000-8000-00805F9B34FB"); public static final int INVALID_SYNC_HANDLE = -1; public static final int INVALID_ADV_SID = -1; Loading Loading @@ -71,4 +73,8 @@ public class BassConstants { public static final int PSYNC_TIMEOUT = 200; public static final int PIN_CODE_CMD_LEN = 18; public static final int CONNECT_TIMEOUT_MS = 30000; // broadcast name AD type and length public static final int BCAST_NAME_AD_TYPE = 0x30; public static final int BCAST_NAME_LEN_MIN = 4; public static final int BCAST_NAME_LEN_MAX = 32; }
android/app/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResult.java +41 −1 Original line number Diff line number Diff line Loading @@ -32,13 +32,17 @@ public class PeriodicAdvertisementResult { private int mPAInterval; private int mBroadcastId; private boolean mIsNotified; private PublicBroadcastData mPbData; private String mBroadcastName; PeriodicAdvertisementResult(BluetoothDevice device, int addressType, int syncHandle, int advSid, int paInterval, int broadcastId) { int broadcastId, PublicBroadcastData pbData, String broadcastName) { mDevice = device; mAddressType = addressType; mAdvSid = advSid; Loading @@ -46,6 +50,8 @@ public class PeriodicAdvertisementResult { mPAInterval = paInterval; mBroadcastId = broadcastId; mIsNotified = false; mPbData = pbData; mBroadcastName = broadcastName; } /** Loading Loading @@ -133,6 +139,34 @@ public class PeriodicAdvertisementResult { return mBroadcastId; } /** * Update public broadcast data */ public void updatePublicBroadcastData(PublicBroadcastData pbData) { mPbData = pbData; } /** * Get public broadcast data */ public PublicBroadcastData getPublicBroadcastData() { return mPbData; } /** * Update broadcast name */ public void updateBroadcastName(String broadcastName) { mBroadcastName = broadcastName; } /** * Get broadcast name */ public String getBroadcastName() { return mBroadcastName; } /** * print */ Loading @@ -145,7 +179,13 @@ public class PeriodicAdvertisementResult { log("mPAInterval:" + mPAInterval); log("mBroadcastId:" + mBroadcastId); log("mIsNotified: " + mIsNotified); log("mBroadcastName: " + mBroadcastName); log("-- END: PeriodicAdvertisementResult --"); if (mPbData != null) { mPbData.print(); } else { log("no public announcement present"); } } static void log(String msg) { Loading
android/app/src/com/android/bluetooth/bass_client/PublicBroadcastData.java 0 → 100644 +134 −0 Original line number Diff line number Diff line /* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.bass_client; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.util.Log; import java.util.Arrays; /** * Helper class to parse the Public Broadcast Announcement data */ class PublicBroadcastData { private static final String TAG = "Bassclient.PublicBroadcastData"; private static final int FEATURES_ENCRYPTION_BIT = 0x01 << 0; private static final int FEATURES_STANDARD_QUALITY_BIT = 0x01 << 1; private static final int FEATURES_HIGH_QUALITY_BIT = 0x01 << 2; // public announcement service data should at least include features and metadata length private static final int PUBLIC_BROADCAST_SERVICE_DATA_LEN_MIN = 2; private final PublicBroadcastInfo mPublicBroadcastInfo; public static class PublicBroadcastInfo { public byte metaDataLength; public byte[] metaData; public boolean isEncrypted; public int audioConfigQuality; PublicBroadcastInfo() { metaDataLength = 0; metaData = new byte[0]; isEncrypted = false; audioConfigQuality = BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_NONE; log("PublicBroadcastInfo is Initialized"); } void print() { log("**BEGIN: Public Broadcast Information**"); log("encrypted: " + isEncrypted); log("audio config quality: " + audioConfigQuality); log("metaDataLength: " + metaDataLength); if (metaDataLength != (byte) 0) { log("metaData: " + Arrays.toString(metaData)); } log("**END: Public Broadcast Information****"); } } PublicBroadcastData(PublicBroadcastInfo publicBroadcastInfo) { mPublicBroadcastInfo = publicBroadcastInfo; } static PublicBroadcastData parsePublicBroadcastData(byte[] serviceData) { if (serviceData == null || serviceData.length < PUBLIC_BROADCAST_SERVICE_DATA_LEN_MIN) { Log.e(TAG, "Invalid service data for PublicBroadcastData construction"); throw new IllegalArgumentException("PublicBroadcastData: serviceData is invalid"); } PublicBroadcastInfo publicBroadcastInfo = new PublicBroadcastInfo(); log("PublicBroadcast input" + Arrays.toString(serviceData)); int offset = 0; // Parse Public broadcast announcement features int features = serviceData[offset++]; publicBroadcastInfo.isEncrypted = ((features & FEATURES_ENCRYPTION_BIT) != 0) ? true : false; publicBroadcastInfo.audioConfigQuality = BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_NONE; if ((features & FEATURES_STANDARD_QUALITY_BIT) != 0) { publicBroadcastInfo.audioConfigQuality |= BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD; } if ((features & FEATURES_HIGH_QUALITY_BIT) != 0) { publicBroadcastInfo.audioConfigQuality |= BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_HIGH; } // Parse Public broadcast announcement metadata publicBroadcastInfo.metaDataLength = serviceData[offset++]; if (serviceData.length != (publicBroadcastInfo.metaDataLength + PUBLIC_BROADCAST_SERVICE_DATA_LEN_MIN)) { Log.e(TAG, "Invalid meta data length for PublicBroadcastData construction"); throw new IllegalArgumentException("PublicBroadcastData: metaData is invalid"); } if (publicBroadcastInfo.metaDataLength != 0) { publicBroadcastInfo.metaData = new byte[(int) publicBroadcastInfo.metaDataLength]; System.arraycopy(serviceData, offset, publicBroadcastInfo.metaData, 0, (int) publicBroadcastInfo.metaDataLength); offset += publicBroadcastInfo.metaDataLength; } publicBroadcastInfo.print(); return new PublicBroadcastData(publicBroadcastInfo); } boolean isEncrypted() { return mPublicBroadcastInfo.isEncrypted; } int getAudioConfigQuality() { return mPublicBroadcastInfo.audioConfigQuality; } int getMetadataLength() { return mPublicBroadcastInfo.metaDataLength; } byte[] getMetadata() { return mPublicBroadcastInfo.metaData; } void print() { mPublicBroadcastInfo.print(); } static void log(String msg) { if (BassConstants.BASS_DBG) { Log.d(TAG, msg); } } }