Merge "[LE Audio] New logic to generate the QR code" into udc-dev am: 042b21059e
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23297244 Change-Id: I9d5313dcbf1fe0d353090816b175cabe64f68b43 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
13
packages/SettingsLib/TEST_MAPPING
Normal file
13
packages/SettingsLib/TEST_MAPPING
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "SettingsLibUnitTests"
|
||||
},
|
||||
{
|
||||
"name": "SpaPrivilegedLibTests"
|
||||
},
|
||||
{
|
||||
"name": "SettingsSpaUnitTests"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -43,39 +43,5 @@ public final class BluetoothBroadcastUtils {
|
||||
/**
|
||||
* Bluetooth scheme.
|
||||
*/
|
||||
public static final String SCHEME_BT_BROADCAST_METADATA = "BT:";
|
||||
|
||||
// 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 = ";";
|
||||
public static final String SCHEME_BT_BROADCAST_METADATA = "BT:BluetoothLeBroadcastMetadata:";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
35
packages/SettingsLib/tests/unit/Android.bp
Normal file
35
packages/SettingsLib/tests/unit/Android.bp
Normal 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",
|
||||
],
|
||||
}
|
||||
29
packages/SettingsLib/tests/unit/AndroidManifest.xml
Normal file
29
packages/SettingsLib/tests/unit/AndroidManifest.xml
Normal 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>
|
||||
2
packages/SettingsLib/tests/unit/OWNERS
Normal file
2
packages/SettingsLib/tests/unit/OWNERS
Normal file
@@ -0,0 +1,2 @@
|
||||
# We do not guard tests - everyone is welcomed to contribute to tests.
|
||||
per-file *.kt=*
|
||||
@@ -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="
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user