Merge "Show LE audio toggle summary as default connection is classic" into main
diff --git a/res/layout-v34/settingslib_main_switch.xml b/res/layout-v34/settingslib_main_switch.xml
deleted file mode 100644
index 5ce4581..0000000
--- a/res/layout-v34/settingslib_main_switch.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<com.google.android.material.materialswitch.MaterialSwitch
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/switch_widget"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center_vertical"
-    android:background="@null"
-    android:clickable="false"
-    android:focusable="false"
-    android:theme="@style/Theme.Material3.DynamicColors.DayNight" />
diff --git a/res/layout-v34/settingslib_main_switch_bar.xml b/res/layout-v34/settingslib_main_switch_bar.xml
new file mode 100644
index 0000000..3a44d2a
--- /dev/null
+++ b/res/layout-v34/settingslib_main_switch_bar.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingTop="@dimen/settingslib_switchbar_margin"
+    android:paddingBottom="@dimen/settingslib_switchbar_margin"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/frame"
+        android:minHeight="@dimen/settingslib_min_switch_bar_height"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:paddingStart="@dimen/settingslib_switchbar_padding_left"
+        android:paddingEnd="@dimen/settingslib_switchbar_padding_right"
+        android:background="@drawable/settingslib_switch_bar_bg">
+
+        <TextView
+            android:id="@+id/switch_text"
+            android:layout_height="wrap_content"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_marginEnd="@dimen/settingslib_switch_title_margin"
+            android:layout_marginVertical="@dimen/settingslib_switch_title_margin"
+            android:layout_gravity="center_vertical"
+            android:ellipsize="end"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:hyphenationFrequency="normalFast"
+            android:lineBreakWordStyle="phrase"
+            style="@style/MainSwitchText.Settingslib" />
+
+        <com.google.android.material.materialswitch.MaterialSwitch
+            android:id="@android:id/switch_widget"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:background="@null"
+            android:clickable="false"
+            android:focusable="false"
+            android:theme="@style/Theme.Material3.DynamicColors.DayNight" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/xml/bluetooth_audio_streams_qr_code.xml b/res/xml/bluetooth_audio_streams_qr_code.xml
new file mode 100644
index 0000000..c750963
--- /dev/null
+++ b/res/xml/bluetooth_audio_streams_qr_code.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipToPadding="false"
+        android:paddingLeft="25dp"
+        android:paddingRight="25dp"
+        android:gravity="center_horizontal"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="start"
+            android:textSize="15sp"
+            android:textColor="?android:attr/textColorPrimary"
+            android:text="Scan this QR code with another device connected to LE audio headphones to start sharing audio"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:paddingTop="70dp">
+
+            <ImageView
+                android:id="@+id/qrcode_view"
+                android:layout_width="@dimen/qrcode_size"
+                android:layout_height="@dimen/qrcode_size"
+                android:src="@android:color/transparent"/>
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/xml/wifi_network_details_fragment2.xml b/res/xml/wifi_network_details_fragment2.xml
index 56e7b04..daff20f 100644
--- a/res/xml/wifi_network_details_fragment2.xml
+++ b/res/xml/wifi_network_details_fragment2.xml
@@ -169,15 +169,11 @@
                 settings:enableCopying="true"/>
     </PreferenceCategory>
 
-    <!-- IPv6 Details -->
-    <PreferenceCategory
-            android:key="ipv6_category"
-            android:title="@string/wifi_details_ipv6_address_header"
-            android:selectable="false">
-        <Preference
-                android:key="ipv6_addresses"
-                android:selectable="false"
-                settings:enableCopying="true"/>
-    </PreferenceCategory>
+    <!-- IPv6 address -->
+    <Preference
+        android:title="@string/wifi_details_ipv6_address_header"
+        android:key="ipv6_addresses"
+        android:selectable="false"
+        settings:enableCopying="true"/>
 
 </PreferenceScreen>
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
index d4803c6..0a6795f 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
@@ -16,10 +16,12 @@
 
 package com.android.settings.connecteddevice.audiosharing;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastAssistant;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.util.Log;
@@ -38,9 +40,14 @@
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.flags.Flags;
 import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LeAudioProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
@@ -53,11 +60,13 @@
             "connected_device_audio_sharing_settings";
 
     private final LocalBluetoothManager mLocalBtManager;
+    private final LocalBluetoothLeBroadcast mBroadcast;
     private final LocalBluetoothLeBroadcastAssistant mAssistant;
     private final Executor mExecutor;
     private PreferenceGroup mPreferenceGroup;
     private Preference mAudioSharingSettingsPreference;
     private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
+    private DashboardFragment mFragment;
 
     private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
             new BluetoothLeBroadcastAssistant.Callback() {
@@ -149,6 +158,7 @@
     public AudioSharingDevicePreferenceController(Context context) {
         super(context, KEY);
         mLocalBtManager = Utils.getLocalBtManager(mContext);
+        mBroadcast = mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile();
         mAssistant = mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
         mExecutor = Executors.newSingleThreadExecutor();
     }
@@ -156,15 +166,15 @@
     @Override
     public void onStart(@NonNull LifecycleOwner owner) {
         if (mLocalBtManager == null) {
-            Log.e(TAG, "onStart() Bluetooth is not supported on this device");
+            Log.d(TAG, "onStart() Bluetooth is not supported on this device");
             return;
         }
         if (mAssistant == null) {
-            Log.e(TAG, "onStart() Broadcast assistant is not supported on this device");
+            Log.d(TAG, "onStart() Broadcast assistant is not supported on this device");
             return;
         }
         if (mBluetoothDeviceUpdater == null) {
-            Log.e(TAG, "onStart() Bluetooth device updater is not initialized");
+            Log.d(TAG, "onStart() Bluetooth device updater is not initialized");
             return;
         }
         mLocalBtManager.getEventManager().registerCallback(this);
@@ -176,15 +186,15 @@
     @Override
     public void onStop(@NonNull LifecycleOwner owner) {
         if (mLocalBtManager == null) {
-            Log.e(TAG, "onStop() Bluetooth is not supported on this device");
+            Log.d(TAG, "onStop() Bluetooth is not supported on this device");
             return;
         }
         if (mAssistant == null) {
-            Log.e(TAG, "onStop() Broadcast assistant is not supported on this device");
+            Log.d(TAG, "onStop() Broadcast assistant is not supported on this device");
             return;
         }
         if (mBluetoothDeviceUpdater == null) {
-            Log.e(TAG, "onStop() Bluetooth device updater is not initialized");
+            Log.d(TAG, "onStop() Bluetooth device updater is not initialized");
             return;
         }
         mLocalBtManager.getEventManager().unregisterCallback(this);
@@ -244,17 +254,60 @@
         }
     }
 
+    @Override
+    public void onProfileConnectionStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice,
+            @ConnectionState int state,
+            int bluetoothProfile) {
+        if (state != BluetoothAdapter.STATE_CONNECTED || !cachedDevice.getDevice().isConnected()) {
+            Log.d(TAG, "Ignore onProfileConnectionStateChanged, not connected state");
+            return;
+        }
+        List<LocalBluetoothProfile> supportedProfiles = cachedDevice.getProfiles();
+        boolean isLeAudioSupported = false;
+        for (LocalBluetoothProfile profile : supportedProfiles) {
+            if (profile instanceof LeAudioProfile && profile.isEnabled(cachedDevice.getDevice())) {
+                isLeAudioSupported = true;
+            }
+            if (profile.getProfileId() != bluetoothProfile
+                    && profile.getConnectionStatus(cachedDevice.getDevice())
+                            == BluetoothProfile.STATE_CONNECTED) {
+                Log.d(
+                        TAG,
+                        "Ignore onProfileConnectionStateChanged, not the first connected profile");
+                return;
+            }
+        }
+        // Show stop audio sharing dialog when an ineligible (not le audio) remote device connected
+        // during a sharing session.
+        if (isBroadcasting() && !isLeAudioSupported) {
+            if (mFragment != null) {
+                AudioSharingStopDialogFragment.show(
+                        mFragment,
+                        cachedDevice.getName(),
+                        () -> {
+                            mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId());
+                        });
+            }
+        }
+    }
+
     /**
      * Initialize the controller.
      *
      * @param fragment The fragment to provide the context and metrics category for {@link
-     *     AudioSharingBluetoothDeviceUpdater}.
+     *     AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
      */
     public void init(DashboardFragment fragment) {
+        mFragment = fragment;
         mBluetoothDeviceUpdater =
                 new AudioSharingBluetoothDeviceUpdater(
                         fragment.getContext(),
                         AudioSharingDevicePreferenceController.this,
                         fragment.getMetricsCategory());
     }
+
+    private boolean isBroadcasting() {
+        return mBroadcast != null && mBroadcast.isEnabled(null);
+    }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
index 387ab7e..b36ea54 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.connecteddevice.audiosharing;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.ImageButton;
@@ -23,6 +24,7 @@
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
 import com.android.settings.widget.ValidatedEditTextPreference;
 
 public class AudioSharingNamePreference extends ValidatedEditTextPreference {
@@ -60,5 +62,12 @@
         super.onBindViewHolder(holder);
         final ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
         shareButton.setImageDrawable(getContext().getDrawable(R.drawable.ic_qrcode_24dp));
+        shareButton.setOnClickListener(
+                unused ->
+                        new SubSettingLauncher(getContext())
+                                .setTitleText("Audio sharing QR code")
+                                .setDestination(AudioStreamsQrCodeFragment.class.getName())
+                                .setSourceMetricsCategory(SettingsEnums.AUDIO_SHARING_SETTINGS)
+                                .launch());
     }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
new file mode 100644
index 0000000..495fad3
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
@@ -0,0 +1,91 @@
+/*
+ * 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.settings.connecteddevice.audiosharing;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.flags.Flags;
+
+public class AudioSharingStopDialogFragment extends InstrumentedDialogFragment {
+    private static final String TAG = "AudioSharingStopDialog";
+
+    private static final String BUNDLE_KEY_NEW_DEVICE_NAME = "bundle_key_new_device_name";
+
+    // The host creates an instance of this dialog fragment must implement this interface to receive
+    // event callbacks.
+    public interface DialogEventListener {
+        /** Called when users click the stop sharing button in the dialog. */
+        void onStopSharingClick();
+    }
+
+    private static DialogEventListener sListener;
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.DIALOG_STOP_AUDIO_SHARING;
+    }
+
+    /**
+     * Display the {@link AudioSharingStopDialogFragment} dialog.
+     *
+     * @param host The Fragment this dialog will be hosted.
+     */
+    public static void show(Fragment host, String newDeviceName, DialogEventListener listener) {
+        if (!Flags.enableLeAudioSharing()) return;
+        final FragmentManager manager = host.getChildFragmentManager();
+        sListener = listener;
+        if (manager.findFragmentByTag(TAG) == null) {
+            final Bundle bundle = new Bundle();
+            bundle.putString(BUNDLE_KEY_NEW_DEVICE_NAME, newDeviceName);
+            AudioSharingStopDialogFragment dialog = new AudioSharingStopDialogFragment();
+            dialog.setArguments(bundle);
+            dialog.show(manager, TAG);
+        }
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        Bundle arguments = requireArguments();
+        String newDeviceName = arguments.getString(BUNDLE_KEY_NEW_DEVICE_NAME);
+        final AlertDialog.Builder builder =
+                new AlertDialog.Builder(getActivity())
+                        .setTitle("Stop sharing audio?")
+                        .setCancelable(false);
+        builder.setMessage(
+                newDeviceName + " is connected, devices in audio sharing will disconnect.");
+        builder.setPositiveButton(
+                "Stop sharing",
+                (dialog, which) -> {
+                    sListener.onStopSharingClick();
+                });
+        builder.setNegativeButton(
+                "Cancel",
+                (dialog, which) -> {
+                    dismiss();
+                });
+        AlertDialog dialog = builder.create();
+        dialog.setCanceledOnTouchOutside(false);
+        return dialog;
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsQrCodeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsQrCodeFragment.java
new file mode 100644
index 0000000..edf2bd3
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsQrCodeFragment.java
@@ -0,0 +1,97 @@
+/*
+ * 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.settings.connecteddevice.audiosharing;
+
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.core.InstrumentedFragment;
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.qrcode.QrCodeGenerator;
+
+import com.google.zxing.WriterException;
+
+import java.util.Optional;
+
+public class AudioStreamsQrCodeFragment extends InstrumentedFragment {
+    private static final String TAG = "AudioStreamsQrCodeFragment";
+
+    @Override
+    public int getMetricsCategory() {
+        // TODO(chelseahao): update metrics id
+        return 0;
+    }
+
+    @Override
+    public final View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View view = inflater.inflate(R.xml.bluetooth_audio_streams_qr_code, container, false);
+        getQrCodeBitmap().ifPresent(
+                bm -> ((ImageView) view.requireViewById(R.id.qrcode_view)).setImageBitmap(bm));
+        return view;
+    }
+
+    private Optional<Bitmap> getQrCodeBitmap() {
+        String broadcastMetadata = getBroadcastMetadataQrCode();
+        if (broadcastMetadata.isEmpty()) {
+            Log.d(TAG, "onCreateView: broadcastMetadata is empty!");
+            return Optional.empty();
+        }
+
+        try {
+            int qrcodeSize = getContext().getResources().getDimensionPixelSize(R.dimen.qrcode_size);
+            Bitmap bitmap = QrCodeGenerator.encodeQrCode(broadcastMetadata, qrcodeSize);
+            return Optional.of(bitmap);
+        } catch (WriterException e) {
+            Log.d(TAG, "onCreateView: broadcastMetadata "
+                    + broadcastMetadata
+                    + " qrCode generation exception "
+                    + e);
+        }
+
+        return Optional.empty();
+    }
+
+    private String getBroadcastMetadataQrCode() {
+        LocalBluetoothLeBroadcast localBluetoothLeBroadcast =
+                Utils.getLocalBtManager(getActivity())
+                        .getProfileManager()
+                        .getLeAudioBroadcastProfile();
+        if (localBluetoothLeBroadcast == null) {
+            Log.d(TAG, "getBroadcastMetadataQrCode: localBluetoothLeBroadcast is null!");
+            return "";
+        }
+
+        BluetoothLeBroadcastMetadata metadata =
+                localBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata();
+        if (metadata == null) {
+            Log.d(TAG, "getBroadcastMetadataQrCode: metadata is null!");
+            return "";
+        }
+
+        return BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(metadata);
+    }
+}
diff --git a/src/com/android/settings/slices/VolumeSliceHelper.java b/src/com/android/settings/slices/VolumeSliceHelper.java
index 1ba1778..8947cc4 100644
--- a/src/com/android/settings/slices/VolumeSliceHelper.java
+++ b/src/com/android/settings/slices/VolumeSliceHelper.java
@@ -24,7 +24,6 @@
 import android.content.IntentFilter;
 import android.media.AudioManager;
 import android.net.Uri;
-import android.util.ArrayMap;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
@@ -32,6 +31,7 @@
 import com.android.settingslib.SliceBroadcastRelay;
 
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * This helper is to handle the broadcasts of volume slices
@@ -41,7 +41,7 @@
     private static final String TAG = "VolumeSliceHelper";
 
     @VisibleForTesting
-    static Map<Uri, Integer> sRegisteredUri = new ArrayMap<>();
+    static Map<Uri, Integer> sRegisteredUri = new ConcurrentHashMap<>();
     @VisibleForTesting
     static IntentFilter sIntentFilter;
 
@@ -133,23 +133,19 @@
     }
 
     private static void handleStreamChanged(Context context, int inputType) {
-        synchronized (sRegisteredUri) {
-            for (Map.Entry<Uri, Integer> entry : sRegisteredUri.entrySet()) {
-                if (entry.getValue() == inputType) {
-                    context.getContentResolver().notifyChange(entry.getKey(), null /* observer */);
-                    if (inputType != AudioManager.STREAM_RING) { // Two URIs are mapped to ring
-                        break;
-                    }
+        for (Map.Entry<Uri, Integer> entry : sRegisteredUri.entrySet()) {
+            if (entry.getValue() == inputType) {
+                context.getContentResolver().notifyChange(entry.getKey(), null /* observer */);
+                if (inputType != AudioManager.STREAM_RING) { // Two URIs are mapped to ring
+                    break;
                 }
             }
         }
     }
 
     private static void notifyAllStreamsChanged(Context context) {
-        synchronized (sRegisteredUri) {
-            sRegisteredUri.forEach((uri, audioStream) -> {
-                context.getContentResolver().notifyChange(uri, null /* observer */);
-            });
-        }
+        sRegisteredUri.keySet().forEach(uri -> {
+            context.getContentResolver().notifyChange(uri, null /* observer */);
+        });
     }
 }
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index 6b96460..7ab836b 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -82,35 +82,41 @@
             allProviders = getTogglePermissionAppListProviders()
         )
         SettingsPageProviderRepository(
-            allPageProviders = listOf(
-                HomePageProvider,
-                AppsMainPageProvider,
-                AllAppListPageProvider,
-                AppInfoSettingsProvider,
-                SpecialAppAccessPageProvider,
-                NotificationMainPageProvider,
-                AppListNotificationsPageProvider,
-                SystemMainPageProvider,
-                LanguageAndInputPageProvider,
-                AppLanguagesPageProvider,
-                UsageStatsPageProvider,
-                PlatformCompatAppListPageProvider,
-                BackgroundInstalledAppsPageProvider,
-                UserAspectRatioAppsPageProvider,
-                CloneAppInfoSettingsProvider,
-                NetworkAndInternetPageProvider,
-                AboutPhonePageProvider,
-                StorageAppListPageProvider.Apps,
-                StorageAppListPageProvider.Games,
-                ApnEditPageProvider,
-                ) + togglePermissionAppListTemplate.createPageProviders(),
+            allPageProviders = settingsPageProviders()
+                + togglePermissionAppListTemplate.createPageProviders(),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage()
             ),
         )
     }
-    override val logger =
-        if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_ENABLE_SPA_METRICS))
-            SpaLogProvider
-        else object : SpaLogger {}
+
+
+    open fun settingsPageProviders() = listOf(
+        HomePageProvider,
+        AppsMainPageProvider,
+        AllAppListPageProvider,
+        AppInfoSettingsProvider,
+        SpecialAppAccessPageProvider,
+        NotificationMainPageProvider,
+        AppListNotificationsPageProvider,
+        SystemMainPageProvider,
+        LanguageAndInputPageProvider,
+        AppLanguagesPageProvider,
+        UsageStatsPageProvider,
+        PlatformCompatAppListPageProvider,
+        BackgroundInstalledAppsPageProvider,
+        UserAspectRatioAppsPageProvider,
+        CloneAppInfoSettingsProvider,
+        NetworkAndInternetPageProvider,
+        AboutPhonePageProvider,
+        StorageAppListPageProvider.Apps,
+        StorageAppListPageProvider.Games,
+        ApnEditPageProvider,
+    )
+
+    override val logger = if (FeatureFlagUtils.isEnabled(
+            context, FeatureFlagUtils.SETTINGS_ENABLE_SPA_METRICS
+        )
+    ) SpaLogProvider
+    else object : SpaLogger {}
 }
diff --git a/src/com/android/settings/vpn2/ConfigDialogFragment.java b/src/com/android/settings/vpn2/ConfigDialogFragment.java
index b8825fe..e38f92a 100644
--- a/src/com/android/settings/vpn2/ConfigDialogFragment.java
+++ b/src/com/android/settings/vpn2/ConfigDialogFragment.java
@@ -207,6 +207,10 @@
                 mService.startLegacyVpn(profile);
             } catch (IllegalStateException e) {
                 Toast.makeText(mContext, R.string.vpn_no_network, Toast.LENGTH_LONG).show();
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "Attempted to start an unsupported VPN type.");
+                Toast.makeText(mContext, R.string.vpn_insecure_dialog_subtitle, Toast.LENGTH_LONG)
+                        .show();
             }
         }
     }
diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
index 5ab8807..faa0c3b 100644
--- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
@@ -59,7 +59,6 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.core.text.BidiFormatter;
 import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceScreen;
 import androidx.recyclerview.widget.RecyclerView;
@@ -184,7 +183,6 @@
     private Preference mSubnetPref;
     private Preference mDnsPref;
     private Preference mTypePref;
-    private PreferenceCategory mIpv6Category;
     private Preference mIpv6AddressPref;
     private final IconInjector mIconInjector;
     private final Clock mClock;
@@ -376,8 +374,6 @@
         mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF);
         mDnsPref = screen.findPreference(KEY_DNS_PREF);
         mTypePref = screen.findPreference(KEY_WIFI_TYPE_PREF);
-
-        mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY);
         mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
     }
 
@@ -824,7 +820,7 @@
             mSubnetPref.setVisible(false);
             mGatewayPref.setVisible(false);
             mDnsPref.setVisible(false);
-            mIpv6Category.setVisible(false);
+            mIpv6AddressPref.setVisible(false);
             return;
         }
 
@@ -864,11 +860,11 @@
         updatePreference(mDnsPref, dnsServers);
 
         if (ipv6Addresses.length() > 0) {
+            mIpv6AddressPref.setVisible(true);
             mIpv6AddressPref.setSummary(
                     BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
-            mIpv6Category.setVisible(true);
         } else {
-            mIpv6Category.setVisible(false);
+            mIpv6AddressPref.setVisible(false);
         }
     }
 
diff --git a/tests/robotests/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceControllerTest.java
index a82e1f1..03bf763 100644
--- a/tests/robotests/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceControllerTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.spy;
@@ -220,7 +221,7 @@
             final ResolveInfo resolveInfo = new ResolveInfo();
             resolveInfo.activityInfo = activityInfo;
 
-            when(mPackageManager.resolveActivity(any(), any())).thenReturn(resolveInfo);
+            when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo);
         }
     }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/IncompatibleChargerDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/IncompatibleChargerDetectorTest.java
index 3f65a67..c0f6108 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/IncompatibleChargerDetectorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/IncompatibleChargerDetectorTest.java
@@ -81,6 +81,7 @@
         when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
         when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {1});
+        when(mUsbPortStatus.getComplianceWarnings())
+                .thenReturn(new int[] {UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY});
     }
 }