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
35     fun toQrCodeString() {
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
73     fun toQrCodeString_NoChannelSelected() {
74         val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
75             setCodecId(0x6)
76             val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
77             setCodecSpecificConfig(audioCodecConfigMetadata)
78             setContentMetadata(BluetoothLeAudioContentMetadata.Builder()
79                 .setProgramInfo("Test").setLanguage("eng").build())
80             addChannel(BluetoothLeBroadcastChannel.Builder().apply {
81                 setSelected(false)
82                 setChannelIndex(2)
83                 setCodecMetadata(audioCodecConfigMetadata)
84             }.build())
85             addChannel(BluetoothLeBroadcastChannel.Builder().apply {
86                 setSelected(false)
87                 setChannelIndex(1)
88                 setCodecMetadata(audioCodecConfigMetadata)
89             }.build())
90         }.build()
91 
92         val metadata = BluetoothLeBroadcastMetadata.Builder().apply {
93             setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM)
94             setSourceAdvertisingSid(1)
95             setBroadcastId(123456)
96             setBroadcastName("Test")
97             setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder()
98                 .setProgramInfo("pTest").build())
99             setPaSyncInterval(160)
100             setEncrypted(true)
101             setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8))
102             addSubgroup(subgroup)
103         }.build()
104 
105         val qrCodeString = metadata.toQrCodeString()
106 
107         val parsedMetadata =
108             BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!!
109 
110         assertThat(parsedMetadata).isNotNull()
111         assertThat(parsedMetadata.subgroups).isNotNull()
112         assertThat(parsedMetadata.subgroups.size).isEqualTo(1)
113         assertThat(parsedMetadata.subgroups[0].channels).isNotNull()
114         assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2)
115         assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isFalse()
116         // Input order does not matter due to parsing through bisMask
117         assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1)
118         assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse()
119         assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2)
120         assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isFalse()
121     }
122 
123     @Test
124     fun toQrCodeString_OneChannelSelected() {
125         val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
126             setCodecId(0x6)
127             val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
128             setCodecSpecificConfig(audioCodecConfigMetadata)
129             setContentMetadata(BluetoothLeAudioContentMetadata.Builder()
130                 .setProgramInfo("Test").setLanguage("eng").build())
131             addChannel(BluetoothLeBroadcastChannel.Builder().apply {
132                 setSelected(false)
133                 setChannelIndex(1)
134                 setCodecMetadata(audioCodecConfigMetadata)
135             }.build())
136             addChannel(BluetoothLeBroadcastChannel.Builder().apply {
137                 setSelected(true)
138                 setChannelIndex(2)
139                 setCodecMetadata(audioCodecConfigMetadata)
140             }.build())
141         }.build()
142 
143         val metadata = BluetoothLeBroadcastMetadata.Builder().apply {
144             setSourceDevice(Device, BluetoothDevice.ADDRESS_TYPE_RANDOM)
145             setSourceAdvertisingSid(1)
146             setBroadcastId(123456)
147             setBroadcastName("Test")
148             setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata.Builder()
149                 .setProgramInfo("pTest").build())
150             setPaSyncInterval(160)
151             setEncrypted(true)
152             setBroadcastCode("TestCode".toByteArray(Charsets.UTF_8))
153             addSubgroup(subgroup)
154         }.build()
155 
156         val qrCodeString = metadata.toQrCodeString()
157 
158         val parsedMetadata =
159             BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)!!
160 
161         assertThat(parsedMetadata).isNotNull()
162         assertThat(parsedMetadata.subgroups).isNotNull()
163         assertThat(parsedMetadata.subgroups.size).isEqualTo(1)
164         assertThat(parsedMetadata.subgroups[0].channels).isNotNull()
165         // Only selected channel can be recovered
166         assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2)
167         assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isTrue()
168         assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1)
169         assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse()
170         assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2)
171         assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isTrue()
172     }
173 
174     @Test
175     fun decodeAndEncodeAgain_sameString() {
176         val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!!
177 
178         val qrCodeString = metadata.toQrCodeString()
179 
180         assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
181     }
182 
183     private companion object {
184         const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
185 
186         val Device: BluetoothDevice =
187             BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS,
188                 BluetoothDevice.ADDRESS_TYPE_RANDOM)
189 
190         const val QR_CODE_STRING =
191             "BT:R:65536;T:1;D:00-A1-A1-A1-A1-A1;AS:1;B:123456;BN:VGVzdA==;" +
192             "PM:BgNwVGVzdA==;SI:160;C:VGVzdENvZGU=;SG:BS:3,BM:3,AC:BQNUZXN0BARlbmc=;" +
193             "VN:U;;"
194     }
195 }