Merge "Move the QrCodeGenerator from Settings to SettingsLib for Wifi and BT"
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
new file mode 100644
index 0000000..3ce7a0e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+public final class BluetoothBroadcastUtils {
+
+    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 = "D:";
+    static final String PREFIX_BT_SUBGROUPS = "G:";
+    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 = ";";
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java
new file mode 100644
index 0000000..9df0f4d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java
@@ -0,0 +1,43 @@
+/*
+ * 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.BluetoothLeAudioContentMetadata;
+
+public class LocalBluetoothLeAudioContentMetadata {
+
+    private static final String TAG = "LocalBluetoothLeAudioContentMetadata";
+    private final BluetoothLeAudioContentMetadata mContentMetadata;
+    private final String mLanguage;
+    private final byte[] mRawMetadata;
+    private String mProgramInfo;
+
+    LocalBluetoothLeAudioContentMetadata(BluetoothLeAudioContentMetadata contentMetadata) {
+        mContentMetadata = contentMetadata;
+        mProgramInfo = contentMetadata.getProgramInfo();
+        mLanguage = contentMetadata.getLanguage();
+        mRawMetadata = contentMetadata.getRawMetadata();
+    }
+
+    public void setProgramInfo(String programInfo) {
+        mProgramInfo = programInfo;
+    }
+
+    public String getProgramInfo() {
+        return mProgramInfo;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
new file mode 100644
index 0000000..bb47c5e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -0,0 +1,192 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * LocalBluetoothLeBroadcast provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothLeBroadcast}.
+ */
+public class LocalBluetoothLeBroadcast implements BluetoothLeBroadcast.Callback {
+
+    private static final String TAG = "LocalBluetoothLeBroadcast";
+    private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+    private static final boolean DEBUG = BluetoothUtils.D;
+
+    private BluetoothLeBroadcast mBluetoothLeBroadcast;
+    private LocalBluetoothProfileManager mProfileManager;
+    private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
+    private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
+    private BluetoothLeAudioContentMetadata.Builder mBuilder;
+    private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
+    private boolean mIsProfileReady;
+
+    private final ServiceListener mServiceListener = new ServiceListener() {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service connected");
+                }
+                mBluetoothLeBroadcast = (BluetoothLeBroadcast) proxy;
+                mProfileManager.callServiceConnectedListeners();
+                mIsProfileReady = true;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service disconnected");
+                }
+                mIsProfileReady = false;
+            }
+        }
+    };
+
+    LocalBluetoothLeBroadcast(Context context, LocalBluetoothProfileManager profileManager) {
+        mProfileManager = profileManager;
+        BluetoothAdapter.getDefaultAdapter().
+                getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
+        mBuilder = new BluetoothLeAudioContentMetadata.Builder();
+    }
+
+    public void startBroadcast(byte[] broadcastCode, String language,
+            String programInfo) {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast.");
+                return;
+            }
+            Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
+        }
+        buildContentMetadata(language, programInfo);
+        mBluetoothLeBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata, broadcastCode);
+    }
+
+    public void stopBroadcast() {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast.");
+                return;
+            }
+            Log.d(TAG, "stopBroadcast()");
+        }
+        mBluetoothLeBroadcast.stopBroadcast(mBroadcastId);
+    }
+
+    public void updateBroadcast(String language, String programInfo) {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
+                return;
+            }
+            Log.d(TAG,
+                    "updateBroadcast: language = " + language + " ,programInfo = " + programInfo);
+        }
+        mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
+        mBluetoothLeBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+    }
+
+    private void buildContentMetadata(String language, String programInfo) {
+        mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo(
+                programInfo).build();
+    }
+
+    public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() {
+        return new LocalBluetoothLeBroadcastMetadata(mBluetoothLeBroadcastMetadata);
+    }
+
+    @Override
+    public void onBroadcastStarted(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastStarted(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastStartFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onBroadcastMetadataChanged(int broadcastId,
+            @NonNull BluetoothLeBroadcastMetadata metadata) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
+        }
+        mBluetoothLeBroadcastMetadata = metadata;
+    }
+
+    @Override
+    public void onBroadcastStopped(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastStopped(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastStopFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onBroadcastUpdated(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastUpdateFailed(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = "
+                            + broadcastId);
+        }
+    }
+
+    @Override
+    public void onPlaybackStarted(int reason, int broadcastId) {
+    }
+
+    @Override
+    public void onPlaybackStopped(int reason, int broadcastId) {
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
new file mode 100644
index 0000000..d904265
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -0,0 +1,214 @@
+/*
+ * 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.annotation.NonNull;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothLeBroadcastAssistant}.
+ */
+public class LocalBluetoothLeBroadcastAssistant implements
+        BluetoothLeBroadcastAssistant.Callback {
+
+    private static final String TAG = "LocalBluetoothLeBroadcastAssistant";
+    private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+    private static final boolean DEBUG = BluetoothUtils.D;
+
+    private LocalBluetoothProfileManager mProfileManager;
+    private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant;
+    private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
+    private BluetoothLeBroadcastMetadata.Builder mBuilder;
+    private boolean mIsProfileReady;
+
+    private final ServiceListener mServiceListener = new ServiceListener() {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service connected");
+                }
+                mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+                mProfileManager.callServiceConnectedListeners();
+                mIsProfileReady = true;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service disconnected");
+                }
+                mIsProfileReady = false;
+            }
+        }
+    };
+
+    LocalBluetoothLeBroadcastAssistant(Context context,
+            LocalBluetoothProfileManager profileManager) {
+        mProfileManager = profileManager;
+        BluetoothAdapter.getDefaultAdapter().
+                getProfileProxy(context, mServiceListener,
+                        BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+        mBuilder = new BluetoothLeBroadcastMetadata.Builder();
+    }
+
+    public void addSource(@NonNull BluetoothDevice sink, int sourceAddressType,
+            int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId,
+            int paSyncInterval, boolean isEncrypted, byte[] broadcastCode,
+            BluetoothDevice sourceDevice, boolean isGroupOp) {
+        if (DEBUG) {
+            Log.d(TAG, "addSource()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        buildMetadata(sourceAddressType, presentationDelayMicros, sourceAdvertisingSid, broadcastId,
+                paSyncInterval, isEncrypted, broadcastCode, sourceDevice);
+        mBluetoothLeBroadcastAssistant.addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp);
+    }
+
+    private void buildMetadata(int sourceAddressType, int presentationDelayMicros,
+            int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted,
+            byte[] broadcastCode, BluetoothDevice sourceDevice) {
+        mBluetoothLeBroadcastMetadata =
+                mBuilder.setSourceDevice(sourceDevice, sourceAddressType)
+                        .setSourceAdvertisingSid(sourceAdvertisingSid)
+                        .setBroadcastId(broadcastId)
+                        .setPaSyncInterval(paSyncInterval)
+                        .setEncrypted(isEncrypted)
+                        .setBroadcastCode(broadcastCode)
+                        .setPresentationDelayMicros(presentationDelayMicros)
+                        .build();
+    }
+
+    public void removeSource(@NonNull BluetoothDevice sink, int sourceId) {
+        if (DEBUG) {
+            Log.d(TAG, "removeSource()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId);
+    }
+
+    public void startSearchingForSources(@NonNull List<android.bluetooth.le.ScanFilter> filters) {
+        if (DEBUG) {
+            Log.d(TAG, "startSearchingForSources()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        mBluetoothLeBroadcastAssistant.startSearchingForSources(filters);
+    }
+
+    @Override
+    public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceAdded(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+
+    }
+
+    @Override
+    public void onSourceAddFailed(@NonNull BluetoothDevice sink,
+            @NonNull BluetoothLeBroadcastMetadata source, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceAddFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceRemoved(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+    }
+
+    @Override
+    public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceRemoveFailed(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+    }
+
+    @Override
+    public void onSearchStarted(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStarted(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStartFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStartFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStopped(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStopped(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStopFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStopFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {
+    }
+
+    @Override
+    public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+    }
+
+    @Override
+    public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+    }
+
+    @Override
+    public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
+            @NonNull BluetoothLeBroadcastReceiveState state) {
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
new file mode 100644
index 0000000..cf4ba8b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
@@ -0,0 +1,298 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+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 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 long mCodecId;
+    private BluetoothLeAudioContentMetadata mContentMetadata;
+    private BluetoothLeAudioCodecConfigMetadata mConfigMetadata;
+    private BluetoothLeBroadcastChannel mChannel;
+
+    // BluetoothLeAudioCodecConfigMetadata
+    private long mAudioLocation;
+
+    // BluetoothLeAudioContentMetadata
+    private String mLanguage;
+    private String mProgramInfo;
+
+    // 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 void setBroadcastCode(byte[] code) {
+        mBroadcastCode = code;
+    }
+
+    public int getBroadcastId() {
+        return mBroadcastId;
+    }
+
+    public String convertToQrCodeString() {
+        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(mSubgroupList).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .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_REGEX);
+        Matcher match = pattern.matcher(qrCodeString);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mSourceAddressType = Integer.parseInt(resultList.get(0));
+            mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+                    resultList.get(1));
+            mSourceAdvertisingSid = Integer.parseInt(resultList.get(2));
+            mBroadcastId = Integer.parseInt(resultList.get(3));
+            mPaSyncInterval = Integer.parseInt(resultList.get(4));
+            mIsEncrypted = Boolean.valueOf(resultList.get(5));
+            mBroadcastCode = resultList.get(6).getBytes();
+            mPresentationDelayMicros = Integer.parseInt(resultList.get(7));
+            mSubgroup = convertToSubgroup(resultList.get(8));
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted qrCodeString result: " + match.group());
+            }
+
+            return new BluetoothLeBroadcastMetadata.Builder()
+                    .setSourceDevice(mSourceDevice, mSourceAddressType)
+                    .setSourceAdvertisingSid(mSourceAdvertisingSid)
+                    .setBroadcastId(mBroadcastId)
+                    .setPaSyncInterval(mPaSyncInterval)
+                    .setEncrypted(mIsEncrypted)
+                    .setBroadcastCode(mBroadcastCode)
+                    .setPresentationDelayMicros(mPresentationDelayMicros)
+                    .addSubgroup(mSubgroup)
+                    .build();
+        } 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_REGEX);
+        Matcher match = pattern.matcher(subgroupString);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mCodecId = Long.getLong(resultList.get(0));
+            mConfigMetadata = convertToConfigMetadata(resultList.get(1));
+            mContentMetadata = convertToContentMetadata(resultList.get(2));
+            mChannel = convertToChannel(resultList.get(3), mConfigMetadata);
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted subgroupString result: " + match.group());
+            }
+
+            return new BluetoothLeBroadcastSubgroup.Builder()
+                    .setCodecId(mCodecId)
+                    .setCodecSpecificConfig(mConfigMetadata)
+                    .setContentMetadata(mContentMetadata)
+                    .addChannel(mChannel)
+                    .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);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mAudioLocation = Long.getLong(resultList.get(0));
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted configMetadataString result: " + match.group());
+            }
+
+            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);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mProgramInfo = resultList.get(0);
+            mLanguage = resultList.get(1);
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted contentMetadataString result: " + match.group());
+            }
+
+            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 BluetoothLeBroadcastChannel convertToChannel(String channelString,
+            BluetoothLeAudioCodecConfigMetadata configMetadata) {
+        if (DEBUG) {
+            Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel");
+        }
+        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+        Matcher match = pattern.matcher(channelString);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mIsSelected = Boolean.valueOf(resultList.get(0));
+            mChannelIndex = Integer.parseInt(resultList.get(1));
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted channelString result: " + match.group());
+            }
+
+            return new BluetoothLeBroadcastChannel.Builder()
+                    .setSelected(mIsSelected)
+                    .setChannelIndex(mChannelIndex)
+                    .setCodecMetadata(configMetadata)
+                    .build();
+        } else {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "The match fail, can not convert it to BluetoothLeBroadcastChannel.");
+            }
+            return null;
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index bbd3c46..4e186e5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14837,7 +14837,7 @@
             }
             ITelephony service = getITelephony();
             if (service != null) {
-                return service.isMvnoMatched(getSubId(), mvnoType, mvnoMatchData);
+                return service.isMvnoMatched(getSlotIndex(), mvnoType, mvnoMatchData);
             }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "Telephony#matchesCurrentSimOperator RemoteException" + ex);
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 0b361dc..dfa0b5d 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -232,13 +232,14 @@
     }
 
     /**
-     * @return True if the profile is enabled.
+     * @return {@code true} if the profile is enabled. If the profile only has a
+     * {@link TrafficDescriptor}, but no {@link ApnSetting}, then this profile is always enabled.
      */
     public boolean isEnabled() {
         if (mApnSetting != null) {
             return mApnSetting.isEnabled();
         }
-        return false;
+        return true;
     }
 
     /**
@@ -534,7 +535,7 @@
         @Type
         private int mType = -1;
 
-        private boolean mEnabled;
+        private boolean mEnabled = true;
 
         @ApnType
         private int mSupportedApnTypesBitmask;
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 4e85d89..fb97336 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -26,6 +26,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.Annotation.ApnType;
 import android.util.Log;
@@ -129,17 +130,36 @@
         }
 
         /**
-         * Update the qualified networks list. Network availability provider must invoke this method
-         * whenever the qualified networks changes. If this method is never invoked for certain
-         * APN types, then frameworks will always use the default (i.e. cellular) data and network
-         * service.
+         * Update the suggested qualified networks list. Network availability provider must invoke
+         * this method whenever the suggested qualified networks changes. If this method is never
+         * invoked for certain APN types, then frameworks uses its own logic to determine the
+         * transport to setup the data network.
          *
-         * @param apnTypes APN types of the qualified networks. This must be a bitmask combination
-         * of {@link ApnType}.
-         * @param qualifiedNetworkTypes List of network types which are qualified for data
-         * connection setup for {@link @apnType} in the preferred order. Each element in the list
-         * is a {@link AccessNetworkType}. An empty list indicates no networks are qualified
-         * for data setup.
+         * For example, QNS can suggest frameworks setting up IMS data network on IWLAN by
+         * specifying {@link ApnSetting#TYPE_IMS} with a list containing
+         * {@link AccessNetworkType#IWLAN}.
+         *
+         * If QNS considers multiple access networks qualified for certain APN type, it can
+         * suggest frameworks by specifying the APN type with multiple access networks in the list,
+         * for example {{@link AccessNetworkType#EUTRAN}, {@link AccessNetworkType#IWLAN}}.
+         * Frameworks will then first attempt to setup data on LTE network, and If the device moves
+         * from LTE to UMTS, then frameworks will perform handover the data network to the second
+         * preferred access network if available.
+         *
+         * If the {@code qualifiedNetworkTypes} list is empty, it means QNS has no suggestion to the
+         * frameworks, and for that APN type frameworks will route the corresponding network
+         * requests to {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN}.
+         *
+         * @param apnTypes APN type(s) of the qualified networks. This must be a bitmask combination
+         * of {@link ApnType}. The same qualified networks will be applicable to all APN types
+         * specified here.
+         * @param qualifiedNetworkTypes List of access network types which are qualified for data
+         * connection setup for {@code apnTypes} in the preferred order. Empty list means QNS has no
+         * suggestion to the frameworks, and for that APN type frameworks will route the
+         * corresponding network requests to {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN}.
+         *
+         * If one of the element is invalid, for example, {@link AccessNetworkType#UNKNOWN}, then
+         * this operation becomes a no-op.
          */
         public final void updateQualifiedNetworkTypes(
                 @ApnType int apnTypes, @NonNull List<Integer> qualifiedNetworkTypes) {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index a49a61b5..afcd263 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -761,7 +761,7 @@
     public static final int ERROR_INSTALL_PROFILE = 10009;
 
     /**
-     * Failed to load profile onto eUICC due to Profile Poicly Rules.
+     * Failed to load profile onto eUICC due to Profile Policy Rules.
      * @see #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE for details
      */
     public static final int ERROR_DISALLOWED_BY_PPR = 10010;
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f5877d8..20b9f3a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2148,7 +2148,7 @@
 
     List<RadioAccessSpecifier> getSystemSelectionChannels(int subId);
 
-    boolean isMvnoMatched(int subId, int mvnoType, String mvnoMatchData);
+    boolean isMvnoMatched(int slotIndex, int mvnoType, String mvnoMatchData);
 
     /**
      * Enqueue a pending sms Consumer, which will answer with the user specified selection for an