Merge "Load icon from correct package for Accessibility slices" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 66c3beb..3f5cd84 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -783,9 +783,13 @@
 
         <activity android:name="Settings$CombinedBiometricSettingsActivity"
                   android:label="@string/security_settings_biometric_preference_title"
-                  android:exported="false"
+                  android:exported="true"
                   android:enableOnBackInvokedCallback="false"
                   android:taskAffinity="com.android.settings.root">
+            <intent-filter>
+                <action android:name="android.settings.COMBINED_BIOMETRICS_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
             <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                        android:value="com.android.settings.biometrics.combination.CombinedBiometricSettings" />
             <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
diff --git a/res/drawable/ic_pointer_and_touchpad.xml b/res/drawable/ic_pointer_and_touchpad.xml
new file mode 100644
index 0000000..c077900
--- /dev/null
+++ b/res/drawable/ic_pointer_and_touchpad.xml
@@ -0,0 +1,35 @@
+<!--
+  Copyright 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <com.android.settingslib.widget.AdaptiveIconShapeDrawable
+            android:width="@dimen/accessibility_icon_size"
+            android:height="@dimen/accessibility_icon_size"
+            android:color="@color/accessibility_feature_background"/>
+    </item>
+    <item android:gravity="center">
+        <vector
+            android:height="32dp"
+            android:width="32dp"
+            android:viewportHeight="32"
+            android:viewportWidth="32">
+            <path
+                android:fillColor="#fff"
+                android:pathData="M19.15,22.35a2.6,2.6 0,0 0,1.91 -0.79,2.6 2.6,0 0,0 0.79,-1.91v-0.9h-5.4v0.9c0,0.75 0.26,1.39 0.79,1.91a2.6,2.6 0,0 0,1.91 0.79ZM16.49,17.4h1.98v-2.17a2.6,2.6 0,0 0,-1.98 2.17ZM19.82,17.4h2a2.6,2.6 0,0 0,-0.68 -1.39,2.62 2.62,0 0,0 -1.32,-0.78v2.17ZM19.15,23.7a3.96,3.96 0,0 1,-4.05 -4.05v-1.8c0,-1.14 0.39,-2.1 1.16,-2.87a3.93,3.93 0,0 1,2.89 -1.18c1.14,0 2.1,0.4 2.87,1.18a3.87,3.87 0,0 1,1.18 2.87v1.8c0,1.14 -0.4,2.1 -1.18,2.89a3.9,3.9 0,0 1,-2.87 1.16ZM10.15,20.55v-8.1,8.1ZM10.15,21.9c-0.38,0 -0.7,-0.13 -0.96,-0.4s-0.39,-0.59 -0.39,-0.95v-8.1c0,-0.36 0.13,-0.67 0.4,-0.94 0.26,-0.27 0.57,-0.41 0.95,-0.41h11.7c0.37,0 0.7,0.14 0.96,0.41 0.26,0.27 0.39,0.58 0.39,0.94L10.15,12.45v8.1h3.6v1.35h-3.6Z"/>
+        </vector>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/layout/main_clear_confirm.xml b/res/layout/main_clear_confirm.xml
index 6027d18..d482132 100644
--- a/res/layout/main_clear_confirm.xml
+++ b/res/layout/main_clear_confirm.xml
@@ -19,21 +19,5 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    android:id="@+id/setup_wizard_layout"
     android:icon="@drawable/ic_delete_accent"
-    app:sucHeaderText="@string/main_clear_confirm_title">
-
-    <LinearLayout
-        style="@style/SudContentFrame"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <TextView
-            android:id="@+id/sud_layout_subtitle"
-            style="@style/TextAppearance.PreferenceTitle.SettingsLib"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/main_clear_final_desc"/>
-    </LinearLayout>
-</com.google.android.setupdesign.GlifLayout>
+    app:sucHeaderText="@string/main_clear_confirm_title" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3494afd..0999f0a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4532,8 +4532,8 @@
     <string name="trackpad_bottom_right_tap_summary">Click in the bottom right corner of the touchpad for more options</string>
     <!-- Title text for 'Pointer speed'. [CHAR LIMIT=35] -->
     <string name="trackpad_pointer_speed">Pointer speed</string>
-    <!-- Title text for mouse pointer fill style. [CHAR LIMIT=35] -->
-    <string name="pointer_fill_style">Pointer fill style</string>
+    <!-- Title text for mouse pointer color. [CHAR LIMIT=35] -->
+    <string name="pointer_fill_style">Pointer color</string>
     <!-- Content description for black pointer fill style. [CHAR LIMIT=60] -->
     <string name="pointer_fill_style_black_button">Change pointer fill style to black</string>
     <!-- Content description for green pointer fill style. [CHAR LIMIT=60] -->
@@ -4810,6 +4810,12 @@
     <string name="display_category_title">Display</string>
     <!-- Title for the accessibility color and motion page. [CHAR LIMIT=50] -->
     <string name="accessibility_color_and_motion_title">Color and motion</string>
+    <!-- Title for the accessibility pointer and touchpad page. [CHAR LIMIT=50] -->
+    <string name="accessibility_pointer_and_touchpad_title">Pointer &amp; touchpad accessibility</string>
+    <!-- Summary for the accessibility pointer and touchpad page. [CHAR LIMIT=50] -->
+    <string name="accessibility_pointer_and_touchpad_summary">Pointer color, pointer size &amp; more</string>
+    <!-- Title for the accessibility pointer color customization page. [CHAR LIMIT=50] -->
+    <string name="accessibility_pointer_color_customization_title">Pointer color customization</string>
     <!-- Title for the accessibility color contrast page. [CHAR LIMIT=50] -->
     <string name="accessibility_color_contrast_title">Color contrast</string>
     <!-- Intro for the accessibility color contrast page. [CHAR LIMIT=NONE] -->
@@ -13360,9 +13366,9 @@
     <!-- Summary of the Live Caption enabled state. -->
     <string name="live_caption_enabled">On</string>
     <!-- State description for the Audio Balance seek bar, with left reported before right. -->
-    <string name="audio_seek_bar_state_left_first">Audio %1$d%% left, %2$d%% right</string>
+    <string name="audio_seek_bar_state_left_first">Audio <xliff:g id="percent_left">%1$s</xliff:g> left, <xliff:g id="percent_right">%2$s</xliff:g> right</string>
     <!-- State description for the Audio Balance seek bar, with right reported before left. -->
-    <string name="audio_seek_bar_state_right_first">Audio %1$d%% right, %2$d%% left</string>
+    <string name="audio_seek_bar_state_right_first">Audio <xliff:g id="percent_right">%1$s</xliff:g> right, <xliff:g id="percent_left">%2$s</xliff:g> left</string>
 
     <!--  Warning text about the visibility of device name. [CHAR LIMIT=NONE] -->
     <string name="about_phone_device_name_warning">Your device name is visible to apps you installed. It may also be seen by other people when you connect to Bluetooth devices, connect to a Wi-Fi network or set up a Wi-Fi hotspot.</string>
diff --git a/res/xml/accessibility_color_and_motion.xml b/res/xml/accessibility_color_and_motion.xml
index 4c4490c..a500b72 100644
--- a/res/xml/accessibility_color_and_motion.xml
+++ b/res/xml/accessibility_color_and_motion.xml
@@ -72,17 +72,6 @@
         android:title="@string/accessibility_toggle_large_pointer_icon_title"
         settings:controller="com.android.settings.accessibility.LargePointerIconPreferenceController"/>
 
-    <com.android.settings.widget.LabeledSeekBarPreference
-        android:key="large_pointer_scale"
-        android:title="@string/accessibility_toggle_large_pointer_icon_title"
-        android:summary="@string/accessibility_toggle_large_pointer_icon_summary"
-        android:max="@integer/pointer_scale_seek_bar_end"
-        settings:iconStart="@drawable/ic_remove_24dp"
-        settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
-        settings:iconEnd="@drawable/ic_add_24dp"
-        settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
-        settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
-
     <PreferenceCategory
         android:key="experimental_category"
         android:persistent="false"
diff --git a/res/xml/accessibility_pointer_and_touchpad.xml b/res/xml/accessibility_pointer_and_touchpad.xml
new file mode 100644
index 0000000..1d3b0f2
--- /dev/null
+++ b/res/xml/accessibility_pointer_and_touchpad.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:key="accessibility_pointer_and_touchpad"
+    android:persistent="false"
+    android:title="@string/accessibility_pointer_and_touchpad_title">
+
+    <com.android.settings.widget.LabeledSeekBarPreference
+        android:key="pointer_scale_preference"
+        android:title="@string/pointer_scale"
+        android:max="@integer/pointer_scale_seek_bar_end"
+        settings:iconStart="@drawable/ic_remove_24dp"
+        settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
+        settings:iconEnd="@drawable/ic_add_24dp"
+        settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
+        settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
+
+    <Preference
+        android:fragment="com.android.settings.inputmethod.PointerColorCustomizationFragment"
+        android:key="pointer_color_customization_preference"
+        android:persistent="false"
+        android:title="@string/accessibility_pointer_color_customization_title"/>
+
+    <Preference
+        android:fragment="com.android.settings.accessibility.ToggleAutoclickPreferenceFragment"
+        android:key="autoclick_preference"
+        android:persistent="false"
+        android:title="@string/accessibility_autoclick_preference_title"
+        settings:keywords="@string/keywords_auto_click"
+        settings:controller="com.android.settings.accessibility.AutoclickPreferenceController"/>
+
+</PreferenceScreen>
diff --git a/res/xml/accessibility_pointer_color_customization.xml b/res/xml/accessibility_pointer_color_customization.xml
new file mode 100644
index 0000000..6d767b4
--- /dev/null
+++ b/res/xml/accessibility_pointer_color_customization.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:key="accessibility_pointer_color_customization"
+    android:persistent="false"
+    android:title="@string/accessibility_pointer_color_customization_title">
+
+    <com.android.settings.inputmethod.PointerFillStylePreference
+        android:key="pointer_fill_style"
+        android:title="@string/pointer_fill_style"
+        settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>
+
+    <com.android.settings.inputmethod.PointerStrokeStylePreference
+        android:key="pointer_stroke_style"
+        android:title="@string/pointer_stroke_style"
+        settings:controller="com.android.settings.inputmethod.PointerStrokeStylePreferenceController"/>
+
+</PreferenceScreen>
diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml
index 6561775..18e6455 100644
--- a/res/xml/accessibility_settings.xml
+++ b/res/xml/accessibility_settings.xml
@@ -110,6 +110,16 @@
             settings:keywords="@string/keywords_vibration"
             android:summary="@string/accessibility_vibration_settings_summary"/>
 
+        <Preference
+            android:fragment="com.android.settings.inputmethod.PointerTouchpadFragment"
+            android:key="pointer_and_touchpad"
+            android:icon="@drawable/ic_pointer_and_touchpad"
+            android:persistent="false"
+            android:title="@string/accessibility_pointer_and_touchpad_title"
+            android:summary="@string/accessibility_pointer_and_touchpad_summary"
+            settings:controller="com.android.settings.inputmethod.PointerTouchpadPreferenceController"
+            settings:searchable="true"/>
+
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/res/xml/accessibility_tap_assistance.xml b/res/xml/accessibility_tap_assistance.xml
index d2ec653..6a53d83 100644
--- a/res/xml/accessibility_tap_assistance.xml
+++ b/res/xml/accessibility_tap_assistance.xml
@@ -37,12 +37,4 @@
         android:title="@string/accessibility_setting_item_control_timeout_title"
         settings:controller="com.android.settings.accessibility.AccessibilityTimeoutPreferenceController"
         settings:keywords="@string/keywords_accessibility_timeout"/>
-
-    <Preference
-        android:fragment="com.android.settings.accessibility.ToggleAutoclickPreferenceFragment"
-        android:key="autoclick_preference"
-        android:persistent="false"
-        android:title="@string/accessibility_autoclick_preference_title"
-        settings:keywords="@string/keywords_auto_click"
-        settings:controller="com.android.settings.accessibility.AutoclickPreferenceController"/>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/trackpad_settings.xml b/res/xml/trackpad_settings.xml
index 935de82..7e94944 100644
--- a/res/xml/trackpad_settings.xml
+++ b/res/xml/trackpad_settings.xml
@@ -62,29 +62,14 @@
         android:selectable="false"
         settings:controller="com.android.settings.inputmethod.TrackpadPointerSpeedPreferenceController"/>
 
-    <com.android.settings.inputmethod.PointerFillStylePreference
-        android:key="pointer_fill_style"
-        android:title="@string/pointer_fill_style"
+    <Preference
+        android:fragment="com.android.settings.inputmethod.PointerTouchpadFragment"
+        android:key="pointer_and_touchpad"
         android:order="50"
-        settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>
-
-    <com.android.settings.inputmethod.PointerStrokeStylePreference
-        android:key="pointer_stroke_style"
-        android:title="@string/pointer_stroke_style"
-        android:order="60"
-        settings:controller="com.android.settings.inputmethod.PointerStrokeStylePreferenceController"/>
-
-    <com.android.settings.widget.LabeledSeekBarPreference
-        android:key="pointer_scale"
-        android:title="@string/pointer_scale"
-        android:order="70"
-        android:max="@integer/pointer_scale_seek_bar_end"
-        settings:iconStart="@drawable/ic_remove_24dp"
-        settings:searchable="false"
-        settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
-        settings:iconEnd="@drawable/ic_add_24dp"
-        settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
-        settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
+        android:persistent="false"
+        android:title="@string/accessibility_pointer_and_touchpad_title"
+        android:summary="@string/accessibility_pointer_and_touchpad_summary"
+        settings:searchable="true"/>
 
     <com.android.settingslib.widget.ButtonPreference
         android:key="trackpad_touch_gesture"
diff --git a/src/com/android/settings/FallbackHome.java b/src/com/android/settings/FallbackHome.java
index b70470b..d62b6c2 100644
--- a/src/com/android/settings/FallbackHome.java
+++ b/src/com/android/settings/FallbackHome.java
@@ -179,6 +179,8 @@
                         SystemClock.uptimeMillis(), false);
                 finish();
             }
+        } else {
+            Log.d(TAG, "User not yet unlocked");
         }
     }
 
diff --git a/src/com/android/settings/MainClearConfirm.java b/src/com/android/settings/MainClearConfirm.java
index a5fbebf..c9887e8 100644
--- a/src/com/android/settings/MainClearConfirm.java
+++ b/src/com/android/settings/MainClearConfirm.java
@@ -19,8 +19,6 @@
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
-import android.app.ActionBar;
-import android.app.Activity;
 import android.app.ProgressDialog;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.FactoryResetProtectionPolicy;
@@ -28,7 +26,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.graphics.Color;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.SystemProperties;
@@ -36,12 +33,10 @@
 import android.os.UserManager;
 import android.service.oemlock.OemLockManager;
 import android.service.persistentdata.PersistentDataBlockManager;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
-import android.widget.TextView;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -62,7 +57,7 @@
  * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
  * ON THE PHONE" prompt.  If at any time the phone is allowed to go to sleep, is
  * locked, et cetera, then the confirmation sequence is abandoned.
- *
+ * <p>
  * This is the confirmation screen.
  */
 public class MainClearConfirm extends InstrumentedFragment {
@@ -70,9 +65,11 @@
 
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
-    @VisibleForTesting View mContentView;
+    @VisibleForTesting
+    GlifLayout mContentView;
     private boolean mEraseSdCard;
-    @VisibleForTesting boolean mEraseEsims;
+    @VisibleForTesting
+    boolean mEraseEsims;
 
     /**
      * The user has gone through the multiple confirmation, so now we go ahead
@@ -215,9 +212,7 @@
      * Configure the UI for the final confirmation interaction
      */
     private void establishFinalConfirmationState() {
-        final GlifLayout layout = mContentView.findViewById(R.id.setup_wizard_layout);
-
-        final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
+        final FooterBarMixin mixin = mContentView.getMixin(FooterBarMixin.class);
         mixin.setPrimaryButton(
                 new FooterButton.Builder(getActivity())
                         .setText(R.string.main_clear_button_text)
@@ -228,21 +223,6 @@
         );
     }
 
-    private void setUpActionBarAndTitle() {
-        final Activity activity = getActivity();
-        if (activity == null) {
-            Log.e(TAG, "No activity attached, skipping setUpActionBarAndTitle");
-            return;
-        }
-        final ActionBar actionBar = activity.getActionBar();
-        if (actionBar == null) {
-            Log.e(TAG, "No actionbar, skipping setUpActionBarAndTitle");
-            return;
-        }
-        actionBar.hide();
-        activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
-    }
-
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
@@ -258,32 +238,26 @@
                     .show();
             return new View(getActivity());
         }
-        mContentView = inflater.inflate(R.layout.main_clear_confirm, null);
-        setUpActionBarAndTitle();
+        mContentView = (GlifLayout) inflater.inflate(R.layout.main_clear_confirm, null);
         establishFinalConfirmationState();
-        setAccessibilityTitle();
         setSubtitle();
+        setAccessibilityTitle();
         return mContentView;
     }
 
     private void setAccessibilityTitle() {
         CharSequence currentTitle = getActivity().getTitle();
-        TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
+        CharSequence confirmationMessage = mContentView.getDescriptionText();
         if (confirmationMessage != null) {
-            String accessibleText = new StringBuilder(currentTitle).append(",").append(
-                    confirmationMessage.getText()).toString();
+            String accessibleText = currentTitle + "," + confirmationMessage;
             getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibleText));
         }
     }
 
     @VisibleForTesting
     void setSubtitle() {
-        if (mEraseEsims) {
-            TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
-            if (confirmationMessage != null) {
-                confirmationMessage.setText(R.string.main_clear_final_desc_esim);
-                }
-        }
+        mContentView.setDescriptionText(
+                mEraseEsims ? R.string.main_clear_final_desc_esim : R.string.main_clear_final_desc);
     }
 
     @Override
diff --git a/src/com/android/settings/accessibility/BalanceSeekBar.java b/src/com/android/settings/accessibility/BalanceSeekBar.java
index 7441d6f..8f8f767 100644
--- a/src/com/android/settings/accessibility/BalanceSeekBar.java
+++ b/src/com/android/settings/accessibility/BalanceSeekBar.java
@@ -36,6 +36,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.R;
+import com.android.settings.Utils;
 
 /**
  * A custom seekbar for the balance setting.
@@ -178,10 +179,12 @@
                 == LAYOUT_DIRECTION_RTL;
         final int rightPercent = (int) (100 * (progress / max));
         final int leftPercent = 100 - rightPercent;
+        final String rightPercentString = Utils.formatPercentage(rightPercent);
+        final String leftPercentString = Utils.formatPercentage(leftPercent);
         if (rightPercent > leftPercent || (rightPercent == leftPercent && isLayoutRtl)) {
-            return context.getString(resIdRightFirst, rightPercent, leftPercent);
+            return context.getString(resIdRightFirst, rightPercentString, leftPercentString);
         } else {
-            return context.getString(resIdLeftFirst, leftPercent, rightPercent);
+            return context.getString(resIdLeftFirst, leftPercentString, rightPercentString);
         }
     }
 }
diff --git a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
index 70882a4..2ef0858 100644
--- a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
+++ b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
@@ -67,14 +67,11 @@
 
     @Override
     public CharSequence getSummary() {
-        if (mPreference != null) {
-            return mPreference.isEnabled()
-                    ? "%s"
-                    : mContext.getString(
-                            R.string.accessibility_button_disabled_button_mode_summary);
-        } else {
-            return "%s";
+        int rId = R.string.accessibility_button_fade_summary;
+        if (mPreference != null && !mPreference.isEnabled()) {
+            rId = R.string.accessibility_button_disabled_button_mode_summary;
         }
+        return mContext.getString(rId);
     }
 
     @Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
index b00f407..79cc56e 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
@@ -44,7 +44,7 @@
     private static final String TAG = "AudioSharingLoadingDlg";
 
     private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message";
-    private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10);
+    private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(15);
     private static final int AUTO_DISMISS_MESSAGE_ID = R.id.message;
 
     private static String sMessage = "";
@@ -74,13 +74,15 @@
         }
         AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
         if (dialog != null) {
-            if (sMessage.equals(message)) {
-                Log.d(TAG, "Dialog is showing with same message, return.");
-                return;
-            } else {
-                Log.d(TAG, "Dialog is showing with different message, dismiss and reshow.");
-                dialog.dismiss();
+            if (!sMessage.equals(message)) {
+                Log.d(TAG, "Update dialog message.");
+                TextView messageView = dialog.findViewById(R.id.message);
+                if (messageView != null) {
+                    messageView.setText(message);
+                }
             }
+            Log.d(TAG, "Dialog is showing, return.");
+            return;
         }
         sMessage = message;
         Log.d(TAG, "Show up the loading dialog.");
@@ -113,8 +115,10 @@
     @NonNull
     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
         mHandler = new Handler(Looper.getMainLooper());
-        mHandler.postDelayed(() -> dismiss(), AUTO_DISMISS_MESSAGE_ID,
-                AUTO_DISMISS_TIME_THRESHOLD_MS);
+        mHandler.postDelayed(() -> {
+            Log.d(TAG, "Auto dismiss dialog after timeout");
+            dismiss();
+        }, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
         Bundle args = requireArguments();
         String message = args.getString(BUNDLE_KEY_MESSAGE, "");
         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -132,6 +136,7 @@
     public void onDismiss(@NonNull DialogInterface dialog) {
         super.onDismiss(dialog);
         if (mHandler != null) {
+            Log.d(TAG, "Dialog dismissed, remove auto dismiss task");
             mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID);
         }
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 2040694..c0f463d 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -70,6 +70,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -112,12 +113,13 @@
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private final OnAudioSharingStateChangedListener mListener;
     private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
-    private List<BluetoothDevice> mTargetActiveSinks = new ArrayList<>();
+    @Nullable private AudioSharingDeviceItem mTargetActiveItem;
     private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
     @VisibleForTesting IntentFilter mIntentFilter;
     private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
     private AtomicInteger mIntentHandleStage =
             new AtomicInteger(StartIntentHandleStage.TO_HANDLE.ordinal());
+    private CopyOnWriteArrayList<BluetoothDevice> mSinksInAdding = new CopyOnWriteArrayList<>();
 
     @VisibleForTesting
     BroadcastReceiver mReceiver =
@@ -294,7 +296,16 @@
                 public void onReceiveStateChanged(
                         @NonNull BluetoothDevice sink,
                         int sourceId,
-                        @NonNull BluetoothLeBroadcastReceiveState state) {}
+                        @NonNull BluetoothLeBroadcastReceiveState state) {
+                    if (BluetoothUtils.isConnected(state)) {
+                        if (mSinksInAdding.contains(sink)) {
+                            mSinksInAdding.remove(sink);
+                        }
+                        dismissLoadingStateDialogIfNeeded();
+                        Log.d(TAG, "onReceiveStateChanged() connected, sink = " + sink
+                                + ", remaining sinks = " + mSinksInAdding);
+                    }
+                }
             };
 
     AudioSharingSwitchBarController(
@@ -506,17 +517,20 @@
                         mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ false);
         // deviceItems is ordered. The active device is the first place if exits.
         mDeviceItemsForSharing = new ArrayList<>(deviceItems);
-        mTargetActiveSinks = new ArrayList<>();
+        mTargetActiveItem = null;
         if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) {
             // If active device exists for audio sharing, share to it
             // automatically once the broadcast is started.
-            mTargetActiveSinks =
-                    mGroupedConnectedDevices.getOrDefault(
-                            deviceItems.get(0).getGroupId(), ImmutableList.of());
+            mTargetActiveItem = deviceItems.get(0);
             mDeviceItemsForSharing.remove(0);
         }
         if (mBroadcast != null) {
             mBroadcast.startPrivateBroadcast();
+            mSinksInAdding.clear();
+            // TODO: use string res once finalized.
+            AudioSharingUtils.postOnMainThread(mContext,
+                    () -> AudioSharingLoadingStateDialogFragment.show(mFragment,
+                            "Starting audio stream..."));
             mMetricsFeatureProvider.action(
                     mContext,
                     SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_ON,
@@ -580,27 +594,30 @@
     }
 
     private void handleOnBroadcastReady() {
+        List<BluetoothDevice> targetActiveSinks = mTargetActiveItem == null ? ImmutableList.of()
+                : mGroupedConnectedDevices.getOrDefault(
+                        mTargetActiveItem.getGroupId(), ImmutableList.of());
         Pair<Integer, Object>[] eventData =
                 AudioSharingUtils.buildAudioSharingDialogEventData(
                         SettingsEnums.AUDIO_SHARING_SETTINGS,
                         SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE,
                         /* userTriggered= */ false,
-                        /* deviceCountInSharing= */ mTargetActiveSinks.isEmpty() ? 0 : 1,
+                        /* deviceCountInSharing= */ targetActiveSinks.isEmpty() ? 0 : 1,
                         /* candidateDeviceCount= */ mDeviceItemsForSharing.size());
-        if (!mTargetActiveSinks.isEmpty()) {
+        if (!targetActiveSinks.isEmpty() && mTargetActiveItem != null) {
             Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks.");
-            AudioSharingUtils.addSourceToTargetSinks(mTargetActiveSinks, mBtManager);
+            addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName());
             mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
-            mTargetActiveSinks.clear();
+            mTargetActiveItem = null;
             if (mIntentHandleStage.compareAndSet(
                             StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
                             StartIntentHandleStage.HANDLED.ordinal())
                     && mDeviceItemsForSharing.size() == 1) {
                 Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
-                AudioSharingUtils.addSourceToTargetSinks(
-                        mGroupedConnectedDevices.getOrDefault(
-                                mDeviceItemsForSharing.get(0).getGroupId(), ImmutableList.of()),
-                        mBtManager);
+                AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
+                List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
+                        target.getGroupId(), ImmutableList.of());
+                addSourceToTargetSinks(targetSinks, target.getName());
                 cleanUp();
                 // TODO: Add metric for auto add by intent
                 return;
@@ -611,6 +628,7 @@
                 StartIntentHandleStage.HANDLED.ordinal());
         if (mFragment == null) {
             Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment.");
+            dismissLoadingStateDialogIfNeeded();
             cleanUp();
             return;
         }
@@ -622,15 +640,15 @@
                 new AudioSharingDialogFragment.DialogEventListener() {
                     @Override
                     public void onItemClick(@NonNull AudioSharingDeviceItem item) {
-                        AudioSharingUtils.addSourceToTargetSinks(
-                                mGroupedConnectedDevices.getOrDefault(
-                                        item.getGroupId(), ImmutableList.of()),
-                                mBtManager);
+                        List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
+                                item.getGroupId(), ImmutableList.of());
+                        addSourceToTargetSinks(targetSinks, item.getName());
                         cleanUp();
                     }
 
                     @Override
                     public void onCancelClick() {
+                        dismissLoadingStateDialogIfNeeded();
                         cleanUp();
                     }
                 };
@@ -700,6 +718,27 @@
                         });
     }
 
+    private void addSourceToTargetSinks(List<BluetoothDevice> targetActiveSinks,
+            @NonNull String sinkName) {
+        mSinksInAdding.addAll(targetActiveSinks);
+        AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager);
+        // TODO: move to res once finalized
+        String loadingMessage = "Sharing with " + sinkName + "...";
+        showLoadingStateDialog(loadingMessage);
+    }
+
+    private void showLoadingStateDialog(@NonNull String loadingMessage) {
+        AudioSharingUtils.postOnMainThread(mContext,
+                () -> AudioSharingLoadingStateDialogFragment.show(mFragment, loadingMessage));
+    }
+
+    private void dismissLoadingStateDialogIfNeeded() {
+        if (mSinksInAdding.isEmpty()) {
+            AudioSharingUtils.postOnMainThread(mContext,
+                    () -> AudioSharingLoadingStateDialogFragment.dismiss(mFragment));
+        }
+    }
+
     private void cleanUp() {
         mGroupedConnectedDevices.clear();
         mDeviceItemsForSharing.clear();
diff --git a/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java b/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java
new file mode 100644
index 0000000..2324c5e
--- /dev/null
+++ b/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.settings.inputmethod;
+
+import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isMouse;
+import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isTouchpad;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Settings for pointer and touchpad. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class PointerColorCustomizationFragment extends DashboardFragment {
+
+    private static final String TAG = "PointerColorCustomizationFragment";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.ACCESSIBILITY_POINTER_COLOR_CUSTOMIZATION;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.accessibility_pointer_color_customization;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+
+    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.accessibility_pointer_color_customization) {
+                @Override
+                protected boolean isPageSearchEnabled(Context context) {
+                    return isTouchpad() || isMouse();
+                }
+            };
+}
diff --git a/src/com/android/settings/inputmethod/PointerTouchpadFragment.java b/src/com/android/settings/inputmethod/PointerTouchpadFragment.java
new file mode 100644
index 0000000..fc069ca
--- /dev/null
+++ b/src/com/android/settings/inputmethod/PointerTouchpadFragment.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.settings.inputmethod;
+
+import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isMouse;
+import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isTouchpad;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Accessibility settings for pointer and touchpad. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class PointerTouchpadFragment extends DashboardFragment {
+
+    private static final String TAG = "PointerTouchpadFragment";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.ACCESSIBILITY_POINTER_TOUCHPAD;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.accessibility_pointer_and_touchpad;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.accessibility_pointer_and_touchpad) {
+                @Override
+                protected boolean isPageSearchEnabled(Context context) {
+                    return isTouchpad() || isMouse();
+                }
+            };
+}
diff --git a/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java b/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java
new file mode 100644
index 0000000..b6ea063
--- /dev/null
+++ b/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.core.BasePreferenceController;
+
+/** Controller that shows and updates the pointer touchpad preference. */
+public class PointerTouchpadPreferenceController extends BasePreferenceController {
+
+    public PointerTouchpadPreferenceController(@NonNull Context context,
+            @NonNull String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        boolean isTouchpad = NewKeyboardSettingsUtils.isTouchpad();
+        boolean isMouse = NewKeyboardSettingsUtils.isMouse();
+        return (isTouchpad || isMouse) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+}
diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
index df3b8ba..fe50522 100644
--- a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
+++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
@@ -20,7 +20,7 @@
 import android.telephony.SubscriptionManager
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
-import com.android.settings.network.telephony.getSelectableSubscriptionInfoList
+import com.android.settings.network.telephony.SubscriptionRepository
 import com.android.settings.network.telephony.subscriptionsChangedFlow
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.SharingStarted
@@ -45,7 +45,8 @@
      * Getting the Selectable SubscriptionInfo List from the SubscriptionRepository's
      * getAvailableSubscriptionInfoList
      */
-    val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map {
-        application.getSelectableSubscriptionInfoList()
-    }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
+    val selectableSubscriptionInfoListFlow =
+        SubscriptionRepository(application)
+            .selectableSubscriptionInfoListFlow()
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
 }
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index fcf1599..aca2fc3 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -52,7 +52,7 @@
 import com.android.settings.network.helper.SubscriptionAnnotation;
 import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
 import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
-import com.android.settings.network.telephony.SubscriptionRepositoryKt;
+import com.android.settings.network.telephony.SubscriptionRepository;
 import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
 
 import java.util.ArrayList;
@@ -508,7 +508,7 @@
      * @return list of user selectable subscriptions.
      */
     public static List<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) {
-        return SubscriptionRepositoryKt.getSelectableSubscriptionInfoList(context);
+        return new SubscriptionRepository(context).getSelectableSubscriptionInfoList();
     }
 
     /**
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index 26ea9b3..6b5b4cb 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -42,13 +42,48 @@
 class SubscriptionRepository(private val context: Context) {
     private val subscriptionManager = context.requireSubscriptionManager()
 
+    /** A cold flow of a list of subscriptions that are available and visible to the user. */
+    fun selectableSubscriptionInfoListFlow(): Flow<List<SubscriptionInfo>> =
+        context
+            .subscriptionsChangedFlow()
+            .map { getSelectableSubscriptionInfoList() }
+            .conflate()
+            .flowOn(Dispatchers.Default)
+
     /**
      * Return a list of subscriptions that are available and visible to the user.
      *
      * @return list of user selectable subscriptions.
      */
-    fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> =
-        context.getSelectableSubscriptionInfoList()
+    fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
+        val availableList =
+            subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList()
+        val visibleList =
+            availableList.filter { subInfo ->
+                // Opportunistic subscriptions are considered invisible to users so they should
+                // never be returned.
+                SubscriptionUtil.isSubscriptionVisible(subscriptionManager, context, subInfo)
+            }
+        return visibleList
+            .groupBy { it.groupUuid }
+            .flatMap { (groupUuid, subInfos) ->
+                if (groupUuid == null) {
+                    subInfos
+                } else {
+                    // Multiple subscriptions in a group should only have one representative.
+                    // It should be the current active primary subscription if any, or the primary
+                    // subscription with minimum subscription id.
+                    subInfos
+                        .filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
+                        .ifEmpty { subInfos.sortedBy { it.subscriptionId } }
+                        .take(1)
+                }
+            }
+            // Matching the sorting order in
+            // SubscriptionManagerService.getAvailableSubscriptionInfoList
+            .sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId }))
+            .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
+    }
 
     /** Flow of whether the subscription visible for the given [subId]. */
     fun isSubscriptionVisibleFlow(subId: Int): Flow<Boolean> {
@@ -154,38 +189,6 @@
 fun Context.subscriptionsChangedFlow(): Flow<Unit> =
     SubscriptionRepository(this).subscriptionsChangedFlow()
 
-/**
- * Return a list of subscriptions that are available and visible to the user.
- *
- * @return list of user selectable subscriptions.
- */
-fun Context.getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
-    val subscriptionManager = requireSubscriptionManager()
-    val availableList = subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList()
-    val visibleList = availableList.filter { subInfo ->
-        // Opportunistic subscriptions are considered invisible
-        // to users so they should never be returned.
-        SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo)
-    }
-    return visibleList
-        .groupBy { it.groupUuid }
-        .flatMap { (groupUuid, subInfos) ->
-            if (groupUuid == null) {
-                subInfos
-            } else {
-                // Multiple subscriptions in a group should only have one representative.
-                // It should be the current active primary subscription if any, or the primary
-                // subscription with minimum subscription id.
-                subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
-                    .ifEmpty { subInfos.sortedBy { it.subscriptionId } }
-                    .take(1)
-            }
-        }
-        // Matching the sorting order in SubscriptionManagerService.getAvailableSubscriptionInfoList
-        .sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId }))
-        .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
-}
-
 /** Subscription with invalid sim slot index has lowest sort order. */
 private val SubscriptionInfo.sortableSimSlotIndex: Int
     get() = if (simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java
index 68cc167..9355b1b 100644
--- a/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java
@@ -58,6 +58,5 @@
             conditionId -> saveMode(mode -> {
                 mode.setCustomModeConditionId(mContext, conditionId);
                 return mode;
-                // TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen?
             });
 }
diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java
index 37772b3..89cb662 100644
--- a/src/com/android/settings/notification/modes/ZenModesListFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java
@@ -142,9 +142,6 @@
                 @Override
                 public List<String> getNonIndexableKeys(Context context) {
                     final List<String> keys = super.getNonIndexableKeys(context);
-                    // TODO: b/332937523 - determine if this should be removed once the preference
-                    //                     controller adds dynamic data to index
-                    keys.add(ZenModesListPreferenceController.KEY);
                     return keys;
                 }
 
diff --git a/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java b/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java
index a41a0b2..560f313 100644
--- a/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java
+++ b/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 
+import androidx.fragment.app.Fragment;
 import androidx.preference.Preference;
 import androidx.preference.TwoStatePreference;
 
@@ -28,7 +29,6 @@
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settings.password.ChooseLockSettingsHelper;
 import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
 
 /**
  * Preference controller for the pin_auto_confirm setting.
@@ -40,11 +40,10 @@
 
     private final int mUserId;
     private final LockPatternUtils mLockPatternUtils;
-    private final ObservablePreferenceFragment mParentFragment;
+    private final Fragment mParentFragment;
 
     public AutoPinConfirmPreferenceController(Context context, int userId,
-            LockPatternUtils lockPatternUtils,
-            ObservablePreferenceFragment parentFragment) {
+            LockPatternUtils lockPatternUtils, Fragment parentFragment) {
         super(context);
         mUserId = userId;
         mLockPatternUtils = lockPatternUtils;
diff --git a/src/com/android/settings/support/actionbar/HelpMenuController.java b/src/com/android/settings/support/actionbar/HelpMenuController.java
deleted file mode 100644
index 7e1f460..0000000
--- a/src/com/android/settings/support/actionbar/HelpMenuController.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 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.support.actionbar;
-
-import static com.android.settings.support.actionbar.HelpResourceProvider.HELP_URI_RESOURCE_KEY;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuInflater;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-
-import com.android.settingslib.HelpUtils;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.ObservableFragment;
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
-import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
-
-/**
- * A controller that adds help menu to any Settings page.
- */
-public class HelpMenuController implements LifecycleObserver, OnCreateOptionsMenu {
-
-    private final Fragment mHost;
-
-    public static void init(@NonNull ObservablePreferenceFragment host) {
-        host.getSettingsLifecycle().addObserver(new HelpMenuController(host));
-    }
-
-    public static void init(@NonNull ObservableFragment host) {
-        host.getSettingsLifecycle().addObserver(new HelpMenuController(host));
-    }
-
-    private HelpMenuController(@NonNull Fragment host) {
-        mHost = host;
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        final Bundle arguments = mHost.getArguments();
-        int helpResourceId = 0;
-        if (arguments != null && arguments.containsKey(HELP_URI_RESOURCE_KEY)) {
-            helpResourceId = arguments.getInt(HELP_URI_RESOURCE_KEY);
-        } else if (mHost instanceof HelpResourceProvider) {
-            helpResourceId = ((HelpResourceProvider) mHost).getHelpResource();
-        }
-
-        String helpUri = null;
-        if (helpResourceId != 0) {
-            helpUri = mHost.getContext().getString(helpResourceId);
-        }
-        final Activity activity = mHost.getActivity();
-        if (helpUri != null && activity != null) {
-            HelpUtils.prepareHelpMenuItem(activity, menu, helpUri, mHost.getClass().getName());
-        }
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/MainClearConfirmTest.java b/tests/robotests/src/com/android/settings/MainClearConfirmTest.java
index f7711c8..a102fda 100644
--- a/tests/robotests/src/com/android/settings/MainClearConfirmTest.java
+++ b/tests/robotests/src/com/android/settings/MainClearConfirmTest.java
@@ -31,10 +31,11 @@
 import android.security.Flags;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.view.LayoutInflater;
-import android.widget.TextView;
 
 import androidx.fragment.app.FragmentActivity;
 
+import com.google.android.setupdesign.GlifLayout;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -85,12 +86,12 @@
         MainClearConfirm mainClearConfirm = new MainClearConfirm();
         mainClearConfirm.mEraseEsims = true;
         mainClearConfirm.mContentView =
-                LayoutInflater.from(mActivity).inflate(R.layout.main_clear_confirm, null);
+                (GlifLayout) LayoutInflater.from(mActivity)
+                        .inflate(R.layout.main_clear_confirm, null);
 
         mainClearConfirm.setSubtitle();
 
-        assertThat(((TextView) mainClearConfirm.mContentView
-                .findViewById(R.id.sud_layout_description)).getText())
+        assertThat(mainClearConfirm.mContentView.getDescriptionText())
                 .isEqualTo(mActivity.getString(R.string.main_clear_final_desc_esim));
     }
 
@@ -99,12 +100,12 @@
         MainClearConfirm mainClearConfirm = new MainClearConfirm();
         mainClearConfirm.mEraseEsims = false;
         mainClearConfirm.mContentView =
-                LayoutInflater.from(mActivity).inflate(R.layout.main_clear_confirm, null);
+                (GlifLayout) LayoutInflater.from(mActivity)
+                        .inflate(R.layout.main_clear_confirm, null);
 
         mainClearConfirm.setSubtitle();
 
-        assertThat(((TextView) mainClearConfirm.mContentView
-                .findViewById(R.id.sud_layout_description)).getText())
+        assertThat(mainClearConfirm.mContentView.getDescriptionText())
                 .isEqualTo(mActivity.getString(R.string.main_clear_final_desc));
     }
 
diff --git a/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java b/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
index d74794f..bbe511d 100644
--- a/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
@@ -34,6 +34,7 @@
 import android.widget.SeekBar;
 
 import com.android.settings.R;
+import com.android.settings.Utils;
 import com.android.settings.testutils.shadow.ShadowSystemSettings;
 
 import org.junit.Before;
@@ -162,7 +163,8 @@
         mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true);
 
         assertThat(mSeekBar.getStateDescription()).isEqualTo(
-                mContext.getString(R.string.audio_seek_bar_state_left_first, 50, 50));
+                mContext.getString(R.string.audio_seek_bar_state_left_first,
+                        Utils.formatPercentage(50), Utils.formatPercentage(50)));
     }
 
     @Test
@@ -177,7 +179,8 @@
         mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true);
 
         assertThat(mSeekBar.getStateDescription()).isEqualTo(
-                mContext.getString(R.string.audio_seek_bar_state_right_first, 50, 50));
+                mContext.getString(R.string.audio_seek_bar_state_right_first,
+                        Utils.formatPercentage(50), Utils.formatPercentage(50)));
     }
 
     @Test
@@ -189,7 +192,8 @@
         mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true);
 
         assertThat(mSeekBar.getStateDescription()).isEqualTo(
-                mContext.getString(R.string.audio_seek_bar_state_left_first, 75, 25));
+                mContext.getString(R.string.audio_seek_bar_state_left_first,
+                        Utils.formatPercentage(75), Utils.formatPercentage(25)));
     }
 
     @Test
@@ -201,7 +205,8 @@
         mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true);
 
         assertThat(mSeekBar.getStateDescription()).isEqualTo(
-                mContext.getString(R.string.audio_seek_bar_state_right_first, 75, 25));
+                mContext.getString(R.string.audio_seek_bar_state_right_first,
+                        Utils.formatPercentage(75), Utils.formatPercentage(25)));
     }
 
     // method to get the center from BalanceSeekBar for testing setMax().
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java
index b5da88c..ff15f52 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java
@@ -150,7 +150,7 @@
     }
 
     @Test
-    public void showDialog_newMessage_dismissAndShowNewDialog() {
+    public void showDialog_newMessage_keepAndUpdateDialog() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1);
         shadowMainLooper().idle();
@@ -163,12 +163,7 @@
 
         AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE2);
         shadowMainLooper().idle();
-        assertThat(dialog.isShowing()).isFalse();
-        AlertDialog newDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
-        assertThat(newDialog).isNotNull();
-        assertThat(newDialog.isShowing()).isTrue();
-        view = newDialog.findViewById(R.id.message);
-        assertThat(view).isNotNull();
+        assertThat(dialog.isShowing()).isTrue();
         assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
index 354c5c7..0d21f18 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -57,7 +57,9 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.CompoundButton;
+import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
@@ -235,6 +237,7 @@
 
     @After
     public void tearDown() {
+        ShadowAlertDialogCompat.reset();
         ShadowBluetoothUtils.reset();
         ShadowThreadUtils.reset();
     }
@@ -426,6 +429,8 @@
         assertThat(childFragments)
                 .comparingElementsUsing(CLAZZNAME_EQUALS)
                 .containsExactly(AudioSharingConfirmDialogFragment.class.getName());
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
@@ -490,14 +495,21 @@
                             public void onAudioSharingProfilesConnected() {}
                         });
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        // No loading state dialog.
+        assertThat(childFragments).isEmpty();
+
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
 
         verify(mFeatureFactory.metricsFeatureProvider)
                 .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
 
-        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        // No audio sharing dialog.
         assertThat(childFragments).isEmpty();
     }
 
@@ -514,7 +526,13 @@
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -522,8 +540,12 @@
         verify(mFeatureFactory.metricsFeatureProvider, never())
                 .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
 
-        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
-        assertThat(childFragments).isEmpty();
+        childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        // No audio sharing dialog.
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).doesNotContain(
+                AudioSharingDialogFragment.class.getName());
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
@@ -534,23 +556,42 @@
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
         when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
         doNothing().when(mBroadcast).startPrivateBroadcast();
-        mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
+        mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+        AudioSharingLoadingStateDialogFragment loadingFragment =
+                (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
+        // TODO: use string res once finalized
+        String expectedMessage = "Starting audio stream...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
+
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
 
         verify(mFeatureFactory.metricsFeatureProvider)
                 .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
+        // TODO: use string res once finalized
+        expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
 
-        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        childFragments = mParentFragment.getChildFragmentManager().getFragments();
         assertThat(childFragments)
                 .comparingElementsUsing(CLAZZNAME_EQUALS)
-                .containsExactly(AudioSharingDialogFragment.class.getName());
+                .containsExactly(AudioSharingDialogFragment.class.getName(),
+                        AudioSharingLoadingStateDialogFragment.class.getName());
 
-        AudioSharingDialogFragment fragment =
-                (AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments);
-        Pair<Integer, Object>[] eventData = fragment.getEventData();
+        Pair<Integer, Object>[] eventData = new Pair[0];
+        for (Fragment fragment : childFragments) {
+            if (fragment instanceof AudioSharingDialogFragment) {
+                eventData = ((AudioSharingDialogFragment) fragment).getEventData();
+                break;
+            }
+        }
         assertThat(eventData)
                 .asList()
                 .containsExactly(
@@ -570,6 +611,8 @@
                                 AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
                                         .ordinal(),
                                 1));
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
@@ -582,6 +625,8 @@
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
@@ -597,6 +642,17 @@
 
         verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
         assertThat(dialog.isShowing()).isFalse();
+        // Loading state dialog shows sharing state for the user chosen sink.
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+        AudioSharingLoadingStateDialogFragment loadingFragment =
+                (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
+        // TODO: use string res once finalized
+        String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
@@ -609,6 +665,8 @@
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
@@ -624,10 +682,21 @@
 
         verify(mAssistant, never()).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
         assertThat(dialog.isShowing()).isFalse();
+        // Loading state dialog shows sharing state for the auto add active sink.
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+        AudioSharingLoadingStateDialogFragment loadingFragment =
+                (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
+        // TODO: use string res once finalized
+        String expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
-    public void testBluetoothLeBroadcastCallbacks_updateSwitch() {
+    public void testBroadcastCallbacks_updateSwitch() {
         mOnAudioSharingStateChanged = false;
         mSwitchBar.setChecked(false);
         when(mBroadcast.isEnabled(any())).thenReturn(false);
@@ -673,7 +742,7 @@
     }
 
     @Test
-    public void testBluetoothLeBroadcastCallbacks_doNothing() {
+    public void testBroadcastCallbacks_doNothing() {
         mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata);
         mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1);
         mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1);
@@ -685,7 +754,7 @@
     }
 
     @Test
-    public void testBluetoothLeBroadcastAssistantCallbacks_logAction() {
+    public void testAssistantCallbacks_onSourceAddFailed_logAction() {
         mController.mBroadcastAssistantCallback.onSourceAddFailed(
                 mDevice1, mMetadata, /* reason= */ 1);
         verify(mFeatureFactory.metricsFeatureProvider)
@@ -696,7 +765,24 @@
     }
 
     @Test
-    public void testBluetoothLeBroadcastAssistantCallbacks_doNothing() {
+    public void testAssistantCallbacks_onReceiveStateChanged_dismissLoadingDialog() {
+        AudioSharingLoadingStateDialogFragment.show(mParentFragment, TEST_DEVICE_NAME1);
+        shadowOf(Looper.getMainLooper()).idle();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+
+        BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+        when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
+        mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice1, /* sourceId= */ 1,
+                state);
+        shadowOf(Looper.getMainLooper()).idle();
+        childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).isEmpty();
+    }
+
+    @Test
+    public void testAssistantCallbacks_doNothing() {
         BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
 
         // Do nothing
@@ -784,7 +870,7 @@
     @Test
     public void handleStartAudioSharingFromIntent_flagOff_doNothing() {
         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
-        setUpStartSharingIntent();
+        var unused = setUpFragmentWithStartSharingIntent();
         mController.onStart(mLifecycleOwner);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -795,7 +881,7 @@
     public void handleStartAudioSharingFromIntent_profileNotReady_doNothing() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         when(mAssistant.isProfileReady()).thenReturn(false);
-        setUpStartSharingIntent();
+        var unused = setUpFragmentWithStartSharingIntent();
         mController.onServiceConnected();
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -817,13 +903,16 @@
         when(mBtnView.isEnabled()).thenReturn(true);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
-        setUpStartSharingIntent();
+        Fragment parentFragment = setUpFragmentWithStartSharingIntent();
         mController.onServiceConnected();
         shadowOf(Looper.getMainLooper()).idle();
 
         verify(mSwitchBar).setChecked(true);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mBroadcast).startPrivateBroadcast();
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -831,11 +920,21 @@
                 .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
         verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
         verify(mAssistant).addSource(mDevice2, mMetadata, /* isGroupOp= */ false);
-        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
-        assertThat(childFragments).isEmpty();
+        List<Fragment> childFragments = parentFragment.getChildFragmentManager().getFragments();
+        // Skip audio sharing dialog.
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+        // The loading state dialog shows sharing state for the auto add second sink.
+        AudioSharingLoadingStateDialogFragment loadingFragment =
+                (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
+        // TODO: use string res once finalized
+        String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
-    private void setUpStartSharingIntent() {
+    private Fragment setUpFragmentWithStartSharingIntent() {
         Bundle args = new Bundle();
         args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true);
         Intent intent = new Intent();
@@ -849,5 +948,15 @@
                 .get();
         shadowOf(Looper.getMainLooper()).idle();
         mController.init(fragment);
+        return fragment;
+    }
+
+    private void checkLoadingStateDialogMessage(
+            @NonNull AudioSharingLoadingStateDialogFragment fragment,
+            @NonNull String expectedMessage) {
+        TextView loadingMessage = fragment.getDialog() == null ? null
+                : fragment.getDialog().findViewById(R.id.message);
+        assertThat(loadingMessage).isNotNull();
+        assertThat(loadingMessage.getText().toString()).isEqualTo(expectedMessage);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java
new file mode 100644
index 0000000..68cd768
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.settings.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class PointerTouchpadFragmentTest {
+
+    private PointerTouchpadFragment mFragment;
+
+    @Before
+    public void setUp() {
+        mFragment = new PointerTouchpadFragment();
+    }
+
+    @Test
+    public void getPreferenceScreenResId_isPointerTouchpad() {
+        assertThat(mFragment.getPreferenceScreenResId())
+                .isEqualTo(R.xml.accessibility_pointer_and_touchpad);
+    }
+
+    @Test
+    public void getMetricsCategory_returnsCorrectCategory() {
+        assertThat(mFragment.getMetricsCategory()).isEqualTo(
+                SettingsEnums.ACCESSIBILITY_POINTER_TOUCHPAD);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java
new file mode 100644
index 0000000..6fceeba
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.settings.inputmethod;
+
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.view.InputDevice;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.shadow.ShadowInputDevice;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link PointerTouchpadPreferenceController}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+        ShadowInputDevice.class,
+})
+public final class PointerTouchpadPreferenceControllerTest {
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    private PointerTouchpadPreferenceController mController;
+
+    @Before
+    public void initObjects() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mController = new PointerTouchpadPreferenceController(context, "pointer_touchpad");
+        ShadowInputDevice.reset();
+    }
+
+    @Test
+    public void getAvailableStatus_noTouchpadOrMouseConditionallyUnavailable() {
+        int deviceId = 1;
+        ShadowInputDevice.sDeviceIds = new int[]{deviceId};
+        InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
+                InputDevice.SOURCE_BLUETOOTH_STYLUS);
+        ShadowInputDevice.addDevice(deviceId, device);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_isTouchpadAvailable() {
+        int deviceId = 1;
+        ShadowInputDevice.sDeviceIds = new int[]{deviceId};
+        InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
+                InputDevice.SOURCE_TOUCHPAD);
+        ShadowInputDevice.addDevice(deviceId, device);
+
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(BasePreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_isMouseAvailable() {
+        int deviceId = 1;
+        ShadowInputDevice.sDeviceIds = new int[]{deviceId};
+        InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
+                InputDevice.SOURCE_MOUSE);
+        ShadowInputDevice.addDevice(deviceId, device);
+
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(BasePreferenceController.AVAILABLE);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
index 86c1244..0e0ed69 100644
--- a/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
@@ -22,11 +22,11 @@
 
 import android.content.Context;
 
+import androidx.fragment.app.Fragment;
 import androidx.preference.SwitchPreference;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -41,7 +41,7 @@
     @Mock
     private LockPatternUtils mLockPatternUtils;
     @Mock
-    private ObservablePreferenceFragment mParentFragment;
+    private Fragment mParentFragment;
     private AutoPinConfirmPreferenceController mController;
     private SwitchPreference mPreference;
 
diff --git a/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java b/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java
deleted file mode 100644
index 8cd26da..0000000
--- a/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2017 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.support.actionbar;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class HelpMenuControllerTest {
-
-    @Mock
-    private Context mContext;
-    private TestFragment mHost;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mHost = spy(new TestFragment());
-        doReturn(mContext).when(mHost).getContext();
-    }
-
-    @Test
-    public void onCreateOptionsMenu_withArgumentOverride_shouldPrepareHelpUsingOverride() {
-        final Bundle bundle = new Bundle();
-        bundle.putInt(HelpResourceProvider.HELP_URI_RESOURCE_KEY, 123);
-        mHost.setArguments(bundle);
-
-        HelpMenuController.init(mHost);
-
-        mHost.getSettingsLifecycle().onCreateOptionsMenu(null /* menu */, null /* inflater */);
-
-        verify(mContext).getString(123);
-    }
-
-    @Test
-    public void onCreateOptionsMenu_noArgumentOverride_shouldPrepareHelpUsingProvider() {
-        HelpMenuController.init(mHost);
-
-        mHost.getSettingsLifecycle().onCreateOptionsMenu(null /* menu */, null /* inflater */);
-
-        verify(mContext).getString(mHost.getHelpResource());
-    }
-
-    private static class TestFragment extends ObservablePreferenceFragment
-        implements HelpResourceProvider {
-
-        @Override
-        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
-        }
-    }
-}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
index 5dbc534..5052f57 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
@@ -120,7 +120,7 @@
             )
         }
 
-        val subInfos = context.getSelectableSubscriptionInfoList()
+        val subInfos = repository.getSelectableSubscriptionInfoList()
 
         assertThat(subInfos.map { it.simSlotIndex })
             .containsExactly(SIM_SLOT_INDEX_0, SIM_SLOT_INDEX_1).inOrder()
@@ -141,7 +141,7 @@
             )
         }
 
-        val subInfos = context.getSelectableSubscriptionInfoList()
+        val subInfos = repository.getSelectableSubscriptionInfoList()
 
         assertThat(subInfos.map { it.simSlotIndex })
             .containsExactly(SIM_SLOT_INDEX_1, SubscriptionManager.INVALID_SIM_SLOT_INDEX).inOrder()
@@ -164,7 +164,7 @@
             )
         }
 
-        val subInfos = context.getSelectableSubscriptionInfoList()
+        val subInfos = repository.getSelectableSubscriptionInfoList()
 
         assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_IN_SLOT_0)
     }
@@ -184,7 +184,7 @@
             )
         }
 
-        val subInfos = context.getSelectableSubscriptionInfoList()
+        val subInfos = repository.getSelectableSubscriptionInfoList()
 
         assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_3_NOT_IN_SLOT)
     }