1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settingslib.bluetooth 17 18 import android.bluetooth.BluetoothAdapter 19 import android.bluetooth.BluetoothDevice 20 import android.bluetooth.BluetoothLeAudioCodecConfigMetadata 21 import android.bluetooth.BluetoothLeAudioContentMetadata 22 import android.bluetooth.BluetoothLeBroadcastChannel 23 import android.bluetooth.BluetoothLeBroadcastMetadata 24 import android.bluetooth.BluetoothLeBroadcastSubgroup 25 import androidx.test.ext.junit.runners.AndroidJUnit4 26 import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString 27 import com.google.common.truth.Truth.assertThat 28 import org.junit.Test 29 import org.junit.runner.RunWith 30 31 @RunWith(AndroidJUnit4::class) 32 class BluetoothLeBroadcastMetadataExtTest { 33 34 @Test toQrCodeString_encryptednull35 fun toQrCodeString_encrypted() { 36 val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { 37 setCodecId(0x6) 38 val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() 39 setCodecSpecificConfig(audioCodecConfigMetadata) 40 setContentMetadata(BluetoothLeAudioContentMetadata.Builder() 41 .setProgramInfo("Test").setLanguage("eng").build()) 42 addChannel(BluetoothLeBroadcastChannel.Builder().apply { 43 setSelected(true) 44 setChannelIndex(2) 45 setCodecMetadata(audioCodecConfigMetadata) 46 }.build()) 47 addChannel(BluetoothLeBroadcastChannel.Builder().apply { 48 setSelected(true) 49 setChannelIndex(1) 50 setCodecMetadata(audioCodecConfigMetadata) 51 }.build()) 52 }.build() 53 54 val metadata = BluetoothLeBroadcastMetadata.Builder().apply { 55 setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) 56 setSourceAdvertisingSid(1) 57 setBroadcastId(123456) 58 setBroadcastName("Test") 59 setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() 60 .setProgramInfo("pTest").build()) 61 setPaSyncInterval(160) 62 setEncrypted(true) 63 setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) 64 addSubgroup(subgroup) 65 }.build() 66 67 val qrCodeString = metadata.toQrCodeString() 68 69 assertThat(qrCodeString).isEqualTo(QR_CODE_STRING) 70 } 71 72 @Test toQrCodeString_non_encryptednull73 fun toQrCodeString_non_encrypted() { 74 val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { 75 setCodecId(0x6) 76 val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() 77 setContentMetadata(BluetoothLeAudioContentMetadata.Builder() 78 .build()) 79 setCodecSpecificConfig(audioCodecConfigMetadata) 80 addChannel(BluetoothLeBroadcastChannel.Builder().apply { 81 setSelected(true) 82 setChannelIndex(1) 83 setCodecMetadata(audioCodecConfigMetadata) 84 }.build()) 85 }.build() 86 87 val metadata = BluetoothLeBroadcastMetadata.Builder().apply { 88 setSourceDevice(DevicePublic, BluetoothDevice.ADDRESS_TYPE_PUBLIC) 89 setSourceAdvertisingSid(1) 90 setBroadcastId(0xDE51E9) 91 setBroadcastName("Hockey") 92 setAudioConfigQuality(BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD) 93 setPaSyncInterval(0xFFFF) 94 setEncrypted(false) 95 addSubgroup(subgroup) 96 }.build() 97 98 val qrCodeString = metadata.toQrCodeString() 99 100 assertThat(qrCodeString).isEqualTo(QR_CODE_STRING_NON_ENCRYPTED) 101 } 102 103 @Test toQrCodeString_NoChannelSelectednull104 fun toQrCodeString_NoChannelSelected() { 105 val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { 106 setCodecId(0x6) 107 val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() 108 setCodecSpecificConfig(audioCodecConfigMetadata) 109 setContentMetadata(BluetoothLeAudioContentMetadata.Builder() 110 .setProgramInfo("Test").setLanguage("eng").build()) 111 addChannel(BluetoothLeBroadcastChannel.Builder().apply { 112 setSelected(false) 113 setChannelIndex(2) 114 setCodecMetadata(audioCodecConfigMetadata) 115 }.build()) 116 addChannel(BluetoothLeBroadcastChannel.Builder().apply { 117 setSelected(false) 118 setChannelIndex(1) 119 setCodecMetadata(audioCodecConfigMetadata) 120 }.build()) 121 }.build() 122 123 val metadata = BluetoothLeBroadcastMetadata.Builder().apply { 124 setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) 125 setSourceAdvertisingSid(1) 126 setBroadcastId(123456) 127 setBroadcastName("Test") 128 setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() 129 .setProgramInfo("pTest").build()) 130 setPaSyncInterval(160) 131 setEncrypted(true) 132 setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) 133 addSubgroup(subgroup) 134 }.build() 135 136 // if no channel is selected, no preference(0xFFFFFFFFu) will be set in BIS 137 val qrCodeString = metadata.toQrCodeString() 138 139 val parsedMetadata = 140 BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!! 141 142 assertThat(parsedMetadata).isNotNull() 143 assertThat(parsedMetadata.subgroups).isNotNull() 144 assertThat(parsedMetadata.subgroups.size).isEqualTo(1) 145 assertThat(parsedMetadata.subgroups[0].channels).isNotNull() 146 assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(1) 147 assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isFalse() 148 // placeholder channel with not selected 149 assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1) 150 assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse() 151 } 152 153 @Test toQrCodeString_OneChannelSelectednull154 fun toQrCodeString_OneChannelSelected() { 155 val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply { 156 setCodecId(0x6) 157 val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build() 158 setCodecSpecificConfig(audioCodecConfigMetadata) 159 setContentMetadata(BluetoothLeAudioContentMetadata.Builder() 160 .setProgramInfo("Test").setLanguage("eng").build()) 161 addChannel(BluetoothLeBroadcastChannel.Builder().apply { 162 setSelected(false) 163 setChannelIndex(1) 164 setCodecMetadata(audioCodecConfigMetadata) 165 }.build()) 166 addChannel(BluetoothLeBroadcastChannel.Builder().apply { 167 setSelected(true) 168 setChannelIndex(2) 169 setCodecMetadata(audioCodecConfigMetadata) 170 }.build()) 171 }.build() 172 173 val metadata = BluetoothLeBroadcastMetadata.Builder().apply { 174 setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM) 175 setSourceAdvertisingSid(1) 176 setBroadcastId(123456) 177 setBroadcastName("Test") 178 setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder() 179 .setProgramInfo("pTest").build()) 180 setPaSyncInterval(160) 181 setEncrypted(true) 182 setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8)) 183 addSubgroup(subgroup) 184 }.build() 185 186 val qrCodeString = metadata.toQrCodeString() 187 188 val parsedMetadata = 189 BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!! 190 191 assertThat(parsedMetadata).isNotNull() 192 assertThat(parsedMetadata.subgroups).isNotNull() 193 assertThat(parsedMetadata.subgroups.size).isEqualTo(1) 194 assertThat(parsedMetadata.subgroups[0].channels).isNotNull() 195 // Only selected channel can be recovered, non-selected ones will be ignored 196 assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(1) 197 assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isTrue() 198 assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(2) 199 assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isTrue() 200 } 201 202 @Test decodeAndEncodeAgain_sameStringnull203 fun decodeAndEncodeAgain_sameString() { 204 val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!! 205 206 val qrCodeString = metadata.toQrCodeString() 207 208 assertThat(qrCodeString).isEqualTo(QR_CODE_STRING) 209 } 210 211 @Test decodeAndEncodeAgain_sameString_non_encryptednull212 fun decodeAndEncodeAgain_sameString_non_encrypted() { 213 val metadata = 214 BluetoothLeBroadcastMetadataExt 215 .convertToBroadcastMetadata(QR_CODE_STRING_NON_ENCRYPTED)!! 216 217 val qrCodeString = metadata.toQrCodeString() 218 219 assertThat(qrCodeString).isEqualTo(QR_CODE_STRING_NON_ENCRYPTED) 220 } 221 222 private companion object { 223 const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1" 224 const val TEST_DEVICE_ADDRESS_PUBLIC = "AA:BB:CC:00:11:22" 225 226 val Device: BluetoothDevice = 227 BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS, 228 BluetoothDevice.ADDRESS_TYPE_RANDOM) 229 230 val DevicePublic: BluetoothDevice = 231 BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS_PUBLIC, 232 BluetoothDevice.ADDRESS_TYPE_PUBLIC) 233 234 const val QR_CODE_STRING = 235 "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" + 236 "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;" 237 const val QR_CODE_STRING_NON_ENCRYPTED = 238 "BLUETOOTH:UUID:184F;BN:SG9ja2V5;AT:0;AD:AABBCC001122;BI:DE51E9;SQ:1;AS:1;PI:FFFF;" + 239 "NS:1;BS:1;NB:1;;" 240 } 241 }