Merge "Consume consecutive delimiters at beginning of refilled buffer" into main
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7bac338..ee7baa1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -178,6 +178,7 @@
     <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
     <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
     <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
+    <protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" />
     <protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
     <protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" />
diff --git a/core/res/res/drawable/ic_thread_network.xml b/core/res/res/drawable/ic_thread_network.xml
new file mode 100644
index 0000000..1d7608f
--- /dev/null
+++ b/core/res/res/drawable/ic_thread_network.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2024 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M476,880Q394,879 322,847.5Q250,816 196,761.5Q142,707 111,634.5Q80,562 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,623 791.5,732.5Q703,842 563,871L563,480L607,480Q661,480 699.5,441.5Q738,403 738,349Q738,295 699.5,256.5Q661,218 607,218Q553,218 514.5,256.5Q476,295 476,349L476,393L345,393Q279,393 233,439Q187,485 187,551Q187,617 233,662.5Q279,708 345,708L345,621Q316,621 295,600.5Q274,580 274,551Q274,522 295,501Q316,480 345,480L476,480L476,880ZM563,393L563,349Q563,331 576,318Q589,305 607,305Q625,305 638,318Q651,331 651,349Q651,367 638,380Q625,393 607,393L563,393Z"/>
+</vector>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fd6158d..7bd74c8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1367,6 +1367,7 @@
   <java-symbol type="drawable" name="platlogo" />
   <java-symbol type="drawable" name="stat_notify_sync_error" />
   <java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+  <java-symbol type="drawable" name="ic_thread_network" />
   <java-symbol type="drawable" name="ic_wifi_signal_0" />
   <java-symbol type="drawable" name="ic_wifi_signal_1" />
   <java-symbol type="drawable" name="ic_wifi_signal_2" />
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index cb803f7..5045918 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -322,11 +322,11 @@
     <library name="android.hidl.manager-V1.0-java"
             file="/system/framework/android.hidl.manager-V1.0-java.jar" />
 
-    <!-- These are the standard packages that are white-listed to always have internet
+    <!-- These are the standard packages that are allowed to always have internet
          access while in power save mode, even if they aren't in the foreground. -->
     <allow-in-power-save package="com.android.providers.downloads" />
 
-    <!-- These are the standard packages that are white-listed to always have internet
+    <!-- These are the standard packages that are allowed to always have internet
          access while in data mode, even if they aren't in the foreground. -->
     <allow-in-data-usage-save package="com.android.providers.downloads" />
 
@@ -338,7 +338,7 @@
     <!-- Emergency app needs to run in the background to reliably provide safety features -->
     <allow-in-power-save package="com.android.emergency" />
 
-    <!-- Whitelist system providers -->
+    <!-- Allow system providers -->
     <!-- Calendar provider needs alarms while in idle -->
     <allow-in-power-save package="com.android.providers.calendar" />
     <allow-in-power-save-except-idle package="com.android.providers.contacts" />
diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp
index f15c153..e62678f 100644
--- a/data/keyboards/Android.bp
+++ b/data/keyboards/Android.bp
@@ -27,3 +27,27 @@
         targets: ["droidcore"],
     },
 }
+
+prebuilt_usr_keylayout {
+    name: "keylayout_data",
+    srcs: [
+        "*.kl",
+    ],
+    installable: false,
+}
+
+prebuilt_usr_keychars {
+    name: "keychars_data",
+    srcs: [
+        "*.kcm",
+    ],
+    installable: false,
+}
+
+prebuilt_usr_idc {
+    name: "idc_data",
+    srcs: [
+        "*.idc",
+    ],
+    installable: false,
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
index 1ff2bef..5e3bd9a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -43,5 +43,5 @@
     /**
      * Bluetooth scheme.
      */
-    public static final String SCHEME_BT_BROADCAST_METADATA = "BT:";
+    public static final String SCHEME_BT_BROADCAST_METADATA = "BLUETOOTH:UUID:184F;";
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index 9bb11f8..0c7d6f0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -31,38 +31,34 @@
 object BluetoothLeBroadcastMetadataExt {
     private const val TAG = "BtLeBroadcastMetadataExt"
 
-    // BluetoothLeBroadcastMetadata
-    private const val KEY_BT_QR_VER = "R"
-    private const val KEY_BT_ADDRESS_TYPE = "T"
-    private const val KEY_BT_DEVICE = "D"
-    private const val KEY_BT_ADVERTISING_SID = "AS"
-    private const val KEY_BT_BROADCAST_ID = "B"
+    // Data Elements for directing Broadcast Assistants
     private const val KEY_BT_BROADCAST_NAME = "BN"
-    private const val KEY_BT_PUBLIC_BROADCAST_DATA = "PM"
-    private const val KEY_BT_SYNC_INTERVAL = "SI"
-    private const val KEY_BT_BROADCAST_CODE = "C"
-    private const val KEY_BT_SUBGROUPS = "SG"
-    private const val KEY_BT_VENDOR_SPECIFIC = "V"
-    private const val KEY_BT_ANDROID_VERSION = "VN"
+    private const val KEY_BT_ADVERTISER_ADDRESS_TYPE = "AT"
+    private const val KEY_BT_ADVERTISER_ADDRESS = "AD"
+    private const val KEY_BT_BROADCAST_ID = "BI"
+    private const val KEY_BT_BROADCAST_CODE = "BC"
+    private const val KEY_BT_STREAM_METADATA = "MD"
+    private const val KEY_BT_STANDARD_QUALITY = "SQ"
+    private const val KEY_BT_HIGH_QUALITY = "HQ"
 
-    // Subgroup data
+    // Extended Bluetooth URI Data Elements
+    private const val KEY_BT_ADVERTISING_SID = "AS"
+    private const val KEY_BT_PA_INTERVAL = "PI"
+    private const val KEY_BT_NUM_SUBGROUPS = "NS"
+
+    // Subgroup data elements
     private const val KEY_BTSG_BIS_SYNC = "BS"
-    private const val KEY_BTSG_BIS_MASK = "BM"
-    private const val KEY_BTSG_AUDIO_CONTENT = "AC"
+    private const val KEY_BTSG_NUM_BISES = "NB"
+    private const val KEY_BTSG_METADATA = "SM"
 
-    // Vendor specific data
-    private const val KEY_BTVSD_COMPANY_ID = "VI"
-    private const val KEY_BTVSD_VENDOR_DATA = "VD"
+    // Vendor specific data, not being used
+    private const val KEY_BTVSD_VENDOR_DATA = "VS"
 
     private const val DELIMITER_KEY_VALUE = ":"
-    private const val DELIMITER_BT_LEVEL_1 = ";"
-    private const val DELIMITER_BT_LEVEL_2 = ","
+    private const val DELIMITER_ELEMENT = ";"
 
     private const val SUFFIX_QR_CODE = ";;"
 
-    private const val ANDROID_VER = "U"
-    private const val QR_CODE_VER = 0x010000
-
     // BT constants
     private const val BIS_SYNC_MAX_CHANNEL = 32
     private const val BIS_SYNC_NO_PREFERENCE = 0xFFFFFFFFu
@@ -71,33 +67,55 @@
     /**
      * Converts [BluetoothLeBroadcastMetadata] to QR code string.
      *
-     * QR code string will prefix with "BT:".
+     * QR code string will prefix with "BLUETOOTH:UUID:184F".
      */
     fun BluetoothLeBroadcastMetadata.toQrCodeString(): String {
         val entries = mutableListOf<Pair<String, String>>()
-        entries.add(Pair(KEY_BT_QR_VER, QR_CODE_VER.toString()))
-        entries.add(Pair(KEY_BT_ADDRESS_TYPE, this.sourceAddressType.toString()))
-        entries.add(Pair(KEY_BT_DEVICE, this.sourceDevice.address.replace(":", "-")))
-        entries.add(Pair(KEY_BT_ADVERTISING_SID, this.sourceAdvertisingSid.toString()))
-        entries.add(Pair(KEY_BT_BROADCAST_ID, this.broadcastId.toString()))
-        if (this.broadcastName != null) {
-            entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString(
-                this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)))
-        }
-        if (this.publicBroadcastMetadata != null) {
-            entries.add(Pair(KEY_BT_PUBLIC_BROADCAST_DATA, Base64.encodeToString(
-                this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP)))
-        }
-        entries.add(Pair(KEY_BT_SYNC_INTERVAL, this.paSyncInterval.toString()))
+        // Generate data elements for directing Broadcast Assistants
+        require(this.broadcastName != null) { "Broadcast name is mandatory for QR code" }
+        entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString(
+            this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)))
+        entries.add(Pair(KEY_BT_ADVERTISER_ADDRESS_TYPE, this.sourceAddressType.toString()))
+        entries.add(Pair(KEY_BT_ADVERTISER_ADDRESS, this.sourceDevice.address.replace(":", "")))
+        entries.add(Pair(KEY_BT_BROADCAST_ID, String.format("%X", this.broadcastId.toLong())))
         if (this.broadcastCode != null) {
             entries.add(Pair(KEY_BT_BROADCAST_CODE,
                 Base64.encodeToString(this.broadcastCode, Base64.NO_WRAP)))
         }
+        if (this.publicBroadcastMetadata != null &&
+                this.publicBroadcastMetadata?.rawMetadata?.size != 0) {
+            entries.add(Pair(KEY_BT_STREAM_METADATA, Base64.encodeToString(
+                this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP)))
+        }
+        if ((this.audioConfigQuality and
+                BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD) != 0) {
+            entries.add(Pair(KEY_BT_STANDARD_QUALITY, "1"))
+        }
+        if ((this.audioConfigQuality and
+                BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_HIGH) != 0) {
+            entries.add(Pair(KEY_BT_HIGH_QUALITY, "1"))
+        }
+
+        // Generate extended Bluetooth URI data elements
+        entries.add(Pair(KEY_BT_ADVERTISING_SID,
+                String.format("%X", this.sourceAdvertisingSid.toLong())))
+        entries.add(Pair(KEY_BT_PA_INTERVAL, String.format("%X", this.paSyncInterval.toLong())))
+        entries.add(Pair(KEY_BT_NUM_SUBGROUPS, String.format("%X", this.subgroups.size.toLong())))
+
         this.subgroups.forEach {
-                subgroup -> entries.add(Pair(KEY_BT_SUBGROUPS, subgroup.toQrCodeString())) }
-        entries.add(Pair(KEY_BT_ANDROID_VERSION, ANDROID_VER))
+            val (bisSync, bisCount) = getBisSyncFromChannels(it.channels)
+            entries.add(Pair(KEY_BTSG_BIS_SYNC, String.format("%X", bisSync.toLong())))
+            if (bisCount > 0u) {
+                entries.add(Pair(KEY_BTSG_NUM_BISES, String.format("%X", bisCount.toLong())))
+            }
+            if (it.contentMetadata.rawMetadata.size != 0) {
+                entries.add(Pair(KEY_BTSG_METADATA,
+                    Base64.encodeToString(it.contentMetadata.rawMetadata, Base64.NO_WRAP)))
+            }
+        }
+
         val qrCodeString = SCHEME_BT_BROADCAST_METADATA +
-                entries.toQrCodeString(DELIMITER_BT_LEVEL_1) + SUFFIX_QR_CODE
+                entries.toQrCodeString(DELIMITER_ELEMENT) + SUFFIX_QR_CODE
         Log.d(TAG, "Generated QR string : $qrCodeString")
         return qrCodeString
     }
@@ -105,7 +123,7 @@
     /**
      * Converts QR code string to [BluetoothLeBroadcastMetadata].
      *
-     * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:".
+     * QR code string should prefix with "BLUETOOTH:UUID:184F".
      */
     fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? {
         if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) {
@@ -126,15 +144,6 @@
         }
     }
 
-    private fun BluetoothLeBroadcastSubgroup.toQrCodeString(): String {
-        val entries = mutableListOf<Pair<String, String>>()
-        entries.add(Pair(KEY_BTSG_BIS_SYNC, getBisSyncFromChannels(this.channels).toString()))
-        entries.add(Pair(KEY_BTSG_BIS_MASK, getBisMaskFromChannels(this.channels).toString()))
-        entries.add(Pair(KEY_BTSG_AUDIO_CONTENT,
-            Base64.encodeToString(this.contentMetadata.rawMetadata, Base64.NO_WRAP)))
-        return entries.toQrCodeString(DELIMITER_BT_LEVEL_2)
-    }
-
     private fun List<Pair<String, String>>.toQrCodeString(delimiter: String): String {
         val entryStrings = this.map{ it.first + DELIMITER_KEY_VALUE + it.second }
         return entryStrings.joinToString(separator = delimiter)
@@ -143,23 +152,29 @@
     @TargetApi(Build.VERSION_CODES.TIRAMISU)
     private fun parseQrCodeToMetadata(input: String): BluetoothLeBroadcastMetadata {
         // Split into a list of list
-        val level1Fields = input.split(DELIMITER_BT_LEVEL_1)
+        val elementFields = input.split(DELIMITER_ELEMENT)
             .map{it.split(DELIMITER_KEY_VALUE, limit = 2)}
-        var qrCodeVersion = -1
+
         var sourceAddrType = BluetoothDevice.ADDRESS_TYPE_UNKNOWN
         var sourceAddrString: String? = null
         var sourceAdvertiserSid = -1
         var broadcastId = -1
         var broadcastName: String? = null
-        var publicBroadcastMetadata: BluetoothLeAudioContentMetadata? = null
+        var streamMetadata: BluetoothLeAudioContentMetadata? = null
         var paSyncInterval = -1
         var broadcastCode: ByteArray? = null
-        // List of VendorID -> Data Pairs
-        var vendorDataList = mutableListOf<Pair<Int, ByteArray?>>()
-        var androidVersion: String? = null
+        var audioConfigQualityStandard = -1
+        var audioConfigQualityHigh = -1
+        var numSubgroups = -1
+
+        // List of subgroup data
+        var subgroupBisSyncList = mutableListOf<UInt>()
+        var subgroupNumOfBisesList = mutableListOf<UInt>()
+        var subgroupMetadataList = mutableListOf<ByteArray?>()
+
         val builder = BluetoothLeBroadcastMetadata.Builder()
 
-        for (field: List<String> in level1Fields) {
+        for (field: List<String> in elementFields) {
             if (field.isEmpty()) {
                 continue
             }
@@ -167,190 +182,200 @@
             // Ignore 3rd value and after
             val value = if (field.size > 1) field[1] else ""
             when (key) {
-                KEY_BT_QR_VER -> {
-                    require(qrCodeVersion == -1) { "Duplicate qrCodeVersion: $input" }
-                    qrCodeVersion = value.toInt()
+                // Parse data elements for directing Broadcast Assistants
+                KEY_BT_BROADCAST_NAME -> {
+                    require(broadcastName == null) { "Duplicate broadcastName: $input" }
+                    broadcastName = String(Base64.decode(value, Base64.NO_WRAP))
                 }
-                KEY_BT_ADDRESS_TYPE -> {
+                KEY_BT_ADVERTISER_ADDRESS_TYPE -> {
                     require(sourceAddrType == BluetoothDevice.ADDRESS_TYPE_UNKNOWN) {
                         "Duplicate sourceAddrType: $input"
                     }
                     sourceAddrType = value.toInt()
                 }
-                KEY_BT_DEVICE -> {
+                KEY_BT_ADVERTISER_ADDRESS -> {
                     require(sourceAddrString == null) { "Duplicate sourceAddr: $input" }
-                    sourceAddrString = value.replace("-", ":")
-                }
-                KEY_BT_ADVERTISING_SID -> {
-                    require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" }
-                    sourceAdvertiserSid = value.toInt()
+                    sourceAddrString = value.chunked(2).joinToString(":")
                 }
                 KEY_BT_BROADCAST_ID -> {
                     require(broadcastId == -1) { "Duplicate broadcastId: $input" }
-                    broadcastId = value.toInt()
-                }
-                KEY_BT_BROADCAST_NAME -> {
-                    require(broadcastName == null) { "Duplicate broadcastName: $input" }
-                    broadcastName = String(Base64.decode(value, Base64.NO_WRAP))
-                }
-                KEY_BT_PUBLIC_BROADCAST_DATA -> {
-                    require(publicBroadcastMetadata == null) {
-                        "Duplicate publicBroadcastMetadata $input"
-                    }
-                    publicBroadcastMetadata = BluetoothLeAudioContentMetadata
-                        .fromRawBytes(Base64.decode(value, Base64.NO_WRAP))
-                }
-                KEY_BT_SYNC_INTERVAL -> {
-                    require(paSyncInterval == -1) { "Duplicate paSyncInterval: $input" }
-                    paSyncInterval = value.toInt()
+                    broadcastId = value.toInt(16)
                 }
                 KEY_BT_BROADCAST_CODE -> {
                     require(broadcastCode == null) { "Duplicate broadcastCode: $input" }
-                    broadcastCode = Base64.decode(value, Base64.NO_WRAP)
+
+                    broadcastCode = Base64.decode(value.dropLastWhile { it.equals(0.toByte()) }
+                            .toByteArray(), Base64.NO_WRAP)
                 }
-                KEY_BT_ANDROID_VERSION -> {
-                    require(androidVersion == null) { "Duplicate androidVersion: $input" }
-                    androidVersion = value
-                    Log.i(TAG, "QR code Android version: $androidVersion")
+                KEY_BT_STREAM_METADATA -> {
+                    require(streamMetadata == null) {
+                        "Duplicate streamMetadata $input"
+                    }
+                    streamMetadata = BluetoothLeAudioContentMetadata
+                        .fromRawBytes(Base64.decode(value, Base64.NO_WRAP))
                 }
-                // Repeatable
-                KEY_BT_SUBGROUPS -> {
-                    builder.addSubgroup(parseSubgroupData(value))
+                KEY_BT_STANDARD_QUALITY -> {
+                    require(audioConfigQualityStandard == -1) {
+                        "Duplicate audioConfigQualityStandard: $input"
+                    }
+                    audioConfigQualityStandard = value.toInt()
                 }
-                // Repeatable
-                KEY_BT_VENDOR_SPECIFIC -> {
-                    vendorDataList.add(parseVendorData(value))
+                KEY_BT_HIGH_QUALITY -> {
+                    require(audioConfigQualityHigh == -1) {
+                        "Duplicate audioConfigQualityHigh: $input"
+                    }
+                    audioConfigQualityHigh = value.toInt()
+                }
+
+                // Parse extended Bluetooth URI data elements
+                KEY_BT_ADVERTISING_SID -> {
+                    require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" }
+                    sourceAdvertiserSid = value.toInt(16)
+                }
+                KEY_BT_PA_INTERVAL -> {
+                    require(paSyncInterval == -1) { "Duplicate paSyncInterval: $input" }
+                    paSyncInterval = value.toInt(16)
+                }
+                KEY_BT_NUM_SUBGROUPS -> {
+                    require(numSubgroups == -1) { "Duplicate numSubgroups: $input" }
+                    numSubgroups = value.toInt(16)
+                }
+
+                // Repeatable subgroup elements
+                KEY_BTSG_BIS_SYNC -> {
+                    subgroupBisSyncList.add(value.toUInt(16))
+                }
+                KEY_BTSG_NUM_BISES -> {
+                    subgroupNumOfBisesList.add(value.toUInt(16))
+                }
+                KEY_BTSG_METADATA -> {
+                    subgroupMetadataList.add(Base64.decode(value, Base64.NO_WRAP))
                 }
             }
         }
-        Log.d(TAG, "parseQrCodeToMetadata: sourceAddrType=$sourceAddrType, " +
+        Log.d(TAG, "parseQrCodeToMetadata: main data elements sourceAddrType=$sourceAddrType, " +
                 "sourceAddr=$sourceAddrString, sourceAdvertiserSid=$sourceAdvertiserSid, " +
                 "broadcastId=$broadcastId, broadcastName=$broadcastName, " +
-                "publicBroadcastMetadata=${publicBroadcastMetadata != null}, " +
+                "streamMetadata=${streamMetadata != null}, " +
                 "paSyncInterval=$paSyncInterval, " +
-                "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}")
-        Log.d(TAG, "Not used in current code, but part of the specification: " +
-                "qrCodeVersion=$qrCodeVersion, androidVersion=$androidVersion, " +
-                "vendorDataListSize=${vendorDataList.size}")
+                "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}, " +
+                "audioConfigQualityStandard=$audioConfigQualityStandard, " +
+                "audioConfigQualityHigh=$audioConfigQualityHigh")
+
         val adapter = BluetoothAdapter.getDefaultAdapter()
+        // Check parsed elements data
+        require(broadcastName != null) {
+            "broadcastName($broadcastName) must present in QR code string"
+        }
+        var addr = sourceAddrString
+        var addrType = sourceAddrType
+        if (sourceAddrString != null) {
+            require(sourceAddrType != BluetoothDevice.ADDRESS_TYPE_UNKNOWN) {
+                "sourceAddrType($sourceAddrType) must present if address present"
+            }
+        } else {
+            // Use placeholder device if not present
+            addr = "FF:FF:FF:FF:FF:FF"
+            addrType = BluetoothDevice.ADDRESS_TYPE_RANDOM
+        }
+        val device = adapter.getRemoteLeDevice(requireNotNull(addr), addrType)
+
         // add source device and set broadcast code
-        val device = adapter.getRemoteLeDevice(requireNotNull(sourceAddrString), sourceAddrType)
+        var audioConfigQuality = BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_NONE or
+                (if (audioConfigQualityStandard != -1) audioConfigQualityStandard else 0) or
+                (if (audioConfigQualityHigh != -1) audioConfigQualityHigh else 0)
+
+        // process subgroup data
+        // metadata should include at least 1 subgroup for metadata, add a placeholder group if not present
+        numSubgroups = if (numSubgroups > 0) numSubgroups else 1
+        for (i in 0 until numSubgroups) {
+            val bisSync = subgroupBisSyncList.getOrNull(i)
+            val bisNum = subgroupNumOfBisesList.getOrNull(i)
+            val metadata = subgroupMetadataList.getOrNull(i)
+
+            val channels = convertToChannels(bisSync, bisNum)
+            val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
+                    .setAudioLocation(0).build()
+            val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
+                setCodecId(SUBGROUP_LC3_CODEC_ID)
+                setCodecSpecificConfig(audioCodecConfigMetadata)
+                setContentMetadata(
+                        BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0)))
+                channels.forEach(::addChannel)
+            }.build()
+
+            Log.d(TAG, "parseQrCodeToMetadata: subgroup $i elements bisSync=$bisSync, " +
+                    "bisNum=$bisNum, metadata=${metadata != null}")
+
+            builder.addSubgroup(subgroup)
+        }
+
         builder.apply {
-            setSourceDevice(device, sourceAddrType)
+            setSourceDevice(device, addrType)
             setSourceAdvertisingSid(sourceAdvertiserSid)
             setBroadcastId(broadcastId)
             setBroadcastName(broadcastName)
-            setPublicBroadcast(publicBroadcastMetadata != null)
-            setPublicBroadcastMetadata(publicBroadcastMetadata)
+            // QR code should set PBP(public broadcast profile) for auracast
+            setPublicBroadcast(true)
+            setPublicBroadcastMetadata(streamMetadata)
             setPaSyncInterval(paSyncInterval)
             setEncrypted(broadcastCode != null)
             setBroadcastCode(broadcastCode)
             // Presentation delay is unknown and not useful when adding source
             // Broadcast sink needs to sync to the Broadcast source to get presentation delay
             setPresentationDelayMicros(0)
+            setAudioConfigQuality(audioConfigQuality)
         }
         return builder.build()
     }
 
-    private fun parseSubgroupData(input: String): BluetoothLeBroadcastSubgroup {
-        Log.d(TAG, "parseSubgroupData: $input")
-        val fields = input.split(DELIMITER_BT_LEVEL_2)
-        var bisSync: UInt? = null
-        var bisMask: UInt? = null
-        var metadata: ByteArray? = null
-
-        fields.forEach { field ->
-            val(key, value) = field.split(DELIMITER_KEY_VALUE)
-            when (key) {
-                KEY_BTSG_BIS_SYNC -> {
-                    require(bisSync == null) { "Duplicate bisSync: $input" }
-                    bisSync = value.toUInt()
-                }
-                KEY_BTSG_BIS_MASK -> {
-                    require(bisMask == null) { "Duplicate bisMask: $input" }
-                    bisMask = value.toUInt()
-                }
-                KEY_BTSG_AUDIO_CONTENT -> {
-                    require(metadata == null) { "Duplicate metadata: $input" }
-                    metadata = Base64.decode(value, Base64.NO_WRAP)
-                }
-            }
-        }
-        val channels = convertToChannels(requireNotNull(bisSync), requireNotNull(bisMask))
-        val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
-                .setAudioLocation(0).build()
-        return BluetoothLeBroadcastSubgroup.Builder().apply {
-            setCodecId(SUBGROUP_LC3_CODEC_ID)
-            setCodecSpecificConfig(audioCodecConfigMetadata)
-            setContentMetadata(
-                    BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0)))
-            channels.forEach(::addChannel)
-        }.build()
-    }
-
-    private fun parseVendorData(input: String): Pair<Int, ByteArray?> {
-        var companyId = -1
-        var data: ByteArray? = null
-        val fields = input.split(DELIMITER_BT_LEVEL_2)
-        fields.forEach { field ->
-            val(key, value) = field.split(DELIMITER_KEY_VALUE)
-            when (key) {
-                KEY_BTVSD_COMPANY_ID -> {
-                    require(companyId == -1) { "Duplicate companyId: $input" }
-                    companyId = value.toInt()
-                }
-                KEY_BTVSD_VENDOR_DATA -> {
-                    require(data == null) { "Duplicate data: $input" }
-                    data = Base64.decode(value, Base64.NO_WRAP)
-                }
-            }
-        }
-        return Pair(companyId, data)
-    }
-
-    private fun getBisSyncFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt {
+    private fun getBisSyncFromChannels(
+        channels: List<BluetoothLeBroadcastChannel>
+    ): Pair<UInt, UInt> {
         var bisSync = 0u
-        // channel index starts from 1
-        channels.forEach { channel ->
-            if (channel.isSelected && channel.channelIndex > 0) {
-                bisSync = bisSync or (1u shl (channel.channelIndex - 1))
-            }
-        }
-        // No channel is selected means no preference on Android platform
-        return if (bisSync == 0u) BIS_SYNC_NO_PREFERENCE else bisSync
-    }
-
-    private fun getBisMaskFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt {
-        var bisMask = 0u
+        var bisCount = 0u
         // channel index starts from 1
         channels.forEach { channel ->
             if (channel.channelIndex > 0) {
-                bisMask = bisMask or (1u shl (channel.channelIndex - 1))
+                bisCount++
+                if (channel.isSelected) {
+                    bisSync = bisSync or (1u shl (channel.channelIndex - 1))
+                }
             }
         }
-        return bisMask
+        // No channel is selected means no preference on Android platform
+        return if (bisSync == 0u) Pair(BIS_SYNC_NO_PREFERENCE, bisCount)
+                else Pair(bisSync, bisCount)
     }
 
-    private fun convertToChannels(bisSync: UInt, bisMask: UInt):
-            List<BluetoothLeBroadcastChannel> {
-        Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisMask=$bisMask")
-        var selectionMask = bisSync
-        if (bisSync != BIS_SYNC_NO_PREFERENCE) {
-            require(bisMask == (bisMask or bisSync)) {
-                "bisSync($bisSync) must select a subset of bisMask($bisMask) if it has preferences"
-            }
-        } else {
-            // No channel preference means no channel is selected
-            selectionMask = 0u
-        }
+    private fun convertToChannels(
+        bisSync: UInt?,
+        bisNum: UInt?
+    ): List<BluetoothLeBroadcastChannel> {
+        Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisNum=$bisNum")
+        // if no BIS_SYNC or BIS_NUM available or BIS_SYNC is no preference
+        // return empty channel map with one placeholder channel
+        var selectedChannels = if (bisSync != null && bisNum != null) bisSync else 0u
         val channels = mutableListOf<BluetoothLeBroadcastChannel>()
         val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
                 .setAudioLocation(0).build()
+
+        if (bisSync == BIS_SYNC_NO_PREFERENCE || selectedChannels == 0u) {
+            // No channel preference means no channel is selected
+            // Generate one placeholder channel for metadata
+            val channel = BluetoothLeBroadcastChannel.Builder().apply {
+                setSelected(false)
+                setChannelIndex(1)
+                setCodecMetadata(audioCodecConfigMetadata)
+            }
+            return listOf(channel.build())
+        }
+
         for (i in 0 until BIS_SYNC_MAX_CHANNEL) {
             val channelMask = 1u shl i
-            if ((bisMask and channelMask) != 0u) {
+            if ((selectedChannels and channelMask) != 0u) {
                 val channel = BluetoothLeBroadcastChannel.Builder().apply {
-                    setSelected((selectionMask and channelMask) != 0u)
+                    setSelected(true)
                     setChannelIndex(i + 1)
                     setCodecMetadata(audioCodecConfigMetadata)
                 }
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
index 27d7078..1ad20dc 100644
--- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
@@ -32,7 +32,7 @@
 class BluetoothLeBroadcastMetadataExtTest {
 
     @Test
-    fun toQrCodeString() {
+    fun toQrCodeString_encrypted() {
         val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
             setCodecId(0x6)
             val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
@@ -70,6 +70,37 @@
     }
 
     @Test
+    fun toQrCodeString_non_encrypted() {
+        val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
+            setCodecId(0x6)
+            val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
+            setContentMetadata(BluetoothLeAudioContentMetadata.Builder()
+                .build())
+            setCodecSpecificConfig(audioCodecConfigMetadata)
+            addChannel(BluetoothLeBroadcastChannel.Builder().apply {
+                setSelected(true)
+                setChannelIndex(1)
+                setCodecMetadata(audioCodecConfigMetadata)
+            }.build())
+        }.build()
+
+        val metadata = BluetoothLeBroadcastMetadata.Builder().apply {
+            setSourceDevice(DevicePublic, BluetoothDevice.ADDRESS_TYPE_PUBLIC)
+            setSourceAdvertisingSid(1)
+            setBroadcastId(0xDE51E9)
+            setBroadcastName("Hockey")
+            setAudioConfigQuality(BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD)
+            setPaSyncInterval(0xFFFF)
+            setEncrypted(false)
+            addSubgroup(subgroup)
+        }.build()
+
+        val qrCodeString = metadata.toQrCodeString()
+
+        assertThat(qrCodeString).isEqualTo(QR_CODE_STRING_NON_ENCRYPTED)
+    }
+
+    @Test
     fun toQrCodeString_NoChannelSelected() {
         val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
             setCodecId(0x6)
@@ -102,6 +133,7 @@
             addSubgroup(subgroup)
         }.build()
 
+        // if no channel is selected, no preference(0xFFFFFFFFu) will be set in BIS
         val qrCodeString = metadata.toQrCodeString()
 
         val parsedMetadata =
@@ -111,13 +143,11 @@
         assertThat(parsedMetadata.subgroups).isNotNull()
         assertThat(parsedMetadata.subgroups.size).isEqualTo(1)
         assertThat(parsedMetadata.subgroups[0].channels).isNotNull()
-        assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2)
+        assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(1)
         assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isFalse()
-        // Input order does not matter due to parsing through bisMask
+        // placeholder channel with not selected
         assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1)
         assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse()
-        assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2)
-        assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isFalse()
     }
 
     @Test
@@ -162,13 +192,11 @@
         assertThat(parsedMetadata.subgroups).isNotNull()
         assertThat(parsedMetadata.subgroups.size).isEqualTo(1)
         assertThat(parsedMetadata.subgroups[0].channels).isNotNull()
-        // Only selected channel can be recovered
-        assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2)
+        // Only selected channel can be recovered, non-selected ones will be ignored
+        assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(1)
         assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isTrue()
-        assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1)
-        assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse()
-        assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2)
-        assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isTrue()
+        assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(2)
+        assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isTrue()
     }
 
     @Test
@@ -180,16 +208,34 @@
         assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
     }
 
+    @Test
+    fun decodeAndEncodeAgain_sameString_non_encrypted() {
+        val metadata =
+                BluetoothLeBroadcastMetadataExt
+                        .convertToBroadcastMetadata(QR_CODE_STRING_NON_ENCRYPTED)!!
+
+        val qrCodeString = metadata.toQrCodeString()
+
+        assertThat(qrCodeString).isEqualTo(QR_CODE_STRING_NON_ENCRYPTED)
+    }
+
     private companion object {
         const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
+        const val TEST_DEVICE_ADDRESS_PUBLIC = "AA:BB:CC:00:11:22"
 
         val Device: BluetoothDevice =
             BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS,
                 BluetoothDevice.ADDRESS_TYPE_RANDOM)
 
+        val DevicePublic: BluetoothDevice =
+            BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS_PUBLIC,
+                BluetoothDevice.ADDRESS_TYPE_PUBLIC)
+
         const val QR_CODE_STRING =
-            "BT:R:65536;T:1;D:00-A1-A1-A1-A1-A1;AS:1;B:123456;BN:VGVzdA==;" +
-            "PM:BgNwVGVzdA==;SI:160;C:VGVzdENvZGU=;SG:BS:3,BM:3,AC:BQNUZXN0BARlbmc=;" +
-            "VN:U;;"
+            "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" +
+            "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"
+        const val QR_CODE_STRING_NON_ENCRYPTED =
+            "BLUETOOTH:UUID:184F;BN:SG9ja2V5;AT:0;AD:AABBCC001122;BI:DE51E9;SQ:1;AS:1;PI:FFFF;" +
+            "NS:1;BS:1;NB:1;;"
     }
 }
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dd49260..9dfca4d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -432,10 +432,10 @@
 import android.security.IKeyChainService;
 import android.security.KeyChain;
 import android.security.KeyChain.KeyChainConnection;
-import android.security.KeyStore;
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keystore.AttestationUtils;
 import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
 import android.security.keystore.ParcelableKeyGenParameterSpec;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.telecom.TelecomManager;
@@ -6248,7 +6248,7 @@
                     KeyChain.bindAsUser(mContext, caller.getUserHandle());
             try {
                 IKeyChainService keyChain = keyChainConnection.getService();
-                if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyStore.UID_SELF)) {
+                if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyProperties.UID_SELF)) {
                     logInstallKeyPairFailure(caller, isCredentialManagementApp);
                     return false;
                 }
@@ -6588,7 +6588,7 @@
         }
         // As the caller will be granted access to the key, ensure no UID was specified, as
         // it will not have the desired effect.
-        if (keySpec.getUid() != KeyStore.UID_SELF) {
+        if (keySpec.getUid() != KeyProperties.UID_SELF) {
             Slogf.e(LOG_TAG, "Only the caller can be granted access to the generated keypair.");
             logGenerateKeyPairFailure(caller, isCredentialManagementApp);
             return false;