diff options
3 files changed, 442 insertions, 40 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java index 2bca7cf9762e..1ff2befd19ed 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java @@ -43,5 +43,5 @@ public final class BluetoothBroadcastUtils { /** * Bluetooth scheme. */ - public static final String SCHEME_BT_BROADCAST_METADATA = "BT:BluetoothLeBroadcastMetadata:"; + public static final String SCHEME_BT_BROADCAST_METADATA = "BT:"; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt index b54b115213d5..9bb11f8da645 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt @@ -15,23 +15,92 @@ */ package com.android.settingslib.bluetooth +import android.annotation.TargetApi +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothLeAudioCodecConfigMetadata +import android.bluetooth.BluetoothLeAudioContentMetadata +import android.bluetooth.BluetoothLeBroadcastChannel import android.bluetooth.BluetoothLeBroadcastMetadata -import android.os.Parcel -import android.os.Parcelable +import android.bluetooth.BluetoothLeBroadcastSubgroup +import android.os.Build import android.util.Base64 import android.util.Log import com.android.settingslib.bluetooth.BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA object BluetoothLeBroadcastMetadataExt { - private const val TAG = "BluetoothLeBroadcastMetadataExt" + private const val TAG = "BtLeBroadcastMetadataExt" + + // BluetoothLeBroadcastMetadata + private const val KEY_BT_QR_VER = "R" + private const val KEY_BT_ADDRESS_TYPE = "T" + private const val KEY_BT_DEVICE = "D" + private const val KEY_BT_ADVERTISING_SID = "AS" + private const val KEY_BT_BROADCAST_ID = "B" + private const val KEY_BT_BROADCAST_NAME = "BN" + private const val KEY_BT_PUBLIC_BROADCAST_DATA = "PM" + private const val KEY_BT_SYNC_INTERVAL = "SI" + private const val KEY_BT_BROADCAST_CODE = "C" + private const val KEY_BT_SUBGROUPS = "SG" + private const val KEY_BT_VENDOR_SPECIFIC = "V" + private const val KEY_BT_ANDROID_VERSION = "VN" + + // Subgroup data + private const val KEY_BTSG_BIS_SYNC = "BS" + private const val KEY_BTSG_BIS_MASK = "BM" + private const val KEY_BTSG_AUDIO_CONTENT = "AC" + + // Vendor specific data + private const val KEY_BTVSD_COMPANY_ID = "VI" + private const val KEY_BTVSD_VENDOR_DATA = "VD" + + private const val DELIMITER_KEY_VALUE = ":" + private const val DELIMITER_BT_LEVEL_1 = ";" + private const val DELIMITER_BT_LEVEL_2 = "," + + private const val SUFFIX_QR_CODE = ";;" + + private const val ANDROID_VER = "U" + private const val QR_CODE_VER = 0x010000 + + // BT constants + private const val BIS_SYNC_MAX_CHANNEL = 32 + private const val BIS_SYNC_NO_PREFERENCE = 0xFFFFFFFFu + private const val SUBGROUP_LC3_CODEC_ID = 0x6L /** * Converts [BluetoothLeBroadcastMetadata] to QR code string. * - * QR code string will prefix with "BT:BluetoothLeBroadcastMetadata:". + * QR code string will prefix with "BT:". */ - fun BluetoothLeBroadcastMetadata.toQrCodeString(): String = - SCHEME_BT_BROADCAST_METADATA + Base64.encodeToString(toBytes(this), Base64.NO_WRAP) + fun BluetoothLeBroadcastMetadata.toQrCodeString(): String { + val entries = mutableListOf<Pair<String, String>>() + entries.add(Pair(KEY_BT_QR_VER, QR_CODE_VER.toString())) + entries.add(Pair(KEY_BT_ADDRESS_TYPE, this.sourceAddressType.toString())) + entries.add(Pair(KEY_BT_DEVICE, this.sourceDevice.address.replace(":", "-"))) + entries.add(Pair(KEY_BT_ADVERTISING_SID, this.sourceAdvertisingSid.toString())) + entries.add(Pair(KEY_BT_BROADCAST_ID, this.broadcastId.toString())) + if (this.broadcastName != null) { + entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString( + this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP))) + } + if (this.publicBroadcastMetadata != null) { + entries.add(Pair(KEY_BT_PUBLIC_BROADCAST_DATA, Base64.encodeToString( + this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP))) + } + entries.add(Pair(KEY_BT_SYNC_INTERVAL, this.paSyncInterval.toString())) + if (this.broadcastCode != null) { + entries.add(Pair(KEY_BT_BROADCAST_CODE, + Base64.encodeToString(this.broadcastCode, Base64.NO_WRAP))) + } + this.subgroups.forEach { + subgroup -> entries.add(Pair(KEY_BT_SUBGROUPS, subgroup.toQrCodeString())) } + entries.add(Pair(KEY_BT_ANDROID_VERSION, ANDROID_VER)) + val qrCodeString = SCHEME_BT_BROADCAST_METADATA + + entries.toQrCodeString(DELIMITER_BT_LEVEL_1) + SUFFIX_QR_CODE + Log.d(TAG, "Generated QR string : $qrCodeString") + return qrCodeString + } /** * Converts QR code string to [BluetoothLeBroadcastMetadata]. @@ -39,32 +108,255 @@ object BluetoothLeBroadcastMetadataExt { * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:". */ fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? { - if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) return null + if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) { + Log.e(TAG, "String \"$qrCodeString\" does not begin with " + + "\"$SCHEME_BT_BROADCAST_METADATA\"") + return null + } return try { - val encodedString = qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA) - val bytes = Base64.decode(encodedString, Base64.NO_WRAP) - createFromBytes(BluetoothLeBroadcastMetadata.CREATOR, bytes) + Log.d(TAG, "Parsing QR string: $qrCodeString") + val strippedString = + qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA) + .removeSuffix(SUFFIX_QR_CODE) + Log.d(TAG, "Stripped to: $strippedString") + parseQrCodeToMetadata(strippedString) } catch (e: Exception) { - Log.w(TAG, "Cannot convert QR code string to BluetoothLeBroadcastMetadata", e) + Log.w(TAG, "Cannot parse: $qrCodeString", e) null } } - private fun toBytes(parcelable: Parcelable): ByteArray = - Parcel.obtain().run { - parcelable.writeToParcel(this, 0) - setDataPosition(0) - val bytes = marshall() - recycle() - bytes + private fun BluetoothLeBroadcastSubgroup.toQrCodeString(): String { + val entries = mutableListOf<Pair<String, String>>() + entries.add(Pair(KEY_BTSG_BIS_SYNC, getBisSyncFromChannels(this.channels).toString())) + entries.add(Pair(KEY_BTSG_BIS_MASK, getBisMaskFromChannels(this.channels).toString())) + entries.add(Pair(KEY_BTSG_AUDIO_CONTENT, + Base64.encodeToString(this.contentMetadata.rawMetadata, Base64.NO_WRAP))) + return entries.toQrCodeString(DELIMITER_BT_LEVEL_2) + } + + private fun List<Pair<String, String>>.toQrCodeString(delimiter: String): String { + val entryStrings = this.map{ it.first + DELIMITER_KEY_VALUE + it.second } + return entryStrings.joinToString(separator = delimiter) + } + + @TargetApi(Build.VERSION_CODES.TIRAMISU) + private fun parseQrCodeToMetadata(input: String): BluetoothLeBroadcastMetadata { + // Split into a list of list + val level1Fields = input.split(DELIMITER_BT_LEVEL_1) + .map{it.split(DELIMITER_KEY_VALUE, limit = 2)} + var qrCodeVersion = -1 + var sourceAddrType = BluetoothDevice.ADDRESS_TYPE_UNKNOWN + var sourceAddrString: String? = null + var sourceAdvertiserSid = -1 + var broadcastId = -1 + var broadcastName: String? = null + var publicBroadcastMetadata: BluetoothLeAudioContentMetadata? = null + var paSyncInterval = -1 + var broadcastCode: ByteArray? = null + // List of VendorID -> Data Pairs + var vendorDataList = mutableListOf<Pair<Int, ByteArray?>>() + var androidVersion: String? = null + val builder = BluetoothLeBroadcastMetadata.Builder() + + for (field: List<String> in level1Fields) { + if (field.isEmpty()) { + continue + } + val key = field[0] + // Ignore 3rd value and after + val value = if (field.size > 1) field[1] else "" + when (key) { + KEY_BT_QR_VER -> { + require(qrCodeVersion == -1) { "Duplicate qrCodeVersion: $input" } + qrCodeVersion = value.toInt() + } + KEY_BT_ADDRESS_TYPE -> { + require(sourceAddrType == BluetoothDevice.ADDRESS_TYPE_UNKNOWN) { + "Duplicate sourceAddrType: $input" + } + sourceAddrType = value.toInt() + } + KEY_BT_DEVICE -> { + require(sourceAddrString == null) { "Duplicate sourceAddr: $input" } + sourceAddrString = value.replace("-", ":") + } + KEY_BT_ADVERTISING_SID -> { + require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" } + sourceAdvertiserSid = value.toInt() + } + KEY_BT_BROADCAST_ID -> { + require(broadcastId == -1) { "Duplicate broadcastId: $input" } + broadcastId = value.toInt() + } + KEY_BT_BROADCAST_NAME -> { + require(broadcastName == null) { "Duplicate broadcastName: $input" } + broadcastName = String(Base64.decode(value, Base64.NO_WRAP)) + } + KEY_BT_PUBLIC_BROADCAST_DATA -> { + require(publicBroadcastMetadata == null) { + "Duplicate publicBroadcastMetadata $input" + } + publicBroadcastMetadata = BluetoothLeAudioContentMetadata + .fromRawBytes(Base64.decode(value, Base64.NO_WRAP)) + } + KEY_BT_SYNC_INTERVAL -> { + require(paSyncInterval == -1) { "Duplicate paSyncInterval: $input" } + paSyncInterval = value.toInt() + } + KEY_BT_BROADCAST_CODE -> { + require(broadcastCode == null) { "Duplicate broadcastCode: $input" } + broadcastCode = Base64.decode(value, Base64.NO_WRAP) + } + KEY_BT_ANDROID_VERSION -> { + require(androidVersion == null) { "Duplicate androidVersion: $input" } + androidVersion = value + Log.i(TAG, "QR code Android version: $androidVersion") + } + // Repeatable + KEY_BT_SUBGROUPS -> { + builder.addSubgroup(parseSubgroupData(value)) + } + // Repeatable + KEY_BT_VENDOR_SPECIFIC -> { + vendorDataList.add(parseVendorData(value)) + } + } + } + Log.d(TAG, "parseQrCodeToMetadata: sourceAddrType=$sourceAddrType, " + + "sourceAddr=$sourceAddrString, sourceAdvertiserSid=$sourceAdvertiserSid, " + + "broadcastId=$broadcastId, broadcastName=$broadcastName, " + + "publicBroadcastMetadata=${publicBroadcastMetadata != null}, " + + "paSyncInterval=$paSyncInterval, " + + "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}") + Log.d(TAG, "Not used in current code, but part of the specification: " + + "qrCodeVersion=$qrCodeVersion, androidVersion=$androidVersion, " + + "vendorDataListSize=${vendorDataList.size}") + val adapter = BluetoothAdapter.getDefaultAdapter() + // add source device and set broadcast code + val device = adapter.getRemoteLeDevice(requireNotNull(sourceAddrString), sourceAddrType) + builder.apply { + setSourceDevice(device, sourceAddrType) + setSourceAdvertisingSid(sourceAdvertiserSid) + setBroadcastId(broadcastId) + setBroadcastName(broadcastName) + setPublicBroadcast(publicBroadcastMetadata != null) + setPublicBroadcastMetadata(publicBroadcastMetadata) + setPaSyncInterval(paSyncInterval) + setEncrypted(broadcastCode != null) + setBroadcastCode(broadcastCode) + // Presentation delay is unknown and not useful when adding source + // Broadcast sink needs to sync to the Broadcast source to get presentation delay + setPresentationDelayMicros(0) + } + return builder.build() + } + + private fun parseSubgroupData(input: String): BluetoothLeBroadcastSubgroup { + Log.d(TAG, "parseSubgroupData: $input") + val fields = input.split(DELIMITER_BT_LEVEL_2) + var bisSync: UInt? = null + var bisMask: UInt? = null + var metadata: ByteArray? = null + + fields.forEach { field -> + val(key, value) = field.split(DELIMITER_KEY_VALUE) + when (key) { + KEY_BTSG_BIS_SYNC -> { + require(bisSync == null) { "Duplicate bisSync: $input" } + bisSync = value.toUInt() + } + KEY_BTSG_BIS_MASK -> { + require(bisMask == null) { "Duplicate bisMask: $input" } + bisMask = value.toUInt() + } + KEY_BTSG_AUDIO_CONTENT -> { + require(metadata == null) { "Duplicate metadata: $input" } + metadata = Base64.decode(value, Base64.NO_WRAP) + } + } } + val channels = convertToChannels(requireNotNull(bisSync), requireNotNull(bisMask)) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder() + .setAudioLocation(0).build() + return BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(SUBGROUP_LC3_CODEC_ID) + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata( + BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0))) + channels.forEach(::addChannel) + }.build() + } + + private fun parseVendorData(input: String): Pair<Int, ByteArray?> { + var companyId = -1 + var data: ByteArray? = null + val fields = input.split(DELIMITER_BT_LEVEL_2) + fields.forEach { field -> + val(key, value) = field.split(DELIMITER_KEY_VALUE) + when (key) { + KEY_BTVSD_COMPANY_ID -> { + require(companyId == -1) { "Duplicate companyId: $input" } + companyId = value.toInt() + } + KEY_BTVSD_VENDOR_DATA -> { + require(data == null) { "Duplicate data: $input" } + data = Base64.decode(value, Base64.NO_WRAP) + } + } + } + return Pair(companyId, data) + } - private fun <T> createFromBytes(creator: Parcelable.Creator<T>, bytes: ByteArray): T = - Parcel.obtain().run { - unmarshall(bytes, 0, bytes.size) - setDataPosition(0) - val created = creator.createFromParcel(this) - recycle() - created + private fun getBisSyncFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt { + var bisSync = 0u + // channel index starts from 1 + channels.forEach { channel -> + if (channel.isSelected && channel.channelIndex > 0) { + bisSync = bisSync or (1u shl (channel.channelIndex - 1)) + } } + // No channel is selected means no preference on Android platform + return if (bisSync == 0u) BIS_SYNC_NO_PREFERENCE else bisSync + } + + private fun getBisMaskFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt { + var bisMask = 0u + // channel index starts from 1 + channels.forEach { channel -> + if (channel.channelIndex > 0) { + bisMask = bisMask or (1u shl (channel.channelIndex - 1)) + } + } + return bisMask + } + + private fun convertToChannels(bisSync: UInt, bisMask: UInt): + List<BluetoothLeBroadcastChannel> { + Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisMask=$bisMask") + var selectionMask = bisSync + if (bisSync != BIS_SYNC_NO_PREFERENCE) { + require(bisMask == (bisMask or bisSync)) { + "bisSync($bisSync) must select a subset of bisMask($bisMask) if it has preferences" + } + } else { + // No channel preference means no channel is selected + selectionMask = 0u + } + val channels = mutableListOf<BluetoothLeBroadcastChannel>() + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder() + .setAudioLocation(0).build() + for (i in 0 until BIS_SYNC_MAX_CHANNEL) { + val channelMask = 1u shl i + if ((bisMask and channelMask) != 0u) { + val channel = BluetoothLeBroadcastChannel.Builder().apply { + setSelected((selectionMask and channelMask) != 0u) + setChannelIndex(i + 1) + setCodecMetadata(audioCodecConfigMetadata) + } + channels.add(channel.build()) + } + } + return channels + } }
\ No newline at end of file diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt index 0e3590d96a14..27d7078774d5 100644 --- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt +++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt @@ -34,24 +34,33 @@ class BluetoothLeBroadcastMetadataExtTest { @Test fun toQrCodeString() { val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { - setCodecId(100) + setCodecId(0x6) val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() setCodecSpecificConfig(audioCodecConfigMetadata) - setContentMetadata(BluetoothLeAudioContentMetadata.Builder().build()) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) addChannel(BluetoothLeBroadcastChannel.Builder().apply { - setChannelIndex(1000) + setSelected(true) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(true) + setChannelIndex(1) setCodecMetadata(audioCodecConfigMetadata) }.build()) }.build() val metadata = BluetoothLeBroadcastMetadata.Builder().apply { - setSourceDevice(Device, 0) + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) setSourceAdvertisingSid(1) - setBroadcastId(2) - setPaSyncInterval(3) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) setEncrypted(true) - setBroadcastCode(byteArrayOf(10, 11, 12, 13)) - setPresentationDelayMicros(4) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) addSubgroup(subgroup) }.build() @@ -61,6 +70,108 @@ class BluetoothLeBroadcastMetadataExtTest { } @Test + fun toQrCodeString_NoChannelSelected() { + val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(0x6) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(1) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + }.build() + + val metadata = BluetoothLeBroadcastMetadata.Builder().apply { + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) + setSourceAdvertisingSid(1) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) + setEncrypted(true) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) + addSubgroup(subgroup) + }.build() + + val qrCodeString = metadata.toQrCodeString() + + val parsedMetadata = + BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!! + + assertThat(parsedMetadata).isNotNull() + assertThat(parsedMetadata.subgroups).isNotNull() + assertThat(parsedMetadata.subgroups.size).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels).isNotNull() + assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isFalse() + // Input order does not matter due to parsing through bisMask + assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse() + assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isFalse() + } + + @Test + fun toQrCodeString_OneChannelSelected() { + val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { + setCodecId(0x6) + val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() + setCodecSpecificConfig(audioCodecConfigMetadata) + setContentMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("Test").setLanguage("eng").build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(false) + setChannelIndex(1) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + addChannel(BluetoothLeBroadcastChannel.Builder().apply { + setSelected(true) + setChannelIndex(2) + setCodecMetadata(audioCodecConfigMetadata) + }.build()) + }.build() + + val metadata = BluetoothLeBroadcastMetadata.Builder().apply { + setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) + setSourceAdvertisingSid(1) + setBroadcastId(123456) + setBroadcastName("Test") + setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() + .setProgramInfo("pTest").build()) + setPaSyncInterval(160) + setEncrypted(true) + setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) + addSubgroup(subgroup) + }.build() + + val qrCodeString = metadata.toQrCodeString() + + val parsedMetadata = + BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!! + + assertThat(parsedMetadata).isNotNull() + assertThat(parsedMetadata.subgroups).isNotNull() + assertThat(parsedMetadata.subgroups.size).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels).isNotNull() + // Only selected channel can be recovered + assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isTrue() + assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1) + assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse() + assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2) + assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isTrue() + } + + @Test fun decodeAndEncodeAgain_sameString() { val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!! @@ -73,13 +184,12 @@ class BluetoothLeBroadcastMetadataExtTest { const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1" val Device: BluetoothDevice = - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_DEVICE_ADDRESS) + BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS, + BluetoothDevice.ADDRESS_TYPE_RANDOM) const val QR_CODE_STRING = - "BT:BluetoothLeBroadcastMetadata:AAAAAAEAAAABAAAAEQAAADAAMAA6AEEAMQA6AEEAMQA6AEEAMQA6" + - "AEEAMQA6AEEAMQAAAAAAAAABAAAAAgAAAAMAAAABAAAABAAAAAQAAAAKCwwNBAAAAAEAAAABAAAAZAAA" + - "AAAAAAABAAAAAAAAAAAAAAAGAAAABgAAAAUDAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP//////////AAAA" + - "AAAAAAABAAAAAQAAAAAAAADoAwAAAQAAAAAAAAAAAAAABgAAAAYAAAAFAwAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAD/////AAAAAAAAAAA=" + "BT:R:65536;T:1;D:00-A1-A1-A1-A1-A1;AS:1;B:123456;BN:VGVzdA==;" + + "PM:BgNwVGVzdA==;SI:160;C:VGVzdENvZGU=;SG:BS:3,BM:3,AC:BQNUZXN0BARlbmc=;" + + "VN:U;;" } }
\ No newline at end of file |