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 }