Merge "[LE Audio] New logic to generate the QR code" into udc-dev

This commit is contained in:
Chaohui Wang
2023-05-26 07:50:05 +00:00
committed by Android (Google) Code Review
9 changed files with 265 additions and 489 deletions

View File

@@ -0,0 +1,13 @@
{
"presubmit": [
{
"name": "SettingsLibUnitTests"
},
{
"name": "SpaPrivilegedLibTests"
},
{
"name": "SettingsSpaUnitTests"
}
]
}

View File

@@ -43,39 +43,5 @@ public final class BluetoothBroadcastUtils {
/** /**
* Bluetooth scheme. * Bluetooth scheme.
*/ */
public static final String SCHEME_BT_BROADCAST_METADATA = "BT:"; public static final String SCHEME_BT_BROADCAST_METADATA = "BT:BluetoothLeBroadcastMetadata:";
// BluetoothLeBroadcastMetadata
static final String PREFIX_BT_ADDRESS_TYPE = "T:";
static final String PREFIX_BT_DEVICE = "D:";
static final String PREFIX_BT_ADVERTISING_SID = "AS:";
static final String PREFIX_BT_BROADCAST_ID = "B:";
static final String PREFIX_BT_SYNC_INTERVAL = "SI:";
static final String PREFIX_BT_IS_ENCRYPTED = "E:";
static final String PREFIX_BT_BROADCAST_CODE = "C:";
static final String PREFIX_BT_PRESENTATION_DELAY = "PD:";
static final String PREFIX_BT_SUBGROUPS = "SG:";
static final String PREFIX_BT_ANDROID_VERSION = "V:";
// BluetoothLeBroadcastSubgroup
static final String PREFIX_BTSG_CODEC_ID = "CID:";
static final String PREFIX_BTSG_CODEC_CONFIG = "CC:";
static final String PREFIX_BTSG_AUDIO_CONTENT = "AC:";
static final String PREFIX_BTSG_CHANNEL_PREF = "CP:";
static final String PREFIX_BTSG_BROADCAST_CHANNEL = "BC:";
// BluetoothLeAudioCodecConfigMetadata
static final String PREFIX_BTCC_AUDIO_LOCATION = "AL:";
static final String PREFIX_BTCC_RAW_METADATA = "CCRM:";
// BluetoothLeAudioContentMetadata
static final String PREFIX_BTAC_PROGRAM_INFO = "PI:";
static final String PREFIX_BTAC_LANGUAGE = "L:";
static final String PREFIX_BTAC_RAW_METADATA = "ACRM:";
// BluetoothLeBroadcastChannel
static final String PREFIX_BTBC_CHANNEL_INDEX = "CI:";
static final String PREFIX_BTBC_CODEC_CONFIG = "BCCM:";
static final String DELIMITER_QR_CODE = ";";
} }

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.bluetooth
import android.bluetooth.BluetoothLeBroadcastMetadata
import android.os.Parcel
import android.os.Parcelable
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"
/**
* Converts [BluetoothLeBroadcastMetadata] to QR code string.
*
* QR code string will prefix with "BT:BluetoothLeBroadcastMetadata:".
*/
fun BluetoothLeBroadcastMetadata.toQrCodeString(): String =
SCHEME_BT_BROADCAST_METADATA + Base64.encodeToString(toBytes(this), Base64.NO_WRAP)
/**
* Converts QR code string to [BluetoothLeBroadcastMetadata].
*
* QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:".
*/
fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? {
if (!qrCodeString.startsWith(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)
} catch (e: Exception) {
Log.w(TAG, "Cannot convert QR code string to BluetoothLeBroadcastMetadata", e)
null
}
}
private fun toBytes(parcelable: Parcelable): ByteArray =
Parcel.obtain().run {
parcelable.writeToParcel(this, 0)
setDataPosition(0)
val bytes = marshall()
recycle()
bytes
}
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
}
}

View File

@@ -1,454 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.bluetooth;
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.bluetooth.BluetoothLeBroadcastSubgroup;
import android.util.Log;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LocalBluetoothLeBroadcastMetadata {
private static final boolean DEBUG = BluetoothUtils.D;
private static final String TAG = "LocalBluetoothLeBroadcastMetadata";
private static final String METADATA_START = "<";
private static final String METADATA_END = ">";
private static final String PATTERN_REGEX = "<(.*?)>";
private static final String PATTERN_BT_BROADCAST_METADATA =
"T:<(.*?)>;+D:<(.*?)>;+AS:<(.*?)>;+B:<(.*?)>;+SI:<(.*?)>;+E:<(.*?)>;+C:<(.*?)>;"
+ "+PD:<(.*?)>;+SG:(.*)";
private static final String PATTERN_BT_SUBGROUP =
"CID:<(.*?)>;+CC:<(.*?);>;+AC:<(.*?);>;+CP:<(.*?)>;+BC:<(.*)>;>;";
private static final String PATTERN_BT_CHANNEL = "CI:<(.*?)>;+BCCM:<(.*?);>;";
/* Index for BluetoothLeBroadcastMetadata */
private static int MATCH_INDEX_ADDRESS_TYPE = 1;
private static int MATCH_INDEX_DEVICE = 2;
private static int MATCH_INDEX_ADVERTISING_SID = 3;
private static int MATCH_INDEX_BROADCAST_ID = 4;
private static int MATCH_INDEX_SYNC_INTERVAL = 5;
private static int MATCH_INDEX_IS_ENCRYPTED = 6;
private static int MATCH_INDEX_BROADCAST_CODE = 7;
private static int MATCH_INDEX_PRESENTATION_DELAY = 8;
private static int MATCH_INDEX_SUBGROUPS = 9;
/* Index for BluetoothLeBroadcastSubgroup */
private static int MATCH_INDEX_CODEC_ID = 1;
private static int MATCH_INDEX_CODEC_CONFIG = 2;
private static int MATCH_INDEX_AUDIO_CONTENT = 3;
private static int MATCH_INDEX_CHANNEL_PREF = 4;
private static int MATCH_INDEX_BROADCAST_CHANNEL = 5;
/* Index for BluetoothLeAudioCodecConfigMetadata */
private static int LIST_INDEX_AUDIO_LOCATION = 0;
private static int LIST_INDEX_CODEC_CONFIG_RAW_METADATA = 1;
/* Index for BluetoothLeAudioContentMetadata */
private static int LIST_INDEX_PROGRAM_INFO = 0;
private static int LIST_INDEX_LANGUAGE = 1;
private static int LIST_INDEX_AUDIO_CONTENT_RAW_METADATA = 2;
/* Index for BluetoothLeBroadcastChannel */
private static int MATCH_INDEX_CHANNEL_INDEX = 1;
private static int MATCH_INDEX_CHANNEL_CODEC_CONFIG = 2;
private BluetoothLeBroadcastSubgroup mSubgroup;
private List<BluetoothLeBroadcastSubgroup> mSubgroupList;
// BluetoothLeBroadcastMetadata
// Optional: Identity address type
private int mSourceAddressType;
// Optional: Must use identity address
private BluetoothDevice mSourceDevice;
private int mSourceAdvertisingSid;
private int mBroadcastId;
private int mPaSyncInterval;
private int mPresentationDelayMicros;
private boolean mIsEncrypted;
private byte[] mBroadcastCode;
// BluetoothLeBroadcastSubgroup
private int mCodecId;
private BluetoothLeAudioContentMetadata mContentMetadata;
private BluetoothLeAudioCodecConfigMetadata mConfigMetadata;
private Boolean mNoChannelPreference;
private List<BluetoothLeBroadcastChannel> mChannel;
// BluetoothLeAudioCodecConfigMetadata
private long mAudioLocation;
private byte[] mCodecConfigMetadata;
// BluetoothLeAudioContentMetadata
private String mLanguage;
private String mProgramInfo;
private byte[] mAudioContentMetadata;
// BluetoothLeBroadcastChannel
private boolean mIsSelected;
private int mChannelIndex;
LocalBluetoothLeBroadcastMetadata(BluetoothLeBroadcastMetadata metadata) {
mSourceAddressType = metadata.getSourceAddressType();
mSourceDevice = metadata.getSourceDevice();
mSourceAdvertisingSid = metadata.getSourceAdvertisingSid();
mBroadcastId = metadata.getBroadcastId();
mPaSyncInterval = metadata.getPaSyncInterval();
mIsEncrypted = metadata.isEncrypted();
mBroadcastCode = metadata.getBroadcastCode();
mPresentationDelayMicros = metadata.getPresentationDelayMicros();
mSubgroupList = metadata.getSubgroups();
}
public LocalBluetoothLeBroadcastMetadata() {
}
public void setBroadcastCode(byte[] code) {
mBroadcastCode = code;
}
public int getBroadcastId() {
return mBroadcastId;
}
public String convertToQrCodeString() {
String subgroupString = convertSubgroupToString(mSubgroupList);
return new StringBuilder()
.append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)
.append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE)
.append(METADATA_START).append(mSourceAddressType).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_DEVICE)
.append(METADATA_START).append(mSourceDevice).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_ADVERTISING_SID)
.append(METADATA_START).append(mSourceAdvertisingSid).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_ID)
.append(METADATA_START).append(mBroadcastId).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_SYNC_INTERVAL)
.append(METADATA_START).append(mPaSyncInterval).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_IS_ENCRYPTED)
.append(METADATA_START).append(mIsEncrypted).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_CODE)
.append(METADATA_START).append(Arrays.toString(mBroadcastCode)).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_PRESENTATION_DELAY)
.append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS)
.append(METADATA_START).append(subgroupString).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.toString();
}
private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) {
StringBuilder subgroupListBuilder = new StringBuilder();
String subgroupString = "";
for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) {
String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig());
String audioContent = convertAudioContentToString(subgroup.getContentMetadata());
boolean hasChannelPreference = subgroup.hasChannelPreference();
String channels = convertChannelToString(subgroup.getChannels());
subgroupString = new StringBuilder()
.append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID)
.append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG)
.append(METADATA_START).append(audioCodec).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT)
.append(METADATA_START).append(audioContent).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTSG_CHANNEL_PREF)
.append(METADATA_START).append(hasChannelPreference).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL)
.append(METADATA_START).append(channels).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.toString();
subgroupListBuilder.append(subgroupString);
}
return subgroupListBuilder.toString();
}
private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) {
String audioLocation = String.valueOf(config.getAudioLocation());
String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8);
return new StringBuilder()
.append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION)
.append(METADATA_START).append(audioLocation).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA)
.append(METADATA_START).append(rawMetadata).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.toString();
}
private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) {
String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8);
return new StringBuilder()
.append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO)
.append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE)
.append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA)
.append(METADATA_START).append(rawMetadata).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.toString();
}
private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) {
StringBuilder channelListBuilder = new StringBuilder();
String channelString = "";
for (BluetoothLeBroadcastChannel channel: channelList) {
String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata());
channelString = new StringBuilder()
.append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX)
.append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG)
.append(METADATA_START).append(channelAudioCodec).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.toString();
channelListBuilder.append(channelString);
}
return channelListBuilder.toString();
}
/**
* Example : prefix is with the “BT:”, and end by the Android Version.
* BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;;
*
* @return BluetoothLeBroadcastMetadata
*/
public BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) {
if (DEBUG) {
Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata");
}
Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA);
Matcher match = pattern.matcher(qrCodeString);
if (match.find()) {
try {
mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE));
mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
match.group(MATCH_INDEX_DEVICE));
mSourceAdvertisingSid = Integer.parseInt(
match.group(MATCH_INDEX_ADVERTISING_SID));
mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID));
mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL));
mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED));
mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes();
mPresentationDelayMicros =
Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY));
if (DEBUG) {
Log.d(TAG, "Converted qrCodeString result: "
+ " ,Type = " + mSourceAddressType
+ " ,Device = " + mSourceDevice
+ " ,AdSid = " + mSourceAdvertisingSid
+ " ,BroadcastId = " + mBroadcastId
+ " ,paSync = " + mPaSyncInterval
+ " ,encrypted = " + mIsEncrypted
+ " ,BroadcastCode = " + Arrays.toString(mBroadcastCode)
+ " ,delay = " + mPresentationDelayMicros);
}
mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS));
return new BluetoothLeBroadcastMetadata.Builder()
.setSourceDevice(mSourceDevice, mSourceAddressType)
.setSourceAdvertisingSid(mSourceAdvertisingSid)
.setBroadcastId(mBroadcastId)
.setPaSyncInterval(mPaSyncInterval)
.setEncrypted(mIsEncrypted)
.setBroadcastCode(mBroadcastCode)
.setPresentationDelayMicros(mPresentationDelayMicros)
.addSubgroup(mSubgroup)
.build();
} catch (IllegalArgumentException e) {
Log.d(TAG, "IllegalArgumentException when convert : " + e);
return null;
}
} else {
if (DEBUG) {
Log.d(TAG, "The match fail, can not convert it to BluetoothLeBroadcastMetadata.");
}
return null;
}
}
private BluetoothLeBroadcastSubgroup convertToSubgroup(String subgroupString) {
if (DEBUG) {
Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup");
}
Pattern pattern = Pattern.compile(PATTERN_BT_SUBGROUP);
Matcher match = pattern.matcher(subgroupString);
if (match.find()) {
mCodecId = Integer.parseInt(match.group(MATCH_INDEX_CODEC_ID));
mConfigMetadata = convertToConfigMetadata(match.group(MATCH_INDEX_CODEC_CONFIG));
mContentMetadata = convertToContentMetadata(match.group(MATCH_INDEX_AUDIO_CONTENT));
mNoChannelPreference = Boolean.valueOf(match.group(MATCH_INDEX_CHANNEL_PREF));
mChannel =
convertToChannel(match.group(MATCH_INDEX_BROADCAST_CHANNEL), mConfigMetadata);
BluetoothLeBroadcastSubgroup.Builder subgroupBuilder =
new BluetoothLeBroadcastSubgroup.Builder();
subgroupBuilder.setCodecId(mCodecId);
subgroupBuilder.setCodecSpecificConfig(mConfigMetadata);
subgroupBuilder.setContentMetadata(mContentMetadata);
for (BluetoothLeBroadcastChannel channel : mChannel) {
subgroupBuilder.addChannel(channel);
}
return subgroupBuilder.build();
} else {
if (DEBUG) {
Log.d(TAG,
"The match fail, can not convert it to BluetoothLeBroadcastSubgroup.");
}
return null;
}
}
private BluetoothLeAudioCodecConfigMetadata convertToConfigMetadata(
String configMetadataString) {
if (DEBUG) {
Log.d(TAG,
"Convert " + configMetadataString + "to BluetoothLeAudioCodecConfigMetadata");
}
Pattern pattern = Pattern.compile(PATTERN_REGEX);
Matcher match = pattern.matcher(configMetadataString);
ArrayList<String> resultList = new ArrayList<>();
while (match.find()) {
resultList.add(match.group(1));
Log.d(TAG, "Codec Config match : " + match.group(1));
}
if (DEBUG) {
Log.d(TAG, "Converted configMetadataString result: " + resultList.size());
}
if (resultList.size() > 0) {
mAudioLocation = Long.parseLong(resultList.get(LIST_INDEX_AUDIO_LOCATION));
mCodecConfigMetadata = resultList.get(LIST_INDEX_CODEC_CONFIG_RAW_METADATA).getBytes();
return new BluetoothLeAudioCodecConfigMetadata.Builder()
.setAudioLocation(mAudioLocation)
.build();
} else {
if (DEBUG) {
Log.d(TAG,
"The match fail, can not convert it to "
+ "BluetoothLeAudioCodecConfigMetadata.");
}
return null;
}
}
private BluetoothLeAudioContentMetadata convertToContentMetadata(String contentMetadataString) {
if (DEBUG) {
Log.d(TAG, "Convert " + contentMetadataString + "to BluetoothLeAudioContentMetadata");
}
Pattern pattern = Pattern.compile(PATTERN_REGEX);
Matcher match = pattern.matcher(contentMetadataString);
ArrayList<String> resultList = new ArrayList<>();
while (match.find()) {
Log.d(TAG, "Audio Content match : " + match.group(1));
resultList.add(match.group(1));
}
if (DEBUG) {
Log.d(TAG, "Converted contentMetadataString result: " + resultList.size());
}
if (resultList.size() > 0) {
mProgramInfo = resultList.get(LIST_INDEX_PROGRAM_INFO);
mLanguage = resultList.get(LIST_INDEX_LANGUAGE);
mAudioContentMetadata =
resultList.get(LIST_INDEX_AUDIO_CONTENT_RAW_METADATA).getBytes();
/* TODO(b/265253566) : Need to set the default value for language when the user starts
* the broadcast.
*/
if (mLanguage.equals("null")) {
mLanguage = "eng";
}
return new BluetoothLeAudioContentMetadata.Builder()
.setProgramInfo(mProgramInfo)
.setLanguage(mLanguage)
.build();
} else {
if (DEBUG) {
Log.d(TAG,
"The match fail, can not convert it to BluetoothLeAudioContentMetadata.");
}
return null;
}
}
private List<BluetoothLeBroadcastChannel> convertToChannel(String channelString,
BluetoothLeAudioCodecConfigMetadata configMetadata) {
if (DEBUG) {
Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel");
}
Pattern pattern = Pattern.compile(PATTERN_BT_CHANNEL);
Matcher match = pattern.matcher(channelString);
Map<Integer, BluetoothLeAudioCodecConfigMetadata> channel =
new HashMap<Integer, BluetoothLeAudioCodecConfigMetadata>();
while (match.find()) {
channel.put(Integer.parseInt(match.group(MATCH_INDEX_CHANNEL_INDEX)),
convertToConfigMetadata(match.group(MATCH_INDEX_CHANNEL_CODEC_CONFIG)));
}
if (channel.size() > 0) {
mIsSelected = false;
ArrayList<BluetoothLeBroadcastChannel> broadcastChannelList = new ArrayList<>();
for (Map.Entry<Integer, BluetoothLeAudioCodecConfigMetadata> entry :
channel.entrySet()) {
broadcastChannelList.add(
new BluetoothLeBroadcastChannel.Builder()
.setSelected(mIsSelected)
.setChannelIndex(entry.getKey())
.setCodecMetadata(entry.getValue())
.build());
}
return broadcastChannelList;
} else {
if (DEBUG) {
Log.d(TAG,
"The match fail, can not convert it to BluetoothLeBroadcastChannel.");
}
return null;
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.bluetooth
import android.bluetooth.BluetoothLeBroadcastMetadata
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString
@Deprecated("Replace with BluetoothLeBroadcastMetadataExt")
class LocalBluetoothLeBroadcastMetadata(private val metadata: BluetoothLeBroadcastMetadata?) {
constructor() : this(null)
fun convertToQrCodeString(): String = metadata?.toQrCodeString() ?: ""
fun convertToBroadcastMetadata(qrCodeString: String) =
BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)
}

View File

@@ -0,0 +1,35 @@
//
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package {
default_applicable_licenses: ["frameworks_base_license"],
}
android_test {
name: "SettingsLibUnitTests",
test_suites: ["device-tests"],
srcs: [
"src/**/*.kt",
],
static_libs: [
"SettingsLib",
"androidx.test.ext.junit",
"androidx.test.runner",
"truth-prebuilt",
],
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.test">
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="Tests for SettingsLib"
android:targetPackage="com.android.settingslib.test">
</instrumentation>
</manifest>

View File

@@ -0,0 +1,2 @@
# We do not guard tests - everyone is welcomed to contribute to tests.
per-file *.kt=*

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.bluetooth
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.bluetooth.BluetoothLeBroadcastSubgroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BluetoothLeBroadcastMetadataExtTest {
@Test
fun toQrCodeString() {
val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
setCodecId(100)
val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
setCodecSpecificConfig(audioCodecConfigMetadata)
setContentMetadata(BluetoothLeAudioContentMetadata.Builder().build())
addChannel(BluetoothLeBroadcastChannel.Builder().apply {
setChannelIndex(1000)
setCodecMetadata(audioCodecConfigMetadata)
}.build())
}.build()
val metadata = BluetoothLeBroadcastMetadata.Builder().apply {
setSourceDevice(Device, 0)
setSourceAdvertisingSid(1)
setBroadcastId(2)
setPaSyncInterval(3)
setEncrypted(true)
setBroadcastCode(byteArrayOf(10, 11, 12, 13))
setPresentationDelayMicros(4)
addSubgroup(subgroup)
}.build()
val qrCodeString = metadata.toQrCodeString()
assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
}
@Test
fun decodeAndEncodeAgain_sameString() {
val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!!
val qrCodeString = metadata.toQrCodeString()
assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
}
private companion object {
const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
val Device: BluetoothDevice =
BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_DEVICE_ADDRESS)
const val QR_CODE_STRING =
"BT:BluetoothLeBroadcastMetadata:AAAAAAEAAAABAAAAEQAAADAAMAA6AEEAMQA6AEEAMQA6AEEAMQA6" +
"AEEAMQA6AEEAMQAAAAAAAAABAAAAAgAAAAMAAAABAAAABAAAAAQAAAAKCwwNBAAAAAEAAAABAAAAZAAA" +
"AAAAAAABAAAAAAAAAAAAAAAGAAAABgAAAAUDAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP//////////AAAA" +
"AAAAAAABAAAAAQAAAAAAAADoAwAAAQAAAAAAAAAAAAAABgAAAAYAAAAFAwAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAD/////AAAAAAAAAAA="
}
}