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 }