From 78fa4e30a2d76683501903ad908007ce31583c61 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 18 May 2023 15:37:12 +0800 Subject: [PATCH] [LE Audio] New logic to generate the QR code Currently the QR code string is generated in SettingsLib, and use "<" and ">" to format the string. Because the formatted string are multi layer nested, like A:;D:<>>;>;E:;>;>; Using Regex cannot handle this case, - Regex cannot tell which B: is label for filed, which B: is user input - Regex cannot tell which pair of <> is the correct open and close for B - And these is no easy fix The BluetoothLeBroadcastMetadata already implements the Parcelable interface, use Parcel to serialize the string to fix issues. Also, add unit test. Fix: 248409874 Test: Unit test Change-Id: I31ce4bb4f5e639deb0cb60ebd7d6bf904ae85788 --- packages/SettingsLib/TEST_MAPPING | 13 + .../bluetooth/BluetoothBroadcastUtils.java | 36 +- .../BluetoothLeBroadcastMetadataExt.kt | 70 +++ .../LocalBluetoothLeBroadcastMetadata.java | 454 ------------------ .../LocalBluetoothLeBroadcastMetadata.kt | 30 ++ packages/SettingsLib/tests/unit/Android.bp | 35 ++ .../tests/unit/AndroidManifest.xml | 29 ++ packages/SettingsLib/tests/unit/OWNERS | 2 + .../BluetoothLeBroadcastMetadataExtTest.kt | 85 ++++ 9 files changed, 265 insertions(+), 489 deletions(-) create mode 100644 packages/SettingsLib/TEST_MAPPING create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt delete mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt create mode 100644 packages/SettingsLib/tests/unit/Android.bp create mode 100644 packages/SettingsLib/tests/unit/AndroidManifest.xml create mode 100644 packages/SettingsLib/tests/unit/OWNERS create mode 100644 packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt diff --git a/packages/SettingsLib/TEST_MAPPING b/packages/SettingsLib/TEST_MAPPING new file mode 100644 index 0000000000000..f6ada4c1ab7fc --- /dev/null +++ b/packages/SettingsLib/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + "presubmit": [ + { + "name": "SettingsLibUnitTests" + }, + { + "name": "SpaPrivilegedLibTests" + }, + { + "name": "SettingsSpaUnitTests" + } + ] +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java index a80061efb19a0..2bca7cf9762e1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java @@ -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:"; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt new file mode 100644 index 0000000000000..b54b115213d59 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt @@ -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 createFromBytes(creator: Parcelable.Creator, bytes: ByteArray): T = + Parcel.obtain().run { + unmarshall(bytes, 0, bytes.size) + setDataPosition(0) + val created = creator.createFromParcel(this) + recycle() + created + } +} \ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java deleted file mode 100644 index 0630a2e515e0e..0000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java +++ /dev/null @@ -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 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 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 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 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 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 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 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 channel = - new HashMap(); - 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 broadcastChannelList = new ArrayList<>(); - for (Map.Entry 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; - } - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt new file mode 100644 index 0000000000000..870ea8d8b59c7 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt @@ -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) +} \ No newline at end of file diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp new file mode 100644 index 0000000000000..a4558f1bd40bf --- /dev/null +++ b/packages/SettingsLib/tests/unit/Android.bp @@ -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", + ], +} diff --git a/packages/SettingsLib/tests/unit/AndroidManifest.xml b/packages/SettingsLib/tests/unit/AndroidManifest.xml new file mode 100644 index 0000000000000..568f9cb2b79e4 --- /dev/null +++ b/packages/SettingsLib/tests/unit/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/packages/SettingsLib/tests/unit/OWNERS b/packages/SettingsLib/tests/unit/OWNERS new file mode 100644 index 0000000000000..66559252048bb --- /dev/null +++ b/packages/SettingsLib/tests/unit/OWNERS @@ -0,0 +1,2 @@ +# We do not guard tests - everyone is welcomed to contribute to tests. +per-file *.kt=* diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt new file mode 100644 index 0000000000000..0e3590d96a141 --- /dev/null +++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.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=" + } +} \ No newline at end of file