Merge "Use correct shortcut preference key on Magnification screen" into main
diff --git a/res/layout/accessibility_magnification_mode_header.xml b/res/layout/accessibility_dialog_header.xml
similarity index 93%
rename from res/layout/accessibility_magnification_mode_header.xml
rename to res/layout/accessibility_dialog_header.xml
index e476553..ace8b23 100644
--- a/res/layout/accessibility_magnification_mode_header.xml
+++ b/res/layout/accessibility_dialog_header.xml
@@ -21,9 +21,9 @@
     android:padding="?android:attr/dialogPreferredPadding">
 
     <TextView
+        android:id="@+id/accessibility_dialog_header_text_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:text="@string/accessibility_magnification_area_settings_message"
         android:textSize="16sp"
         style="?android:attr/textAppearanceMedium"
         android:textColor="?android:attr/textColorAlertDialogListItem"/>
diff --git a/res/layout/vpn_dialog.xml b/res/layout/vpn_dialog.xml
index 062772e..f0e7b83 100644
--- a/res/layout/vpn_dialog.xml
+++ b/res/layout/vpn_dialog.xml
@@ -13,7 +13,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:theme="@style/Theme.Material3.DynamicColors.DayNight"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
@@ -45,12 +48,19 @@
                     android:orientation="vertical"
                     android:visibility="gone">
 
-                <TextView style="@style/vpn_label"
-                        android:text="@string/vpn_name"
-                        android:labelFor="@+id/name"/>
-                <EditText style="@style/vpn_value"
+                <com.google.android.material.textfield.TextInputLayout
+                    style="@style/vpn_label"
+                    android:id="@+id/name_layout"
+                    android:hint="@string/vpn_name"
+                    app:endIconMode="clear_text"
+                    app:helperTextEnabled="true"
+                    app:helperText="@string/vpn_required"
+                    app:errorEnabled="true">
+                    <com.google.android.material.textfield.TextInputEditText
+                        style="@style/vpn_value"
                         android:id="@+id/name"
                         android:inputType="textCapWords"/>
+                </com.google.android.material.textfield.TextInputLayout>
 
                 <TextView style="@style/vpn_label"
                         android:text="@string/vpn_type"
@@ -60,23 +70,36 @@
                         android:prompt="@string/vpn_type"
                         android:entries="@array/vpn_types"/>
 
-                <TextView style="@style/vpn_label"
-                        android:text="@string/vpn_server"
-                        android:labelFor="@+id/server"/>
-                <EditText style="@style/vpn_value"
+                <com.google.android.material.textfield.TextInputLayout
+                    style="@style/vpn_label"
+                    android:id="@+id/server_layout"
+                    android:hint="@string/vpn_server"
+                    app:endIconMode="clear_text"
+                    app:helperTextEnabled="true"
+                    app:helperText="@string/vpn_required"
+                    app:errorEnabled="true">
+                    <com.google.android.material.textfield.TextInputEditText
+                        style="@style/vpn_value"
                         android:id="@+id/server"/>
+                </com.google.android.material.textfield.TextInputLayout>
 
                 <LinearLayout android:id="@+id/options_ipsec_identity"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:orientation="vertical"
                         android:visibility="gone">
-                    <TextView style="@style/vpn_label"
-                            android:text="@string/vpn_ipsec_identifier"
-                            android:labelFor="@+id/ipsec_identifier"/>
-                    <EditText style="@style/vpn_value"
-                            android:id="@+id/ipsec_identifier"
-                            android:hint="@string/vpn_not_used"/>
+                    <com.google.android.material.textfield.TextInputLayout
+                        style="@style/vpn_label"
+                        android:id="@+id/ipsec_identifier_layout"
+                        android:hint="@string/vpn_ipsec_identifier"
+                        app:endIconMode="clear_text"
+                        app:helperTextEnabled="true"
+                        app:helperText="@string/vpn_required"
+                        app:errorEnabled="true">
+                        <com.google.android.material.textfield.TextInputEditText
+                            style="@style/vpn_value"
+                            android:id="@+id/ipsec_identifier"/>
+                    </com.google.android.material.textfield.TextInputLayout>
                 </LinearLayout>
 
                 <LinearLayout android:id="@+id/ipsec_psk"
@@ -84,12 +107,19 @@
                         android:layout_height="wrap_content"
                         android:orientation="vertical"
                         android:visibility="gone">
-                    <TextView style="@style/vpn_label"
-                            android:text="@string/vpn_ipsec_secret"
-                            android:labelFor="@+id/ipsec_secret"/>
-                    <EditText style="@style/vpn_value"
+                    <com.google.android.material.textfield.TextInputLayout
+                        style="@style/vpn_label"
+                        android:id="@+id/ipsec_secret_layout"
+                        android:hint="@string/vpn_ipsec_secret"
+                        app:endIconMode="password_toggle"
+                        app:helperTextEnabled="true"
+                        app:helperText="@string/vpn_required"
+                        app:errorEnabled="true">
+                        <com.google.android.material.textfield.TextInputEditText
+                            style="@style/vpn_value"
                             android:id="@+id/ipsec_secret"
-                            android:password="true"/>
+                            android:inputType="textPassword"/>
+                    </com.google.android.material.textfield.TextInputLayout>
                 </LinearLayout>
 
                 <LinearLayout android:id="@+id/ipsec_user"
@@ -153,27 +183,33 @@
                     android:orientation="vertical"
                     android:visibility="gone" >
 
-                    <TextView
+                    <com.google.android.material.textfield.TextInputLayout
                         style="@style/vpn_label"
-                        android:text="@string/proxy_hostname_label"
-                        android:labelFor="@+id/vpn_proxy_host" />
+                        android:id="@+id/vpn_proxy_host_layout"
+                        android:hint="@string/proxy_hostname_label"
+                        app:endIconMode="clear_text"
+                        app:helperTextEnabled="true"
+                        app:helperText="@string/vpn_optional"
+                        app:errorEnabled="true">
+                        <com.google.android.material.textfield.TextInputEditText
+                            style="@style/vpn_value"
+                            android:id="@+id/vpn_proxy_host"
+                            android:inputType="textNoSuggestions"/>
+                    </com.google.android.material.textfield.TextInputLayout>
 
-                    <EditText
-                        android:id="@+id/vpn_proxy_host"
-                        style="@style/vpn_value"
-                        android:hint="@string/proxy_hostname_hint"
-                        android:inputType="textNoSuggestions" />
-
-                    <TextView
+                    <com.google.android.material.textfield.TextInputLayout
                         style="@style/vpn_label"
-                        android:text="@string/proxy_port_label"
-                        android:labelFor="@+id/vpn_proxy_port" />
-
-                    <EditText
-                        android:id="@+id/vpn_proxy_port"
-                        style="@style/vpn_value"
-                        android:hint="@string/proxy_port_hint"
-                        android:inputType="number" />
+                        android:id="@+id/vpn_proxy_port_layout"
+                        android:hint="@string/proxy_port_label"
+                        app:endIconMode="clear_text"
+                        app:helperTextEnabled="true"
+                        app:helperText="@string/vpn_optional"
+                        app:errorEnabled="true">
+                        <com.google.android.material.textfield.TextInputEditText
+                            style="@style/vpn_value"
+                            android:id="@+id/vpn_proxy_port"
+                            android:inputType="number"/>
+                    </com.google.android.material.textfield.TextInputLayout>
                 </LinearLayout>
             </LinearLayout>
 
@@ -182,18 +218,32 @@
                     android:layout_height="wrap_content"
                     android:orientation="vertical">
 
-                <TextView style="@style/vpn_label"
-                        android:text="@string/vpn_username"
-                        android:labelFor="@+id/username"/>
-                <EditText style="@style/vpn_value"
+                <com.google.android.material.textfield.TextInputLayout
+                    style="@style/vpn_label"
+                    android:id="@+id/username_layout"
+                    android:hint="@string/vpn_username"
+                    app:endIconMode="clear_text"
+                    app:helperTextEnabled="true"
+                    app:helperText="@string/vpn_optional"
+                    app:errorEnabled="true">
+                    <com.google.android.material.textfield.TextInputEditText
+                        style="@style/vpn_value"
                         android:id="@+id/username"/>
+                </com.google.android.material.textfield.TextInputLayout>
 
-                <TextView style="@style/vpn_label"
-                        android:text="@string/vpn_password"
-                        android:labelFor="@+id/password"/>
-                <EditText style="@style/vpn_value"
+                <com.google.android.material.textfield.TextInputLayout
+                    style="@style/vpn_label"
+                    android:id="@+id/password_layout"
+                    android:hint="@string/vpn_password"
+                    app:endIconMode="password_toggle"
+                    app:helperTextEnabled="true"
+                    app:helperText="@string/vpn_optional"
+                    app:errorEnabled="true">
+                    <com.google.android.material.textfield.TextInputEditText
+                        style="@style/vpn_value"
                         android:id="@+id/password"
-                        android:password="true"/>
+                        android:inputType="textPassword"/>
+                </com.google.android.material.textfield.TextInputLayout>
 
                 <CheckBox style="@style/vpn_value"
                         android:id="@+id/save_login"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cbe5640..e551749 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1054,6 +1054,7 @@
     <!-- Watch unlock enrollment and settings --><skip />
     <!-- Title shown for menu item that launches watch unlock settings. [CHAR LIMIT=40] -->
     <string name ="security_settings_activeunlock_preference_title">Watch Unlock</string>
+    <string name="security_settings_activeunlock">Watch</string>
     <!-- Introduction shown in face and fingerprint page to introduce the biometric feature. [CHAR LIMIT=NONE]-->
     <string name="biometric_settings_intro_with_activeunlock">When you set up Face Unlock and Fingerprint Unlock, your phone will ask for your fingerprint when you wear a mask or are in a dark area.\n\nWatch Unlock is another convenient way to unlock your phone, for example, when your fingers are wet or face isn\u2019t recognized.</string>
     <!-- Introduction shown in fingerprint page to explain that watch unlock can be used if fingerprint isn't recognized. [CHAR LIMIT=NONE]-->
@@ -5219,6 +5220,16 @@
     <string name="accessibility_screen_magnification_title">Magnification</string>
     <!-- Title for accessibility shortcut preference for magnification. [CHAR LIMIT=60] -->
     <string name="accessibility_screen_magnification_shortcut_title">Magnification shortcut</string>
+    <!-- Title of cursor following mode preference for magnification. [CHAR LIMIT=60] -->
+    <string name="accessibility_magnification_cursor_following_title">Cursor following</string>
+    <!-- Header message of cursor following mode dialog for magnification. [CHAR LIMIT=none] -->
+    <string name="accessibility_magnification_cursor_following_header">Choose how Magnification follows your cursor.</string>
+    <!-- Option title of cursor following continuous mode in the mode selection dialog. [CHAR LIMIT=none] -->
+    <string name="accessibility_magnification_cursor_following_continuous">Move screen continuously as mouse moves</string>
+    <!-- Option title of cursor following center mode in the mode selection dialog. [CHAR LIMIT=none] -->
+    <string name="accessibility_magnification_cursor_following_center">Move screen keeping mouse at center of screen</string>
+    <!-- Option title of cursor following edge mode in the mode selection dialog. [CHAR LIMIT=none] -->
+    <string name="accessibility_magnification_cursor_following_edge">Move screen when mouse touches edges of screen</string>
     <!-- Title for accessibility follow typing preference for magnification. [CHAR LIMIT=35] -->
     <string name="accessibility_screen_magnification_follow_typing_title">Magnify typing</string>
     <!-- Summary for accessibility follow typing preference for magnification. [CHAR LIMIT=none] -->
@@ -6600,8 +6611,12 @@
     <string name="battery_usage_timestamps_content_description"><xliff:g id="from_timestamp">%1$s</xliff:g> to <xliff:g id="to_timestamp">%2$s</xliff:g></string>
     <!-- [CHAR_LIMIT=NONE] The first slot is a week day (e.g. "Monday"); the second slot is a hourly time span (e.g. "6 AM - 8 AM"). -->
     <string name="battery_usage_day_and_hour"><xliff:g id="day">%1$s</xliff:g> <xliff:g id="hour">%2$s</xliff:g></string>
-    <!-- [CHAR_LIMIT=NONE] Accessibility content description for each slot in battery chart view. -->
-    <string name="battery_usage_time_info_and_battery_level"><xliff:g id="time_info" example="Battery usage for Monday 6 AM - 8 AM">%1$s</xliff:g> <xliff:g id="battery_level" example="Battery level percentage from 83% to 59%">%2$s</xliff:g></string>
+    <!-- [CHAR_LIMIT=NONE] Accessibility content description for each slot in battery chart view. Please reuse the words in tc/6732629268310936155 -->
+    <string name="battery_usage_status_time_info_and_battery_level"><xliff:g id="selected_status" example="Selected">%1$s</xliff:g>, <xliff:g id="time_info" example="Battery usage for Monday 6 AM - 8 AM">%2$s</xliff:g> <xliff:g id="battery_level" example="Battery level percentage from 83% to 59%">%3$s</xliff:g></string>
+    <!-- [CHAR_LIMIT=NONE] Accessibility content description for the battery usage slot is selected. -->
+    <string name="battery_chart_slot_status_selected">Selected</string>
+    <!-- [CHAR_LIMIT=NONE] Accessibility content description for the battery usage slot is not selected -->
+    <string name="battery_chart_slot_status_unselected">Unselected</string>
     <!-- [CHAR_LIMIT=NONE] Accessibility content description for battery chart view. -->
     <string name="battery_usage_chart">Battery usage chart</string>
     <!-- [CHAR_LIMIT=NONE] Accessibility content description for daily battery chart view. -->
@@ -7275,6 +7290,12 @@
         generic error. [CHAR LIMIT=120] -->
     <string name="vpn_always_on_invalid_reason_other">The information entered doesn\'t support
         always-on VPN</string>
+    <!-- Hint for an optional field in a VPN profile. [CHAR LIMIT=40] -->
+    <string name="vpn_optional">(optional)</string>
+    <!-- Hint for a required field in a VPN profile. [CHAR LIMIT=40] -->
+    <string name="vpn_required">(required)</string>
+    <!-- Error message displayed below the VPN EditText when the filed is required. [CHAR LIMIT=NONE] -->
+    <string name="vpn_field_required">The field is required</string>
 
     <!-- Button label to cancel changing a VPN profile. [CHAR LIMIT=40] -->
     <string name="vpn_cancel">Cancel</string>
@@ -12923,12 +12944,12 @@
     <!-- Title for a toggle that enables freeform windows. Freeform windows enables users to freely arrange and resize overlapping apps. [CHAR LIMIT=50] -->
     <string name="enable_desktop_mode">Enable freeform windows</string>
 
-    <!-- Title for a toggle that enables desktop experience features. This includes desktop view and connected displays. [CHAR LIMIT=50] -->
+    <!-- Title for a toggle that enables desktop experience features. This includes desktop windowing and connected displays. [CHAR LIMIT=50] -->
     <string name="enable_desktop_experience_features">Enable desktop experience features</string>
     <!-- Summary for a toggle that enables desktop experience features when the device itself can show the desktop (but it is not available without the developer option). [CHAR LIMIT=NONE] -->
-    <string name="enable_desktop_experience_features_summary_with_desktop">Enable Desktop View on the device and on secondary displays.</string>
-    <!-- Summary for a toggle that enables desktop experience features when desktop views don't need to be enable. [CHAR LIMIT=NONE] -->
-    <string name="enable_desktop_experience_features_summary_without_desktop">Enable Desktop View on secondary displays.</string>
+    <string name="enable_desktop_experience_features_summary_with_desktop">Enable desktop windowing on the device and on secondary displays.</string>
+    <!-- Summary for a toggle that enables desktop experience features when desktop windowing doesn't need to be enabled. [CHAR LIMIT=NONE] -->
+    <string name="enable_desktop_experience_features_summary_without_desktop">Enable desktop windowing on secondary displays.</string>
 
     <!-- Title for a toggle that enables freeform windows on secondary display. Freeform windows enables users to freely arrange and resize overlapping apps. [CHAR LIMIT=50] -->
     <string name="enable_desktop_mode_on_secondary_display">Enable freeform windows on secondary display</string>
@@ -14282,4 +14303,6 @@
     <string name="supervision_add_forgot_pin_preference_title">Forgot PIN</string>
     <!-- Title for web content filters entry [CHAR LIMIT=60] -->
     <string name="supervision_web_content_filters_title">Web content filters</string>
+    <!-- Generic content description that is attached to the preview illustration at the top of an Accessibility feature toggle page. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_illustration_content_description"><xliff:g id="feature" example="Select to Speak">%1$s</xliff:g> animation</string>
 </resources>
diff --git a/res/xml/mouse_settings.xml b/res/xml/mouse_settings.xml
index e4b3f1c..ec1c39d 100644
--- a/res/xml/mouse_settings.xml
+++ b/res/xml/mouse_settings.xml
@@ -28,11 +28,11 @@
         settings:controller="com.android.settings.inputmethod.MousePointerAccelerationPreferenceController" />
 
     <com.android.settings.widget.SeekBarPreference
-        android:key="trackpad_pointer_speed"
-        android:title="@string/trackpad_pointer_speed"
+        android:key="pointer_speed"
+        android:title="@string/pointer_speed"
         android:order="20"
         android:selectable="false"
-        settings:controller="com.android.settings.inputmethod.TouchpadPointerSpeedPreferenceController"/>
+        settings:controller="com.android.settings.inputmethod.MousePointerSpeedPreferenceController"/>
 
     <SwitchPreferenceCompat
         android:key="mouse_swap_primary_button"
diff --git a/res/xml/night_display_settings.xml b/res/xml/night_display_settings.xml
index 95d5034..d75619d 100644
--- a/res/xml/night_display_settings.xml
+++ b/res/xml/night_display_settings.xml
@@ -52,7 +52,7 @@
         android:title="@string/night_display_end_time_title"
         settings:controller="com.android.settings.display.NightDisplayCustomEndTimePreferenceController"/>
 
-    <com.android.settings.widget.SeekBarPreference
+    <com.android.settingslib.widget.SliderPreference
         android:key="night_display_temperature"
         android:title="@string/night_display_temperature_title"
         settings:keywords="@string/keywords_display_night_display"
diff --git a/src/com/android/settings/accessibility/AccessibilityActivityPreference.java b/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
index a8e456d..3ae64fc 100644
--- a/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
+++ b/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
@@ -127,14 +127,13 @@
         final String htmlDescription = mA11yShortcutInfo.loadHtmlDescription(mPm);
         final String settingsClassName = mA11yShortcutInfo.getSettingsActivityName();
         final String tileServiceClassName = mA11yShortcutInfo.getTileServiceName();
-        final int metricsCategory = FeatureFactory.getFeatureFactory()
-                .getAccessibilityMetricsFeatureProvider()
-                .getDownloadedFeatureMetricsCategory(mComponentName);
+        final int pageIdCategory = FeatureFactory.getFeatureFactory()
+                .getAccessibilityPageIdFeatureProvider().getCategory(mComponentName);
 
         ThreadUtils.getUiThreadHandler().post(() -> {
             RestrictedPreferenceHelper.putBasicExtras(
                     this, prefKey, getTitle(), intro, description, imageRes,
-                    htmlDescription, mComponentName, metricsCategory);
+                    htmlDescription, mComponentName, pageIdCategory);
             RestrictedPreferenceHelper.putSettingsExtras(this, getPackageName(), settingsClassName);
             RestrictedPreferenceHelper.putTileServiceExtras(
                     this, getPackageName(), tileServiceClassName);
diff --git a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
index 4e9cd92..347c769 100644
--- a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
@@ -228,10 +228,10 @@
                     new ComponentName(packageName, tileServiceClassName).flattenToString());
         }
 
-        final int metricsCategory = FeatureFactory.getFeatureFactory()
-                .getAccessibilityMetricsFeatureProvider()
-                .getDownloadedFeatureMetricsCategory(componentName);
-        extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory);
+        final int pageIdCategory = FeatureFactory.getFeatureFactory()
+                .getAccessibilityPageIdFeatureProvider().getCategory(componentName);
+        extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, pageIdCategory);
+        extras.putInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY, pageIdCategory);
         extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
         extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());
 
diff --git a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
index c89b8d7..dc49008 100644
--- a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
+++ b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
@@ -104,6 +104,11 @@
          * screen / Switch between full and partial screen > Save.
          */
         int DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING = 1011;
+
+        /**
+         * OPEN: Settings > Accessibility > Magnification > Cursor following.
+         */
+        int DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE = 1012;
     }
 
     /**
diff --git a/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java
index 018bd2e..7d03230 100644
--- a/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java
+++ b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java
@@ -15,8 +15,6 @@
  */
 package com.android.settings.accessibility;
 
-import android.content.ComponentName;
-
 import androidx.annotation.Nullable;
 
 /**
@@ -25,11 +23,11 @@
 public interface AccessibilityFeedbackFeatureProvider {
 
     /**
-     * Returns value according to the {@code componentName}.
+     * Returns value according to the {@code pageId}.
      *
-     * @param componentName the component name of the downloaded service or activity
-     * @return Feedback bucket ID
+     * @param pageId The unique identifier of the page.
+     * @return Feedback bucket ID associated with the page, or {@code null} if is not found.
      */
     @Nullable
-    String getCategory(@Nullable ComponentName componentName);
+    String getCategory(int pageId);
 }
diff --git a/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProviderImpl.java b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProviderImpl.java
index 917c5f6..2381887 100644
--- a/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProviderImpl.java
+++ b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProviderImpl.java
@@ -15,8 +15,6 @@
  */
 package com.android.settings.accessibility;
 
-import android.content.ComponentName;
-
 import androidx.annotation.Nullable;
 
 /** Default implementation of {@link AccessibilityFeedbackFeatureProvider}. */
@@ -25,7 +23,7 @@
 
     @Override
     @Nullable
-    public String getCategory(@Nullable ComponentName componentName) {
-        return "";
+    public String getCategory(int pageId) {
+        return null;
     }
 }
diff --git a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProvider.java
similarity index 84%
rename from src/com/android/settings/accessibility/AccessibilityMetricsFeatureProvider.java
rename to src/com/android/settings/accessibility/AccessibilityPageIdFeatureProvider.java
index a9d7c05..698efbe 100644
--- a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProvider.java
+++ b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProvider.java
@@ -21,9 +21,9 @@
 import androidx.annotation.Nullable;
 
 /**
- * Provider for Accessibility metrics related features.
+ * Provider for Accessibility page id related features.
  */
-public interface AccessibilityMetricsFeatureProvider {
+public interface AccessibilityPageIdFeatureProvider {
 
     /**
      * Returns {@link android.app.settings.SettingsEnums} value according to the {@code
@@ -32,5 +32,5 @@
      * @param componentName the component name of the downloaded service or activity
      * @return value in {@link android.app.settings.SettingsEnums}
      */
-    int getDownloadedFeatureMetricsCategory(@Nullable ComponentName componentName);
+    int getCategory(@Nullable ComponentName componentName);
 }
diff --git a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProviderImpl.java b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProviderImpl.java
similarity index 75%
rename from src/com/android/settings/accessibility/AccessibilityMetricsFeatureProviderImpl.java
rename to src/com/android/settings/accessibility/AccessibilityPageIdFeatureProviderImpl.java
index 0f85f38..acd8aab 100644
--- a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProviderImpl.java
+++ b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProviderImpl.java
@@ -19,14 +19,16 @@
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
 
+import androidx.annotation.Nullable;
+
 /**
- * Provider implementation for Accessibility metrics related features.
+ * Provider implementation for Accessibility page id related features.
  */
-public class AccessibilityMetricsFeatureProviderImpl implements
-        AccessibilityMetricsFeatureProvider {
+public class AccessibilityPageIdFeatureProviderImpl implements
+        AccessibilityPageIdFeatureProvider {
 
     @Override
-    public int getDownloadedFeatureMetricsCategory(ComponentName componentName) {
+    public int getCategory(@Nullable ComponentName componentName) {
         return SettingsEnums.ACCESSIBILITY_SERVICE;
     }
 }
diff --git a/src/com/android/settings/accessibility/AccessibilityServicePreference.java b/src/com/android/settings/accessibility/AccessibilityServicePreference.java
index 8a22d82..7032774 100644
--- a/src/com/android/settings/accessibility/AccessibilityServicePreference.java
+++ b/src/com/android/settings/accessibility/AccessibilityServicePreference.java
@@ -123,13 +123,12 @@
         final String settingsClassName = mA11yServiceInfo.getSettingsActivityName();
         final String tileServiceClassName = mA11yServiceInfo.getTileServiceName();
         final ResolveInfo resolveInfo = mA11yServiceInfo.getResolveInfo();
-        final int metricsCategory = FeatureFactory.getFeatureFactory()
-                .getAccessibilityMetricsFeatureProvider()
-                .getDownloadedFeatureMetricsCategory(mComponentName);
+        final int pageIdCategory = FeatureFactory.getFeatureFactory()
+                .getAccessibilityPageIdFeatureProvider().getCategory(mComponentName);
         ThreadUtils.getUiThreadHandler().post(() -> {
             RestrictedPreferenceHelper.putBasicExtras(
                     this, prefKey, getTitle(), intro, description, imageRes,
-                    htmlDescription, mComponentName, metricsCategory);
+                    htmlDescription, mComponentName, pageIdCategory);
             RestrictedPreferenceHelper.putServiceExtras(this, resolveInfo, mServiceEnabled);
             RestrictedPreferenceHelper.putSettingsExtras(this, getPackageName(), settingsClassName);
             RestrictedPreferenceHelper.putTileServiceExtras(
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 57eb4d5..2c8247f 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -97,6 +97,7 @@
     static final String EXTRA_HTML_DESCRIPTION = "html_description";
     static final String EXTRA_TIME_FOR_LOGGING = "start_time_to_log_a11y_tool";
     static final String EXTRA_METRICS_CATEGORY = "metrics_category";
+    static final String EXTRA_FEEDBACK_CATEGORY = "feedback_category";
 
     // Timeout before we update the services if packages are added/removed
     // since the AccessibilityManagerService has to do that processing first
@@ -255,7 +256,7 @@
     public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
         if (getFeedbackManager().isAvailable()) {
             menu.add(Menu.NONE, MENU_ID_SEND_FEEDBACK, Menu.NONE,
-                    getPrefContext().getText(R.string.accessibility_send_feedback_title));
+                    R.string.accessibility_send_feedback_title);
         }
         super.onCreateOptionsMenu(menu, inflater);
     }
@@ -286,7 +287,7 @@
 
     private FeedbackManager getFeedbackManager() {
         if (mFeedbackManager == null) {
-            mFeedbackManager = new FeedbackManager(getActivity());
+            mFeedbackManager = new FeedbackManager(getActivity(), SettingsEnums.ACCESSIBILITY);
         }
         return mFeedbackManager;
     }
diff --git a/src/com/android/settings/accessibility/FeedbackManager.java b/src/com/android/settings/accessibility/FeedbackManager.java
index 52aefd2..dc4baa7 100644
--- a/src/com/android/settings/accessibility/FeedbackManager.java
+++ b/src/com/android/settings/accessibility/FeedbackManager.java
@@ -16,7 +16,6 @@
 package com.android.settings.accessibility;
 
 import android.app.Activity;
-import android.content.ComponentName;
 import android.content.Intent;
 import android.text.TextUtils;
 
@@ -46,23 +45,14 @@
      * Constructs a new FeedbackManager.
      *
      * @param activity The activity context. A WeakReference is used to prevent memory leaks.
+     * @param pageId The unique identifier of the page associated with the feedback.
      */
-    public FeedbackManager(@Nullable Activity activity) {
-        this(activity, /* componentName= */ null);
-    }
-
-    /**
-     * Constructs a new FeedbackManager.
-     *
-     * @param activity The activity context. A WeakReference is used to prevent memory leaks.
-     * @param componentName The component name associated with the feedback.
-     */
-    public FeedbackManager(@Nullable Activity activity, @Nullable ComponentName componentName) {
+    public FeedbackManager(@Nullable Activity activity, int pageId) {
         this(activity,
                 DeviceInfoUtils.getFeedbackReporterPackage(activity),
                 FeatureFactory.getFeatureFactory()
                         .getAccessibilityFeedbackFeatureProvider()
-                        .getCategory(componentName));
+                        .getCategory(pageId));
     }
 
     /**
diff --git a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
index 013fdee..c6995b0 100644
--- a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
@@ -31,8 +31,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
@@ -60,6 +58,11 @@
     }
 
     @Override
+    public int getFeedbackCategory() {
+        return getArguments().getInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY);
+    }
+
+    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         // Init new preference to replace the switch preference instead.
@@ -115,13 +118,6 @@
         return mTileComponentName;
     }
 
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        // Do not call super. We don't want to see the "Help & feedback" option on this page so as
-        // not to confuse users who think they might be able to send feedback about a specific
-        // accessibility service from this page.
-    }
-
     // IMPORTANT: Refresh the info since there are dynamically changing capabilities.
     private AccessibilityShortcutInfo getAccessibilityShortcutInfo() {
         final List<AccessibilityShortcutInfo> infos = AccessibilityManager.getInstance(
diff --git a/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java b/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java
new file mode 100644
index 0000000..d217ead
--- /dev/null
+++ b/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2025 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.accessibility;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.provider.Settings;
+import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.util.Preconditions;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.DialogCreatable;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
+import com.android.settings.core.BasePreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controller that shows the magnification cursor following mode and the preference click behavior.
+ */
+public class MagnificationCursorFollowingModePreferenceController extends
+        BasePreferenceController implements DialogCreatable {
+    static final String PREF_KEY =
+            Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
+
+    private static final String TAG =
+            MagnificationCursorFollowingModePreferenceController.class.getSimpleName();
+
+    private final List<ModeInfo> mModeList = new ArrayList<>();
+    @Nullable
+    private DialogHelper mDialogHelper;
+    @VisibleForTesting
+    @Nullable
+    ListView mModeListView;
+    @Nullable
+    private Preference mModePreference;
+
+    public MagnificationCursorFollowingModePreferenceController(@NonNull Context context,
+            @NonNull String preferenceKey) {
+        super(context, preferenceKey);
+        initModeList();
+    }
+
+    public void setDialogHelper(@NonNull DialogHelper dialogHelper) {
+        mDialogHelper = dialogHelper;
+    }
+
+    private void initModeList() {
+        mModeList.add(new ModeInfo(mContext.getString(
+                R.string.accessibility_magnification_cursor_following_continuous),
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS));
+        mModeList.add(new ModeInfo(
+                mContext.getString(R.string.accessibility_magnification_cursor_following_center),
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER));
+        mModeList.add(new ModeInfo(
+                mContext.getString(R.string.accessibility_magnification_cursor_following_edge),
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE));
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @NonNull
+    @Override
+    public CharSequence getSummary() {
+        return getCursorFollowingModeSummary(getCurrentMagnificationCursorFollowingMode());
+    }
+
+    @Override
+    public void displayPreference(@NonNull PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mModePreference = screen.findPreference(getPreferenceKey());
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(@NonNull Preference preference) {
+        if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) || mModePreference == null) {
+            return super.handlePreferenceTreeClick(preference);
+        }
+
+        Preconditions.checkNotNull(mDialogHelper).showDialog(
+                    DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE);
+        return true;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(int dialogId) {
+        Preconditions.checkArgument(
+                dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
+                "This only handles cursor following mode dialog");
+        return createMagnificationCursorFollowingModeDialog();
+    }
+
+    @Override
+    public int getDialogMetricsCategory(int dialogId) {
+        Preconditions.checkArgument(
+                dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
+                "This only handles cursor following mode dialog");
+        return SettingsEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING;
+    }
+
+    @NonNull
+    private Dialog createMagnificationCursorFollowingModeDialog() {
+        mModeListView = AccessibilityDialogUtils.createSingleChoiceListView(mContext, mModeList,
+                /* itemListener= */null);
+        final View headerView = LayoutInflater.from(mContext).inflate(
+                R.layout.accessibility_dialog_header, mModeListView,
+                /* attachToRoot= */false);
+        final TextView textView = Preconditions.checkNotNull(headerView.findViewById(
+                R.id.accessibility_dialog_header_text_view));
+        textView.setText(
+                mContext.getString(R.string.accessibility_magnification_cursor_following_header));
+        textView.setVisibility(View.VISIBLE);
+        mModeListView.addHeaderView(headerView, /* data= */null, /* isSelectable= */false);
+        final int selectionIndex = computeSelectionIndex();
+        if (selectionIndex != AdapterView.INVALID_POSITION) {
+            mModeListView.setItemChecked(selectionIndex, /* value= */true);
+        }
+        final CharSequence title = mContext.getString(
+                R.string.accessibility_magnification_cursor_following_title);
+        final CharSequence positiveBtnText = mContext.getString(R.string.save);
+        final CharSequence negativeBtnText = mContext.getString(R.string.cancel);
+        return AccessibilityDialogUtils.createCustomDialog(mContext, title, mModeListView,
+                positiveBtnText,
+                this::onMagnificationCursorFollowingModeDialogPositiveButtonClicked,
+                negativeBtnText, /* negativeListener= */null);
+    }
+
+    void onMagnificationCursorFollowingModeDialogPositiveButtonClicked(
+            DialogInterface dialogInterface, int which) {
+        ListView listView = Preconditions.checkNotNull(mModeListView);
+        final int selectionIndex = listView.getCheckedItemPosition();
+        if (selectionIndex == AdapterView.INVALID_POSITION) {
+            Log.w(TAG, "Selected positive button with INVALID_POSITION index");
+            return;
+        }
+        ModeInfo cursorFollowingMode = (ModeInfo) listView.getItemAtPosition(selectionIndex);
+        if (cursorFollowingMode != null) {
+            Preconditions.checkNotNull(mModePreference).setSummary(
+                    getCursorFollowingModeSummary(cursorFollowingMode.mMode));
+            Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY,
+                    cursorFollowingMode.mMode);
+        }
+    }
+
+    private int computeSelectionIndex() {
+        ListView listView = Preconditions.checkNotNull(mModeListView);
+        @AccessibilityMagnificationCursorFollowingMode
+        final int currentMode = getCurrentMagnificationCursorFollowingMode();
+        for (int i = 0; i < listView.getCount(); i++) {
+            final ModeInfo mode = (ModeInfo) listView.getItemAtPosition(i);
+            if (mode != null && mode.mMode == currentMode) {
+                return i;
+            }
+        }
+        return AdapterView.INVALID_POSITION;
+    }
+
+    @NonNull
+    private CharSequence getCursorFollowingModeSummary(
+            @AccessibilityMagnificationCursorFollowingMode int cursorFollowingMode) {
+        int stringId = switch (cursorFollowingMode) {
+            case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER ->
+                    R.string.accessibility_magnification_cursor_following_center;
+            case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE ->
+                    R.string.accessibility_magnification_cursor_following_edge;
+            default ->
+                    R.string.accessibility_magnification_cursor_following_continuous;
+        };
+        return mContext.getString(stringId);
+    }
+
+    private @AccessibilityMagnificationCursorFollowingMode int
+            getCurrentMagnificationCursorFollowingMode() {
+        return Settings.Secure.getInt(mContext.getContentResolver(), PREF_KEY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
+    }
+
+    static class ModeInfo extends ItemInfoArrayAdapter.ItemInfo {
+        @AccessibilityMagnificationCursorFollowingMode
+        public final int mMode;
+
+        ModeInfo(@NonNull CharSequence title,
+                @AccessibilityMagnificationCursorFollowingMode int mode) {
+            super(title, /* summary= */null, /* drawableId= */null);
+            mMode = mode;
+        }
+    }
+}
diff --git a/src/com/android/settings/accessibility/MagnificationModePreferenceController.java b/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
index 7f9f336..2c291df 100644
--- a/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
+++ b/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
@@ -176,8 +176,12 @@
                 mContext, mModeInfos, this::onMagnificationModeSelected);
 
         final View headerView = LayoutInflater.from(mContext).inflate(
-                R.layout.accessibility_magnification_mode_header,
-                getMagnificationModesListView(), /* attachToRoot= */false);
+                R.layout.accessibility_dialog_header, getMagnificationModesListView(),
+                /* attachToRoot= */false);
+        final TextView textView = Preconditions.checkNotNull(headerView.findViewById(
+                R.id.accessibility_dialog_header_text_view));
+        textView.setText(
+                mContext.getString(R.string.accessibility_magnification_area_settings_message));
         getMagnificationModesListView().addHeaderView(headerView, /* data= */null,
                 /* isSelectable= */false);
 
diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
index 5c18be8..ae6239a 100644
--- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
+++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
@@ -217,6 +217,7 @@
         extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, imageRes);
         extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
         extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory);
+        extras.putInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY, metricsCategory);
     }
 
     /**
diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index 06bcdb7..a11ad46 100644
--- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
@@ -39,8 +39,6 @@
 import android.text.BidiFormatter;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.CompoundButton;
 
@@ -76,10 +74,8 @@
     }
 
     @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        // Do not call super. We don't want to see the "Help & feedback" option on this page so as
-        // not to confuse users who think they might be able to send feedback about a specific
-        // accessibility service from this page.
+    public int getFeedbackCategory() {
+        return getArguments().getInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY);
     }
 
     @Override
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index 9367251..d8c3985 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -40,6 +40,9 @@
 import android.text.Html;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
@@ -48,6 +51,7 @@
 import android.widget.CompoundButton.OnCheckedChangeListener;
 import android.widget.ImageView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
@@ -64,6 +68,7 @@
 import com.android.settings.flags.Flags;
 import com.android.settings.widget.SettingsMainSwitchBar;
 import com.android.settings.widget.SettingsMainSwitchPreference;
+import com.android.settingslib.utils.ThreadUtils;
 import com.android.settingslib.widget.IllustrationPreference;
 import com.android.settingslib.widget.TopIntroPreference;
 
@@ -89,6 +94,7 @@
     // <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
     private static final String IMG_PREFIX = "R.drawable.";
     private static final String DRAWABLE_FOLDER = "drawable";
+    static final int MENU_ID_SEND_FEEDBACK = 0;
 
     protected TopIntroPreference mTopIntroPreference;
     protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
@@ -102,6 +108,7 @@
     protected Intent mSettingsIntent;
     // The mComponentName maybe null, such as Magnify
     protected ComponentName mComponentName;
+    @Nullable private FeedbackManager mFeedbackManager;
     protected CharSequence mFeatureName;
     protected Uri mImageUri;
     protected CharSequence mHtmlDescription;
@@ -241,6 +248,24 @@
     }
 
     @Override
+    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
+        if (getFeedbackManager().isAvailable()) {
+            menu.add(Menu.NONE, MENU_ID_SEND_FEEDBACK, Menu.NONE,
+                    R.string.accessibility_send_feedback_title);
+        }
+        super.onCreateOptionsMenu(menu, inflater);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+        if (item.getItemId() == MENU_ID_SEND_FEEDBACK) {
+            getFeedbackManager().sendFeedback();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
     public int getDialogMetricsCategory(int dialogId) {
         switch (dialogId) {
             case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
@@ -287,6 +312,11 @@
         return getString(R.string.accessibility_shortcut_title, mFeatureName);
     }
 
+    @VisibleForTesting
+    CharSequence getContentDescriptionForAnimatedIllustration() {
+        return getString(R.string.accessibility_illustration_content_description, mFeatureName);
+    }
+
     protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
     }
 
@@ -403,22 +433,38 @@
 
         return drawable;
     }
-
     private void initAnimatedImagePreference() {
-        if (mImageUri == null) {
+        initAnimatedImagePreference(mImageUri, new IllustrationPreference(getPrefContext()));
+    }
+
+    @VisibleForTesting
+    void initAnimatedImagePreference(
+            @Nullable Uri imageUri,
+            @NonNull IllustrationPreference preference) {
+        if (imageUri == null) {
             return;
         }
 
         final int displayHalfHeight =
                 AccessibilityUtil.getDisplayBounds(getPrefContext()).height() / 2;
-        final IllustrationPreference illustrationPreference =
-                new IllustrationPreference(getPrefContext());
-        illustrationPreference.setImageUri(mImageUri);
-        illustrationPreference.setSelectable(false);
-        illustrationPreference.setMaxHeight(displayHalfHeight);
-        illustrationPreference.setKey(KEY_ANIMATED_IMAGE);
-
-        getPreferenceScreen().addPreference(illustrationPreference);
+        preference.setImageUri(imageUri);
+        preference.setSelectable(false);
+        preference.setMaxHeight(displayHalfHeight);
+        preference.setKey(KEY_ANIMATED_IMAGE);
+        preference.setOnBindListener(view -> {
+            // isAnimatable is decided in
+            // {@link IllustrationPreference#onBindViewHolder(PreferenceViewHolder)}. Therefore, we
+            // wait until the view is bond to set the content description for it.
+            // The content description is added for an animation illustration only. Since the static
+            // images are decorative.
+            ThreadUtils.getUiThreadHandler().post(() -> {
+                if (preference.isAnimatable()) {
+                    preference.setContentDescription(
+                            getContentDescriptionForAnimatedIllustration());
+                }
+            });
+        });
+        getPreferenceScreen().addPreference(preference);
     }
 
     @VisibleForTesting
@@ -739,4 +785,28 @@
                 super.onCreateRecyclerView(inflater, parent, savedInstanceState);
         return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView);
     }
+
+    @VisibleForTesting
+    void setFeedbackManager(FeedbackManager feedbackManager) {
+        this.mFeedbackManager = feedbackManager;
+    }
+
+    private FeedbackManager getFeedbackManager() {
+        if (mFeedbackManager == null) {
+            mFeedbackManager = new FeedbackManager(getActivity(), getFeedbackCategory());
+        }
+        return mFeedbackManager;
+    }
+
+    /**
+     * Returns the category of the feedback page.
+     *
+     * <p>By default, this method returns {@link SettingsEnums#PAGE_UNKNOWN}. This indicates that
+     * the feedback category is unknown, and the absence of a feedback menu.
+     *
+     * @return The feedback category, which is {@link SettingsEnums#PAGE_UNKNOWN} by default.
+     */
+    protected int getFeedbackCategory() {
+        return SettingsEnums.PAGE_UNKNOWN;
+    }
 }
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
index 8b52507..71c95c0 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
@@ -93,6 +93,8 @@
     private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
     @Nullable
     private DialogCreatable mMagnificationModeDialogDelegate;
+    @Nullable
+    private DialogCreatable mMagnificationCursorFollowingModeDialogDelegate;
 
     @Nullable
     MagnificationOneFingerPanningPreferenceController mOneFingerPanningPreferenceController;
@@ -104,6 +106,12 @@
         mMagnificationModeDialogDelegate = delegate;
     }
 
+    @VisibleForTesting
+    public void setMagnificationCursorFollowingModeDialogDelegate(
+            @NonNull DialogCreatable delegate) {
+        mMagnificationCursorFollowingModeDialogDelegate = delegate;
+    }
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -186,6 +194,9 @@
             case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
                 return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
                         .onCreateDialog(dialogId);
+            case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE:
+                return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate)
+                        .onCreateDialog(dialogId);
             case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
                 return AccessibilityShortcutsTutorial
                         .showAccessibilityGestureTutorialDialog(getPrefContext());
@@ -201,6 +212,11 @@
                 PackageManager.FEATURE_WINDOW_MAGNIFICATION);
     }
 
+    private static boolean isMagnificationCursorFollowingModeDialogSupported() {
+        // TODO(b/398066000): Hide the setting when no pointer device exists for most form factors.
+        return com.android.settings.accessibility.Flags.enableMagnificationCursorFollowingDialog();
+    }
+
     @Override
     protected void initSettingsPreference() {
         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
@@ -213,6 +229,7 @@
             addJoystickSetting(generalCategory);
             // LINT.ThenChange(:search_data)
         }
+        addCursorFollowingSetting(generalCategory);
         addFeedbackSetting(generalCategory);
     }
 
@@ -286,6 +303,31 @@
         addPreferenceController(magnificationModePreferenceController);
     }
 
+    private static Preference createCursorFollowingPreference(Context context) {
+        final Preference pref = new Preference(context);
+        pref.setTitle(R.string.accessibility_magnification_cursor_following_title);
+        pref.setKey(MagnificationCursorFollowingModePreferenceController.PREF_KEY);
+        pref.setPersistent(false);
+        return pref;
+    }
+
+    private void addCursorFollowingSetting(PreferenceCategory generalCategory) {
+        if (!isMagnificationCursorFollowingModeDialogSupported()) {
+            return;
+        }
+
+        generalCategory.addPreference(createCursorFollowingPreference(getPrefContext()));
+
+        final MagnificationCursorFollowingModePreferenceController controller =
+                new MagnificationCursorFollowingModePreferenceController(
+                        getContext(),
+                        MagnificationCursorFollowingModePreferenceController.PREF_KEY);
+        controller.setDialogHelper(/* dialogHelper= */this);
+        mMagnificationCursorFollowingModeDialogDelegate = controller;
+        controller.displayPreference(getPreferenceScreen());
+        addPreferenceController(controller);
+    }
+
     private static Preference createFollowTypingPreference(Context context) {
         final Preference pref = new SwitchPreferenceCompat(context);
         pref.setTitle(R.string.accessibility_screen_magnification_follow_typing_title);
@@ -510,6 +552,9 @@
             case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
                 return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
                         .getDialogMetricsCategory(dialogId);
+            case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE:
+                return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate)
+                        .getDialogMetricsCategory(dialogId);
             case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
                 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
             case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL:
@@ -667,6 +712,11 @@
                         return rawData;
                     }
 
+                    // Add all preferences to search raw data so that they are included in
+                    // indexing, which happens infrequently. Irrelevant preferences should be
+                    // hidden from the live returned search results by `getNonIndexableKeys`,
+                    // which is called every time a search occurs. This allows for dynamic search
+                    // entries that hide or show depending on current device state.
                     rawData.add(createShortcutPreferenceSearchData(context));
                     Stream.of(
                                     createMagnificationModePreference(context),
@@ -674,6 +724,7 @@
                                     createOneFingerPanningPreference(context),
                                     createAlwaysOnPreference(context),
                                     createJoystickPreference(context),
+                                    createCursorFollowingPreference(context),
                                     createFeedbackPreference(context)
                             )
                             .forEach(pref ->
@@ -714,6 +765,10 @@
                         }
                     }
 
+                    if (!isMagnificationCursorFollowingModeDialogSupported()) {
+                        niks.add(MagnificationCursorFollowingModePreferenceController.PREF_KEY);
+                    }
+
                     if (!Flags.enableLowVisionHats()) {
                         niks.add(MagnificationFeedbackPreferenceController.PREF_KEY);
                     }
diff --git a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
index 10813a7..eb0c93b 100644
--- a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
@@ -80,6 +80,12 @@
     }
 
     @Override
+    public int getFeedbackCategory() {
+        // The feedback options should not be displayed on the setup wizard page.
+        return SettingsEnums.PAGE_UNKNOWN;
+    }
+
+    @Override
     public void onStop() {
         // Log the final choice in value if it's different from the previous value.
         if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
diff --git a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
index 10796b5..14dc0bc 100644
--- a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
@@ -80,6 +80,12 @@
     }
 
     @Override
+    public int getFeedbackCategory() {
+        // The feedback options should not be displayed on the setup wizard page.
+        return SettingsEnums.PAGE_UNKNOWN;
+    }
+
+    @Override
     public void onStop() {
         // Log the final choice in value if it's different from the previous value.
         if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
diff --git a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
index 826583d..ad1f823 100644
--- a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
+++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
@@ -95,8 +95,7 @@
                     userHandle)) {
                 if (RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(context,
                         DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, userHandle.getIdentifier())) {
-                    return context.getString(com.android.settingslib.widget.restricted
-                            .R.string.disabled_by_advanced_protection);
+                    return context.getString(com.android.settingslib.R.string.disabled);
                 } else {
                     return context.getString(
                             com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
index ef19709..83f23bd 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
@@ -107,7 +107,10 @@
     // intent will include this extra containing a bundle of the form:
     // "modality" -> consented (boolean).
     public static final String EXTRA_PARENTAL_CONSENT_STATUS = "consent_status";
-
+    // Whether the face enrollment should be launched first when there are multiple biometrics
+    // supported.
+    public static final String EXTRA_LAUNCH_FACE_ENROLL_FIRST =
+            "launch_face_enroll_first";
     private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials";
     private static final String SAVED_STATE_IS_SINGLE_ENROLLING =
             "is_single_enrolling";
@@ -130,6 +133,7 @@
     private boolean mIsFingerprintEnrollable = false;
     private boolean mParentalOptionsRequired = false;
     private boolean mSkipReturnToParent = false;
+    private boolean mLaunchFaceEnrollFirst = false;
     private Bundle mParentalOptions;
     @Nullable private Long mGkPwHandle;
     @Nullable private ParentalConsentHelper mParentalConsentHelper;
@@ -214,6 +218,7 @@
 
         mParentalOptionsRequired = intent.getBooleanExtra(EXTRA_REQUIRE_PARENTAL_CONSENT, false);
         mSkipReturnToParent = intent.getBooleanExtra(EXTRA_SKIP_RETURN_TO_PARENT, false);
+        mLaunchFaceEnrollFirst = intent.getBooleanExtra(EXTRA_LAUNCH_FACE_ENROLL_FIRST, false);
 
         // determine what can be enrolled
         final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
@@ -221,6 +226,7 @@
 
         Log.d(TAG, "parentalOptionsRequired: " + mParentalOptionsRequired
                 + ", skipReturnToParent: " + mSkipReturnToParent
+                + ", launchFaceEnrollFirst: " + mLaunchFaceEnrollFirst
                 + ", isSetupWizard: " + isSetupWizard
                 + ", isMultiSensor: " + isMultiSensor);
 
@@ -356,7 +362,8 @@
         } else if (canUseFace || canUseFingerprint) {
             if (mGkPwHandle == null) {
                 setOrConfirmCredentialsNow();
-            } else if (canUseFingerprint && mIsFingerprintEnrollable) {
+            } else if (canUseFingerprint && mIsFingerprintEnrollable
+                    && !(canUseFace && mIsFaceEnrollable && mLaunchFaceEnrollFirst)) {
                 launchFingerprintOnlyEnroll();
             } else if (canUseFace && mIsFaceEnrollable) {
                 launchFaceOnlyEnroll();
@@ -510,7 +517,8 @@
             int requestCode, int resultCode, Intent data) {
 
         Log.d(TAG, "handleOnActivityResultWhileEnrolling, request = " + requestCode + ""
-                + ", resultCode = " + resultCode);
+                + ", resultCode = " + resultCode + ", launchFaceEnrollFirst="
+                + mLaunchFaceEnrollFirst);
         switch (requestCode) {
             case REQUEST_HANDOFF_PARENT:
                 setResult(RESULT_OK, newResultIntent());
@@ -526,7 +534,8 @@
                     // SetupFingerprintEnrollIntroduction/FingerprintEnrollmentActivity
                     TransitionHelper.applyForwardTransition(this, TRANSITION_FADE_THROUGH);
                     updateGatekeeperPasswordHandle(data);
-                    if (mIsFingerprintEnrollable) {
+                    if (mIsFingerprintEnrollable
+                            && !(mIsFaceEnrollable && mLaunchFaceEnrollFirst)) {
                         launchFingerprintOnlyEnroll();
                     } else {
                         launchFaceOnlyEnroll();
@@ -548,7 +557,7 @@
                 }
                 if ((resultCode == BiometricEnrollBase.RESULT_SKIP
                         || resultCode == BiometricEnrollBase.RESULT_FINISHED)
-                        && mIsFaceEnrollable) {
+                        && mIsFaceEnrollable && !mLaunchFaceEnrollFirst) {
                     // Apply forward animation during the transition from
                     // SetupFingerprintEnroll*/FingerprintEnrollmentActivity to
                     // SetupFaceEnrollIntroduction
@@ -556,6 +565,9 @@
                     mIsPreviousEnrollmentCanceled =
                             resultCode != BiometricEnrollBase.RESULT_FINISHED;
                     launchFaceOnlyEnroll();
+                } else if (resultCode == Activity.RESULT_CANCELED && mIsFaceEnrollable
+                        && mLaunchFaceEnrollFirst) {
+                    launchFaceOnlyEnroll();
                 } else {
                     notifySafetyIssueActionLaunchedIfNeeded(resultCode);
                     finishOrLaunchHandToParent(resultCode);
@@ -563,7 +575,14 @@
                 break;
             case REQUEST_SINGLE_ENROLL_FACE:
                 mIsSingleEnrolling = false;
-                if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable) {
+                if ((resultCode == BiometricEnrollBase.RESULT_SKIP
+                        || resultCode == BiometricEnrollBase.RESULT_FINISHED)
+                        && mIsFingerprintEnrollable && mLaunchFaceEnrollFirst) {
+                    mIsPreviousEnrollmentCanceled =
+                            resultCode != BiometricEnrollBase.RESULT_FINISHED;
+                    launchFingerprintOnlyEnroll();
+                } else if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable
+                        && !mLaunchFaceEnrollFirst) {
                     mIsPreviousEnrollmentCanceled = true;
                     launchFingerprintOnlyEnroll();
                 } else {
diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java
index db6abc3..21b0fa0 100644
--- a/src/com/android/settings/biometrics/BiometricUtils.java
+++ b/src/com/android/settings/biometrics/BiometricUtils.java
@@ -43,6 +43,7 @@
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.settings.R;
 import com.android.settings.SetupWizardUtils;
+import com.android.settings.biometrics.face.FaceEnroll;
 import com.android.settings.biometrics.fingerprint.FingerprintEnroll;
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
 import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
@@ -282,9 +283,7 @@
      */
     public static Intent getFaceIntroIntent(@NonNull Context context,
             @NonNull Intent activityIntent) {
-        final Intent intent = new Intent(context,
-                FeatureFactory.getFeatureFactory().getFaceFeatureProvider()
-                        .getEnrollActivityClassProvider().getNext());
+        final Intent intent = new Intent(context, FaceEnroll.class);
         WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
         return intent;
     }
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
index 8cc7d6a..42029ff 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
@@ -28,6 +28,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.utils.ThreadUtils;
@@ -76,17 +77,22 @@
         mContentKey = contentKey;
         String authority = new ActiveUnlockStatusUtils(mContext).getAuthority();
         if (authority != null) {
-            mUri = new Uri.Builder()
-                    .scheme(ContentResolver.SCHEME_CONTENT)
-                    .authority(authority)
-                    .appendPath(CONTENT_PROVIDER_PATH)
-                    .build();
+            mUri = getUri(authority);
         } else {
             mUri = null;
         }
 
     }
 
+    /** Returns Active Unlock Uri. */
+    public static @NonNull Uri getUri(@NonNull String authority) {
+        return new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority(authority)
+                    .appendPath(CONTENT_PROVIDER_PATH)
+                    .build();
+    }
+
     /** Returns true if start listening for updates from the ContentProvider, false otherwise. */
     public synchronized boolean subscribe() {
         if (mSubscribed || mUri == null) {
@@ -123,25 +129,40 @@
             Log.e(mLogTag, "Uri null when trying to fetch content");
             return;
         }
-        ContentResolver contentResolver = mContext.getContentResolver();
-        ContentProviderClient client = contentResolver.acquireContentProviderClient(mUri);
-        Bundle bundle;
-        try {
-            bundle = client.call(mMethodName, null /* arg */, null /* extras */);
-        } catch (RemoteException e) {
-            Log.e(mLogTag, "Failed to call contentProvider", e);
-            return;
-        } finally {
-            client.close();
-        }
-        if (bundle == null) {
-            Log.e(mLogTag, "Null bundle returned from contentProvider");
-            return;
-        }
-        String newValue = bundle.getString(mContentKey);
+
+        @Nullable String newValue = getContentFromUri(
+            mContext, mUri, mLogTag, mMethodName, mContentKey);
         if (!TextUtils.equals(mContent, newValue)) {
             mContent = newValue;
             mContentChangedListener.onContentChanged(mContent);
         }
     }
+
+    /** Get the content from Uri. */
+    public static @Nullable String getContentFromUri(
+            @NonNull Context context,
+            @NonNull Uri uri,
+            @NonNull String logTag,
+            @NonNull String methodName,
+            @NonNull String contentKey) {
+        ContentResolver contentResolver = context.getContentResolver();
+        ContentProviderClient client = contentResolver.acquireContentProviderClient(uri);
+
+        @Nullable Bundle bundle = null;
+
+        try {
+            bundle = client.call(methodName, /* arg= */ null, /* extras = */ null);
+        } catch (RemoteException e) {
+            Log.e(logTag, "Failed to call contentProvider", e);
+        } finally {
+            client.close();
+        }
+
+        if (bundle == null) {
+            Log.e(logTag, "Null bundle returned from contentProvider");
+            return null;
+        }
+
+        return bundle.getString(contentKey);
+    }
 }
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
index 1badb0f..9e81762 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
@@ -21,8 +21,8 @@
 /** Listens to device name updates from the content provider and fetches the latest value. */
 public class ActiveUnlockDeviceNameListener  {
     private static final String TAG = "ActiveUnlockDeviceNameListener";
-    private static final String METHOD_NAME = "getDeviceName";
-    private static final String DEVICE_NAME_KEY = "com.android.settings.active_unlock.device_name";
+    static final String METHOD_NAME = "getDeviceName";
+    static final String DEVICE_NAME_KEY = "com.android.settings.active_unlock.device_name";
 
     private final ActiveUnlockContentListener mActiveUnlockContentListener;
     public ActiveUnlockDeviceNameListener(
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
index 4ff2e90..66485d3 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
@@ -156,9 +156,16 @@
     }
 
     /**
+     * Returns the title of active unlock only.
+     */
+    public @NonNull String getTitleForActiveUnlockOnly() {
+        return mContext.getString(R.string.security_settings_activeunlock);
+    }
+
+    /**
      * Returns the title of the combined biometric settings entity when active unlock is enabled.
      */
-    public String getTitleForActiveUnlock() {
+    public @NonNull String getTitleForActiveUnlock() {
         final boolean faceAllowed = Utils.hasFaceHardware(mContext);
         final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
         return mContext.getString(getTitleRes(faceAllowed, fingerprintAllowed));
@@ -264,6 +271,30 @@
         return mContext.getString(getUseBiometricTitleRes(faceAllowed, fingerprintAllowed));
     }
 
+    /**
+     * Returns the summary from content provider.
+     */
+    @Nullable
+    public static String getSummaryFromContentProvider(
+            @NonNull Context context, @NonNull String authority, @NonNull String logTag) {
+        return ActiveUnlockContentListener.getContentFromUri(
+            context, ActiveUnlockContentListener.getUri(authority), logTag,
+            ActiveUnlockSummaryListener.METHOD_NAME,
+            ActiveUnlockSummaryListener.SUMMARY_KEY);
+    }
+
+    /**
+     * Returns the device name from content provider.
+     */
+    @Nullable
+    public static String getDeviceNameFromContentProvider(
+            @NonNull Context context, @NonNull String authority, @NonNull String logTag) {
+        return ActiveUnlockContentListener.getContentFromUri(
+            context, ActiveUnlockContentListener.getUri(authority), logTag,
+            ActiveUnlockDeviceNameListener.METHOD_NAME,
+            ActiveUnlockDeviceNameListener.DEVICE_NAME_KEY);
+    }
+
     @StringRes
     private static int getUseBiometricTitleRes(
             boolean isFaceAllowed, boolean isFingerprintAllowed) {
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
index bcffe62..38e137b 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
@@ -21,8 +21,8 @@
 /** Listens to summary updates from the content provider and fetches the latest value. */
 public class ActiveUnlockSummaryListener {
     private static final String TAG = "ActiveUnlockSummaryListener";
-    private static final String METHOD_NAME = "getSummary";
-    private static final String SUMMARY_KEY = "com.android.settings.summary";
+    static final String METHOD_NAME = "getSummary";
+    static final String SUMMARY_KEY = "com.android.settings.summary";
 
     private final ActiveUnlockContentListener mContentListener;
     public ActiveUnlockSummaryListener(
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index 1d8b7a1..ed4b713 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -66,7 +66,7 @@
     @VisibleForTesting
     static final int CONFIRM_REQUEST = 2001;
     private static final int CHOOSE_LOCK_REQUEST = 2002;
-    protected static final int ACTIVE_UNLOCK_REQUEST = 2003;
+    public static final int ACTIVE_UNLOCK_REQUEST = 2003;
     @VisibleForTesting
     static final int BIOMETRIC_AUTH_REQUEST = 2004;
 
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
index a1bb84c..ec8d7bc 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
@@ -34,7 +34,10 @@
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Process;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -51,24 +54,21 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.VolumeControlProfile;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.utils.ThreadUtils;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.HashMap;
+import java.util.Map;
 
 public class AudioStreamMediaService extends Service {
     static final String BROADCAST_ID = "audio_stream_media_service_broadcast_id";
     static final String BROADCAST_TITLE = "audio_stream_media_service_broadcast_title";
     static final String DEVICES = "audio_stream_media_service_devices";
     private static final String TAG = "AudioStreamMediaService";
-    private static final int NOTIFICATION_ID = 1;
+    private static final int NOTIFICATION_ID = R.string.audio_streams_title;
     private static final int BROADCAST_LISTENING_NOW_TEXT = R.string.audio_streams_listening_now;
     private static final int BROADCAST_STREAM_PAUSED_TEXT = R.string.audio_streams_present_now;
     @VisibleForTesting static final String LEAVE_BROADCAST_ACTION = "leave_broadcast_action";
@@ -113,17 +113,16 @@
 
     private final MetricsFeatureProvider mMetricsFeatureProvider =
             FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
-    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
-    private final AtomicBoolean mIsMuted = new AtomicBoolean(false);
-    private final AtomicBoolean mIsHysteresis = new AtomicBoolean(false);
+    private final HandlerThread mHandlerThread = new HandlerThread(TAG,
+            Process.THREAD_PRIORITY_BACKGROUND);
+    private boolean mIsMuted = false;
     // Set 25 as default as the volume range from `VolumeControlProfile` is from 0 to 255.
     // If the initial volume from `onDeviceVolumeChanged` is larger than zero (not muted), we will
     // override this value. Otherwise, we raise the volume to 25 when the play button is clicked.
-    private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25);
-    private final Object mLocalSessionLock = new Object();
+    private int mLatestPositiveVolume = 25;
     private boolean mHysteresisModeFixAvailable;
     private int mBroadcastId;
-    @Nullable private List<BluetoothDevice> mDevices;
+    @Nullable private Map<BluetoothDevice, LocalBluetoothLeBroadcastSourceState> mStateByDevice;
     @Nullable private LocalBluetoothManager mLocalBtManager;
     @Nullable private AudioStreamsHelper mAudioStreamsHelper;
     @Nullable private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
@@ -154,7 +153,6 @@
             Log.w(TAG, "onCreate() : mLeBroadcastAssistant is null!");
             return;
         }
-        mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(this);
 
         mNotificationManager = getSystemService(NotificationManager.class);
         if (mNotificationManager == null) {
@@ -162,7 +160,8 @@
             return;
         }
 
-        mExecutor.execute(
+        mHandlerThread.start();
+        getHandler().post(
                 () -> {
                     if (mLocalBtManager == null
                             || mLeBroadcastAssistant == null
@@ -184,45 +183,49 @@
                     mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile();
                     if (mVolumeControl != null) {
                         mVolumeControlCallback = new VolumeControlCallback();
-                        mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback);
+                        mVolumeControl.registerCallback(getHandler()::post, mVolumeControlCallback);
                     }
 
                     mBroadcastAssistantCallback = new AssistantCallback();
                     mLeBroadcastAssistant.registerServiceCallBack(
-                            mExecutor, mBroadcastAssistantCallback);
+                            getHandler()::post, mBroadcastAssistantCallback);
+
+                    mHysteresisModeFixAvailable =
+                            BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(this);
                 });
     }
 
+    @VisibleForTesting
+    Handler getHandler() {
+        return mHandlerThread.getThreadHandler();
+    }
+
     @Override
     public void onDestroy() {
         Log.d(TAG, "onDestroy()");
-        super.onDestroy();
-        if (BluetoothUtils.isAudioSharingUIAvailable(this)) {
-            if (mDevices != null) {
-                mDevices.clear();
-                mDevices = null;
-            }
-            synchronized (mLocalSessionLock) {
-                if (mLocalSession != null) {
-                    mLocalSession.release();
-                    mLocalSession = null;
-                }
-            }
-            mExecutor.execute(
-                    () -> {
-                        if (mLocalBtManager != null) {
-                            mLocalBtManager.getEventManager().unregisterCallback(
-                                    mBluetoothCallback);
-                        }
-                        if (mLeBroadcastAssistant != null && mBroadcastAssistantCallback != null) {
-                            mLeBroadcastAssistant.unregisterServiceCallBack(
-                                    mBroadcastAssistantCallback);
-                        }
-                        if (mVolumeControl != null && mVolumeControlCallback != null) {
-                            mVolumeControl.unregisterCallback(mVolumeControlCallback);
-                        }
-                    });
-        }
+        getHandler().post(
+                () -> {
+                    if (mStateByDevice != null) {
+                        mStateByDevice.clear();
+                        mStateByDevice = null;
+                    }
+                    if (mLocalSession != null) {
+                        mLocalSession.release();
+                        mLocalSession = null;
+                    }
+                    if (mLocalBtManager != null) {
+                        mLocalBtManager.getEventManager().unregisterCallback(
+                                mBluetoothCallback);
+                    }
+                    if (mLeBroadcastAssistant != null && mBroadcastAssistantCallback != null) {
+                        mLeBroadcastAssistant.unregisterServiceCallBack(
+                                mBroadcastAssistantCallback);
+                    }
+                    if (mVolumeControl != null && mVolumeControlCallback != null) {
+                        mVolumeControl.unregisterCallback(mVolumeControlCallback);
+                    }
+                });
+        mHandlerThread.quitSafely();
     }
 
     @Override
@@ -233,53 +236,59 @@
             stopSelf();
             return START_NOT_STICKY;
         }
-        mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
-        if (mBroadcastId == -1) {
-            Log.w(TAG, "Invalid broadcast ID. Service will not start.");
-            stopSelf();
-            return START_NOT_STICKY;
-        }
-        var extra = intent.getParcelableArrayListExtra(DEVICES, BluetoothDevice.class);
-        if (extra == null || extra.isEmpty()) {
-            Log.w(TAG, "No device. Service will not start.");
-            stopSelf();
-            return START_NOT_STICKY;
-        }
-        mDevices = Collections.synchronizedList(extra);
-        MediaSession.Token token =
-                getOrCreateLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
-        startForeground(NOTIFICATION_ID, buildNotification(token));
+        getHandler().post(() -> {
+            mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
+            if (mBroadcastId == -1) {
+                Log.w(TAG, "Invalid broadcast ID. Service will not start.");
+                stopSelf();
+                return;
+            }
+            var devices = intent.getParcelableArrayListExtra(DEVICES, BluetoothDevice.class);
+            if (devices == null || devices.isEmpty()) {
+                Log.w(TAG, "No device. Service will not start.");
+                stopSelf();
+            } else {
+                mStateByDevice = new HashMap<>();
+                devices.forEach(d -> mStateByDevice.put(d, STREAMING));
+                MediaSession.Token token =
+                        getOrCreateLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
+                startForeground(NOTIFICATION_ID, buildNotification(token));
+            }
+        });
         return START_NOT_STICKY;
     }
 
     private MediaSession.Token getOrCreateLocalMediaSession(String title) {
-        synchronized (mLocalSessionLock) {
-            if (mLocalSession != null) {
-                return mLocalSession.getSessionToken();
-            }
-            mLocalSession = new MediaSession(this, TAG);
-            mLocalSession.setMetadata(
-                    new MediaMetadata.Builder()
-                            .putString(MediaMetadata.METADATA_KEY_TITLE, title)
-                            .putLong(MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION)
-                            .build());
-            mLocalSession.setActive(true);
-            mLocalSession.setPlaybackState(getPlaybackState());
-            mMediaSessionCallback = new MediaSessionCallback();
-            mLocalSession.setCallback(mMediaSessionCallback);
+        if (mLocalSession != null) {
             return mLocalSession.getSessionToken();
         }
+        mLocalSession = new MediaSession(this, TAG);
+        mLocalSession.setMetadata(
+                new MediaMetadata.Builder()
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, title)
+                        .putLong(MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION)
+                        .build());
+        mLocalSession.setActive(true);
+        mLocalSession.setPlaybackState(getPlaybackState());
+        mMediaSessionCallback = new MediaSessionCallback();
+        mLocalSession.setCallback(mMediaSessionCallback, getHandler());
+        return mLocalSession.getSessionToken();
     }
 
     private PlaybackState getPlaybackState() {
-        if (mIsHysteresis.get()) {
+        if (isAllDeviceHysteresis()) {
             return mPlayStateHysteresisBuilder.build();
         }
-        return mIsMuted.get() ? mPlayStatePausingBuilder.build() : mPlayStatePlayingBuilder.build();
+        return mIsMuted ? mPlayStatePausingBuilder.build() : mPlayStatePlayingBuilder.build();
+    }
+
+    private boolean isAllDeviceHysteresis() {
+        return mHysteresisModeFixAvailable && mStateByDevice != null
+                && mStateByDevice.values().stream().allMatch(v -> v == PAUSED);
     }
 
     private String getDeviceName() {
-        if (mDevices == null || mDevices.isEmpty() || mLocalBtManager == null) {
+        if (mStateByDevice == null || mStateByDevice.isEmpty() || mLocalBtManager == null) {
             return DEFAULT_DEVICE_NAME;
         }
 
@@ -288,7 +297,8 @@
             return DEFAULT_DEVICE_NAME;
         }
 
-        CachedBluetoothDevice device = manager.findDevice(mDevices.get(0));
+        CachedBluetoothDevice device = manager.findDevice(
+                mStateByDevice.keySet().iterator().next());
         return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
     }
 
@@ -304,7 +314,7 @@
                         .setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
                         .setStyle(mediaStyle)
                         .setContentText(getString(
-                                mIsHysteresis.get() ? BROADCAST_STREAM_PAUSED_TEXT :
+                                isAllDeviceHysteresis() ? BROADCAST_STREAM_PAUSED_TEXT :
                                         BROADCAST_LISTENING_NOW_TEXT))
                         .setSilent(true);
         return notificationBuilder.build();
@@ -333,7 +343,8 @@
         public void onReceiveStateChanged(
                 BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
             super.onReceiveStateChanged(sink, sourceId, state);
-            if (!mHysteresisModeFixAvailable || mDevices == null || !mDevices.contains(sink)) {
+            if (!mHysteresisModeFixAvailable || mStateByDevice == null
+                    || !mStateByDevice.containsKey(sink)) {
                 return;
             }
             var sourceState = LocalBluetoothLeBroadcastAssistant.getLocalSourceState(state);
@@ -343,12 +354,10 @@
             if (!streaming && !paused) {
                 return;
             }
-            // Atomically update mIsHysteresis if its current value is not the current paused state
-            if (mIsHysteresis.compareAndSet(!paused, paused)) {
-                synchronized (mLocalSessionLock) {
-                    if (mLocalSession == null) {
-                        return;
-                    }
+            boolean shouldUpdate = mStateByDevice.get(sink) != sourceState;
+            if (shouldUpdate) {
+                mStateByDevice.put(sink, sourceState);
+                if (mLocalSession != null) {
                     mLocalSession.setPlaybackState(getPlaybackState());
                     if (mNotificationManager != null) {
                         mNotificationManager.notify(
@@ -356,7 +365,7 @@
                                 buildNotification(mLocalSession.getSessionToken())
                         );
                     }
-                    Log.d(TAG, "updating hysteresis mode to : " + paused);
+                    Log.d(TAG, "updating source state to : " + sourceState);
                 }
             }
         }
@@ -374,24 +383,22 @@
         @Override
         public void onDeviceVolumeChanged(
                 @NonNull BluetoothDevice device, @IntRange(from = -255, to = 255) int volume) {
-            if (mDevices == null || mDevices.isEmpty()) {
+            if (mStateByDevice == null || mStateByDevice.isEmpty()) {
                 Log.w(TAG, "active device or device has source is null!");
                 return;
             }
             Log.d(
                     TAG,
                     "onDeviceVolumeChanged() bluetoothDevice : " + device + " volume: " + volume);
-            if (mDevices.contains(device)) {
+            if (mStateByDevice.containsKey(device)) {
                 if (volume == 0) {
-                    mIsMuted.set(true);
+                    mIsMuted = true;
                 } else {
-                    mIsMuted.set(false);
-                    mLatestPositiveVolume.set(volume);
+                    mIsMuted = false;
+                    mLatestPositiveVolume = volume;
                 }
-                synchronized (mLocalSessionLock) {
-                    if (mLocalSession != null) {
-                        mLocalSession.setPlaybackState(getPlaybackState());
-                    }
+                if (mLocalSession != null) {
+                    mLocalSession.setPlaybackState(getPlaybackState());
                 }
             }
         }
@@ -400,10 +407,12 @@
     private class BtCallback implements BluetoothCallback {
         @Override
         public void onBluetoothStateChanged(int bluetoothState) {
-            if (BluetoothAdapter.STATE_OFF == bluetoothState) {
-                Log.d(TAG, "onBluetoothStateChanged() : stopSelf");
-                stopSelf();
-            }
+            getHandler().post(() -> {
+                if (BluetoothAdapter.STATE_OFF == bluetoothState) {
+                    Log.d(TAG, "onBluetoothStateChanged() : stopSelf");
+                    stopSelf();
+                }
+            });
         }
 
         @Override
@@ -411,24 +420,17 @@
                 @NonNull CachedBluetoothDevice cachedDevice,
                 @ConnectionState int state,
                 int bluetoothProfile) {
-            if (state == BluetoothAdapter.STATE_DISCONNECTED
-                    && bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
-                    && mDevices != null) {
-                mDevices.remove(cachedDevice.getDevice());
-                cachedDevice
-                        .getMemberDevice()
-                        .forEach(
-                                m -> {
-                                    // Check nullability to pass NullAway check
-                                    if (mDevices != null) {
-                                        mDevices.remove(m.getDevice());
-                                    }
-                                });
-            }
-            if (mDevices == null || mDevices.isEmpty()) {
-                Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
-                stopSelf();
-            }
+            getHandler().post(() -> {
+                if (state == BluetoothAdapter.STATE_DISCONNECTED
+                        && bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+                        && mStateByDevice != null) {
+                    mStateByDevice.remove(cachedDevice.getDevice());
+                }
+                if (mStateByDevice == null || mStateByDevice.isEmpty()) {
+                    Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
+                    stopSelf();
+                }
+            });
         }
     }
 
@@ -454,10 +456,8 @@
         @Override
         public void onSeekTo(long pos) {
             Log.d(TAG, "onSeekTo: " + pos);
-            synchronized (mLocalSessionLock) {
-                if (mLocalSession != null) {
-                    mLocalSession.setPlaybackState(getPlaybackState());
-                }
+            if (mLocalSession != null) {
+                mLocalSession.setPlaybackState(getPlaybackState());
             }
         }
 
@@ -484,28 +484,26 @@
     }
 
     private void handleOnPlay() {
-        if (mDevices == null || mDevices.isEmpty()) {
+        if (mStateByDevice == null || mStateByDevice.isEmpty()) {
             Log.w(TAG, "active device or device has source is null!");
             return;
         }
-        Log.d(
-                TAG,
-                "onPlay() setting volume for device : "
-                        + mDevices.getFirst()
-                        + " volume: "
-                        + mLatestPositiveVolume.get());
-        setDeviceVolume(mDevices.getFirst(), mLatestPositiveVolume.get());
+        mStateByDevice.keySet().forEach(device -> {
+            Log.d(TAG, "onPlay() setting volume for device : " + device + " volume: "
+                    + mLatestPositiveVolume);
+            setDeviceVolume(device, mLatestPositiveVolume);
+        });
     }
 
     private void handleOnPause() {
-        if (mDevices == null || mDevices.isEmpty()) {
+        if (mStateByDevice == null || mStateByDevice.isEmpty()) {
             Log.w(TAG, "active device or device has source is null!");
             return;
         }
-        Log.d(
-                TAG,
-                "onPause() setting volume for device : " + mDevices.getFirst() + " volume: " + 0);
-        setDeviceVolume(mDevices.getFirst(), /* volume= */ 0);
+        mStateByDevice.keySet().forEach(device -> {
+            Log.d(TAG, "onPause() setting volume for device : " + device + " volume: " + 0);
+            setDeviceVolume(device, /* volume= */ 0);
+        });
     }
 
     private void setDeviceVolume(BluetoothDevice device, int volume) {
@@ -514,7 +512,7 @@
                 ThreadUtils.postOnBackgroundThread(
                         () -> {
                             if (mVolumeControl != null) {
-                                mVolumeControl.setDeviceVolume(device, volume, true);
+                                mVolumeControl.setDeviceVolume(device, volume, false);
                                 mMetricsFeatureProvider.action(
                                         getApplicationContext(), event, volume == 0 ? 1 : 0);
                             }
diff --git a/src/com/android/settings/connecteddevice/display/DesktopExperienceFlags.kt b/src/com/android/settings/connecteddevice/display/DesktopExperienceFlags.kt
new file mode 100644
index 0000000..c6ce00d
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/display/DesktopExperienceFlags.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 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.display
+
+import android.window.DesktopExperienceFlags.DesktopExperienceFlag
+import com.android.settings.flags.FeatureFlags
+
+/** Class handling Settings flags, but using the Desktop Experience developer option overrides. */
+class DesktopExperienceFlags(private val featureFlagsImpl: FeatureFlags) : FeatureFlags by featureFlagsImpl {
+
+    private val displayTopologyPaneInDisplayListFlag =
+        DesktopExperienceFlag(
+            featureFlagsImpl::displayTopologyPaneInDisplayList,
+            /* shouldOverrideByDevOption= */ true,
+        )
+
+    override fun displayTopologyPaneInDisplayList(): Boolean =
+        displayTopologyPaneInDisplayListFlag.isTrue
+
+    private val displaySizeConnectedDisplaySettingFlag =
+        DesktopExperienceFlag(
+            featureFlagsImpl::displaySizeConnectedDisplaySetting,
+            /* shouldOverrideByDevOption= */ true,
+        )
+
+    override fun displaySizeConnectedDisplaySetting(): Boolean =
+        displaySizeConnectedDisplaySettingFlag.isTrue
+}
\ No newline at end of file
diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java b/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java
index 1148aa5..52ec8d2 100644
--- a/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java
+++ b/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java
@@ -109,7 +109,8 @@
         private final Handler mHandler;
 
         Injector(@Nullable Context context) {
-            this(context, new FeatureFlagsImpl(), new Handler(Looper.getMainLooper()));
+            this(context, new DesktopExperienceFlags(new FeatureFlagsImpl()),
+                    new Handler(Looper.getMainLooper()));
         }
 
         Injector(@Nullable Context context, @NonNull FeatureFlags flags, @NonNull Handler handler) {
diff --git a/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
index 700b601..a145913 100644
--- a/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
+++ b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
@@ -25,11 +25,11 @@
 
 import com.android.settings.R;
 import com.android.settings.core.SliderPreferenceController;
-import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.widget.SliderPreference;
 
 public class NightDisplayIntensityPreferenceController extends SliderPreferenceController {
 
-    private ColorDisplayManager mColorDisplayManager;
+    private final ColorDisplayManager mColorDisplayManager;
 
     public NightDisplayIntensityPreferenceController(Context context, String key) {
         super(context, key);
@@ -64,11 +64,11 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
-        final SeekBarPreference preference = screen.findPreference(getPreferenceKey());
-        preference.setContinuousUpdates(true);
+        SliderPreference preference = screen.findPreference(getPreferenceKey());
+        preference.setUpdatesContinuously(true);
         preference.setMax(getMax());
         preference.setMin(getMin());
-        preference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
+        // TODO(b/394828723) add haptic feedback
     }
 
     @Override
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index a248bdf..2681067 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.fuelgauge.batteryusage;
 
+import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_ALL;
+import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_INVALID;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.app.settings.SettingsEnums;
@@ -82,10 +85,10 @@
     @VisibleForTesting TextView mChartSummaryTextView;
     @VisibleForTesting BatteryChartView mDailyChartView;
     @VisibleForTesting BatteryChartView mHourlyChartView;
-    @VisibleForTesting int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
-    @VisibleForTesting int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
-    @VisibleForTesting int mDailyHighlightSlotIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
-    @VisibleForTesting int mHourlyHighlightSlotIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
+    @VisibleForTesting int mDailyChartIndex = SELECTED_INDEX_ALL;
+    @VisibleForTesting int mHourlyChartIndex = SELECTED_INDEX_ALL;
+    @VisibleForTesting int mDailyHighlightSlotIndex = SELECTED_INDEX_INVALID;
+    @VisibleForTesting int mHourlyHighlightSlotIndex = SELECTED_INDEX_INVALID;
 
     private boolean mIs24HourFormat;
     private View mBatteryChartViewGroup;
@@ -198,8 +201,8 @@
                 getTotalHours(batteryLevelData));
 
         if (batteryLevelData == null) {
-            mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
-            mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
+            mDailyChartIndex = SELECTED_INDEX_ALL;
+            mHourlyChartIndex = SELECTED_INDEX_ALL;
             mDailyViewModel = null;
             mHourlyViewModels = null;
             refreshUi();
@@ -226,9 +229,9 @@
     }
 
     boolean isHighlightSlotFocused() {
-        return (mDailyHighlightSlotIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID
+        return (mDailyHighlightSlotIndex != SELECTED_INDEX_INVALID
                 && mDailyHighlightSlotIndex == mDailyChartIndex
-                && mHourlyHighlightSlotIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID
+                && mHourlyHighlightSlotIndex != SELECTED_INDEX_INVALID
                 && mHourlyHighlightSlotIndex == mHourlyChartIndex);
     }
 
@@ -242,8 +245,8 @@
     }
 
     void selectHighlightSlotIndex() {
-        if (mDailyHighlightSlotIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
-                || mHourlyHighlightSlotIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID) {
+        if (mDailyHighlightSlotIndex == SELECTED_INDEX_INVALID
+                || mHourlyHighlightSlotIndex == SELECTED_INDEX_INVALID) {
             return;
         }
         if (mDailyHighlightSlotIndex == mDailyChartIndex
@@ -258,8 +261,11 @@
                         "onDailyChartSelect:%d, onHourlyChartSelect:%d",
                         mDailyChartIndex, mHourlyChartIndex));
         refreshUi();
+        // The highlight slot must be selected.
         mHandler.post(
-                () -> mDailyChartView.setAccessibilityPaneTitle(getAccessibilityAnnounceMessage()));
+                () ->
+                        mDailyChartView.setAccessibilityPaneTitle(
+                                getAccessibilityAnnounceMessage(/* isSlotSelected= */ true)));
         if (mOnSelectedIndexUpdatedListener != null) {
             mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated();
         }
@@ -295,15 +301,16 @@
                     }
                     Log.d(TAG, "onDailyChartSelect:" + trapezoidIndex);
                     mDailyChartIndex = trapezoidIndex;
-                    mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
+                    mHourlyChartIndex = SELECTED_INDEX_ALL;
                     refreshUi();
                     mHandler.post(
                             () ->
                                     mDailyChartView.setAccessibilityPaneTitle(
-                                            getAccessibilityAnnounceMessage()));
+                                            getAccessibilityAnnounceMessage(
+                                                    mDailyChartIndex != SELECTED_INDEX_ALL)));
                     mMetricsFeatureProvider.action(
                             mPrefContext,
-                            trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
+                            trapezoidIndex == SELECTED_INDEX_ALL
                                     ? SettingsEnums.ACTION_BATTERY_USAGE_DAILY_SHOW_ALL
                                     : SettingsEnums.ACTION_BATTERY_USAGE_DAILY_TIME_SLOT,
                             mDailyChartIndex);
@@ -314,7 +321,7 @@
         mHourlyChartView = hourlyChartView;
         mHourlyChartView.setOnSelectListener(
                 trapezoidIndex -> {
-                    if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
+                    if (mDailyChartIndex == SELECTED_INDEX_ALL) {
                         // This will happen when a daily slot and an hour slot are clicked together.
                         return;
                     }
@@ -327,10 +334,11 @@
                     mHandler.post(
                             () ->
                                     mHourlyChartView.setAccessibilityPaneTitle(
-                                            getAccessibilityAnnounceMessage()));
+                                            getAccessibilityAnnounceMessage(
+                                                    mHourlyChartIndex != SELECTED_INDEX_ALL)));
                     mMetricsFeatureProvider.action(
                             mPrefContext,
-                            trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
+                            trapezoidIndex == SELECTED_INDEX_ALL
                                     ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
                                     : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT,
                             mHourlyChartIndex);
@@ -378,27 +386,27 @@
         } else {
             mDailyChartView.setVisibility(View.VISIBLE);
             if (mDailyChartIndex >= mDailyViewModel.size()) {
-                mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
+                mDailyChartIndex = SELECTED_INDEX_ALL;
             }
             mDailyViewModel.setSelectedIndex(mDailyChartIndex);
             mDailyViewModel.setHighlightSlotIndex(mDailyHighlightSlotIndex);
             mDailyChartView.setViewModel(mDailyViewModel);
         }
 
-        if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
+        if (mDailyChartIndex == SELECTED_INDEX_ALL) {
             // Multiple days are selected, hide the hourly chart view.
             animateBatteryHourlyChartView(/* visible= */ false);
         } else {
             animateBatteryHourlyChartView(/* visible= */ true);
             final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex);
             if (mHourlyChartIndex >= hourlyViewModel.size()) {
-                mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
+                mHourlyChartIndex = SELECTED_INDEX_ALL;
             }
             hourlyViewModel.setSelectedIndex(mHourlyChartIndex);
             hourlyViewModel.setHighlightSlotIndex(
                     (mDailyChartIndex == mDailyHighlightSlotIndex)
                             ? mHourlyHighlightSlotIndex
-                            : BatteryChartViewModel.SELECTED_INDEX_INVALID);
+                            : SELECTED_INDEX_INVALID);
             mHourlyChartView.setViewModel(hourlyViewModel);
         }
     }
@@ -416,7 +424,7 @@
                 isAccessibilityText
                         ? mDailyViewModel.getContentDescription(mDailyChartIndex)
                         : mDailyViewModel.getFullText(mDailyChartIndex);
-        if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
+        if (mHourlyChartIndex == SELECTED_INDEX_ALL) {
             return selectedDayText;
         }
 
@@ -441,15 +449,19 @@
             return "";
         }
 
-        if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
-                || mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
+        if (mDailyChartIndex == SELECTED_INDEX_ALL || mHourlyChartIndex == SELECTED_INDEX_ALL) {
             return mDailyViewModel.getSlotBatteryLevelText(mDailyChartIndex);
         }
 
         return mHourlyViewModels.get(mDailyChartIndex).getSlotBatteryLevelText(mHourlyChartIndex);
     }
 
-    private String getAccessibilityAnnounceMessage() {
+    private String getAccessibilityAnnounceMessage(final boolean isSlotSelected) {
+        final String selectedInformation =
+                mPrefContext.getString(
+                        isSlotSelected
+                                ? R.string.battery_chart_slot_status_selected
+                                : R.string.battery_chart_slot_status_unselected);
         final String slotInformation = getSlotInformation(/* isAccessibilityText= */ true);
         final String slotInformationMessage =
                 slotInformation == null
@@ -460,7 +472,8 @@
         final String batteryLevelPercentageMessage = getBatteryLevelPercentageInfo();
 
         return mPrefContext.getString(
-                R.string.battery_usage_time_info_and_battery_level,
+                R.string.battery_usage_status_time_info_and_battery_level,
+                selectedInformation,
                 slotInformationMessage,
                 batteryLevelPercentageMessage);
     }
@@ -533,9 +546,8 @@
     }
 
     private boolean isAllSelected() {
-        return (isBatteryLevelDataInOneDay()
-                        || mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL)
-                && mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL;
+        return (isBatteryLevelDataInOneDay() || mDailyChartIndex == SELECTED_INDEX_ALL)
+                && mHourlyChartIndex == SELECTED_INDEX_ALL;
     }
 
     @VisibleForTesting
@@ -571,9 +583,7 @@
             return null;
         }
         BatteryDiffData allBatteryDiffData =
-                batteryUsageData
-                        .get(BatteryChartViewModel.SELECTED_INDEX_ALL)
-                        .get(BatteryChartViewModel.SELECTED_INDEX_ALL);
+                batteryUsageData.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL);
         return allBatteryDiffData == null ? null : allBatteryDiffData.getAppDiffEntryList();
     }
 
@@ -613,12 +623,9 @@
 
         @Override
         public String generateSlotBatteryLevelText(List<Integer> levels, int index) {
-            final int fromBatteryLevelIndex =
-                    index == BatteryChartViewModel.SELECTED_INDEX_ALL ? 0 : index;
+            final int fromBatteryLevelIndex = index == SELECTED_INDEX_ALL ? 0 : index;
             final int toBatteryLevelIndex =
-                    index == BatteryChartViewModel.SELECTED_INDEX_ALL
-                            ? levels.size() - 1
-                            : index + 1;
+                    index == SELECTED_INDEX_ALL ? levels.size() - 1 : index + 1;
             return mPrefContext.getString(
                     R.string.battery_level_percentage,
                     generateBatteryLevelText(levels.get(fromBatteryLevelIndex)),
@@ -687,9 +694,9 @@
             return index == timestamps.size() - 1
                     ? generateText(timestamps, index)
                     : mContext.getString(
-                    R.string.battery_usage_timestamps_content_description,
-                    generateText(timestamps, index),
-                    generateText(timestamps, index + 1));
+                            R.string.battery_usage_timestamps_content_description,
+                            generateText(timestamps, index),
+                            generateText(timestamps, index + 1));
         }
 
         HourlyChartLabelTextGenerator updateSpecialCaseContext(
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
index eafccdb..393d751 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
@@ -17,6 +17,8 @@
 
 import static com.android.settings.Utils.formatPercentage;
 import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS;
+import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_ALL;
+import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_INVALID;
 import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
 
 import static java.lang.Math.abs;
@@ -81,7 +83,7 @@
             getContext().getResources().getConfiguration().getLayoutDirection();
 
     private BatteryChartViewModel mViewModel;
-    private int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
+    private int mHoveredIndex = SELECTED_INDEX_INVALID;
     private int mDividerWidth;
     private int mDividerHeight;
     private float mTrapezoidVOffset;
@@ -245,9 +247,9 @@
                 // sent here.
                 return true;
             case MotionEvent.ACTION_HOVER_EXIT:
-                if (mHoveredIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID) {
+                if (mHoveredIndex != SELECTED_INDEX_INVALID) {
                     sendAccessibilityEventForHover(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
-                    mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
+                    mHoveredIndex = SELECTED_INDEX_INVALID; // reset
                     invalidate();
                 }
                 // Ignore the super.onHoverEvent() because the hovered trapezoid has already been
@@ -262,7 +264,7 @@
     public void onHoverChanged(boolean hovered) {
         super.onHoverChanged(hovered);
         if (!hovered) {
-            mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
+            mHoveredIndex = SELECTED_INDEX_INVALID; // reset
             invalidate();
         }
     }
@@ -295,9 +297,7 @@
         if (mOnSelectListener != null) {
             // Selects all if users click the same trapezoid item two times.
             mOnSelectListener.onSelect(
-                    index == mViewModel.selectedIndex()
-                            ? BatteryChartViewModel.SELECTED_INDEX_ALL
-                            : index);
+                    index == mViewModel.selectedIndex() ? SELECTED_INDEX_ALL : index);
         }
         view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
     }
@@ -332,8 +332,8 @@
         setBackgroundColor(Color.TRANSPARENT);
         mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
         mTrapezoidColor = Utils.getDisabled(context, mTrapezoidSolidColor);
-        mTrapezoidHoverColor = context.getColor(
-                com.android.internal.R.color.materialColorSecondaryContainer);
+        mTrapezoidHoverColor =
+                context.getColor(com.android.internal.R.color.materialColorSecondaryContainer);
         // Initializes the divider line paint.
         final Resources resources = getContext().getResources();
         mDividerWidth = resources.getDimensionPixelSize(R.dimen.chartview_divider_width);
@@ -623,8 +623,7 @@
             // Configures the trapezoid paint color.
             final int trapezoidColor =
                     (mViewModel.selectedIndex() == index
-                                    || mViewModel.selectedIndex()
-                                            == BatteryChartViewModel.SELECTED_INDEX_ALL)
+                                    || mViewModel.selectedIndex() == SELECTED_INDEX_ALL)
                             ? mTrapezoidSolidColor
                             : mTrapezoidColor;
             final boolean isHoverState =
@@ -659,9 +658,7 @@
     }
 
     private boolean isHighlightSlotValid() {
-        return mViewModel != null
-                && mViewModel.getHighlightSlotIndex()
-                        != BatteryChartViewModel.SELECTED_INDEX_INVALID;
+        return mViewModel != null && mViewModel.getHighlightSlotIndex() != SELECTED_INDEX_INVALID;
     }
 
     private void drawTransomLine(Canvas canvas) {
@@ -715,7 +712,7 @@
     // Searches the corresponding trapezoid index from x location.
     private int getTrapezoidIndex(float x) {
         if (mTrapezoidSlots == null) {
-            return BatteryChartViewModel.SELECTED_INDEX_INVALID;
+            return SELECTED_INDEX_INVALID;
         }
         for (int index = 0; index < mTrapezoidSlots.length; index++) {
             final TrapezoidSlot slot = mTrapezoidSlots[index];
@@ -723,7 +720,7 @@
                 return index;
             }
         }
-        return BatteryChartViewModel.SELECTED_INDEX_INVALID;
+        return SELECTED_INDEX_INVALID;
     }
 
     private void initializeAxisLabelsBounds() {
@@ -796,7 +793,11 @@
             childInfo.setText(slotTimeInfo);
             childInfo.setContentDescription(
                     mContext.getString(
-                            R.string.battery_usage_time_info_and_battery_level,
+                            R.string.battery_usage_status_time_info_and_battery_level,
+                            mContext.getString(
+                                    mViewModel.selectedIndex() == virtualViewId
+                                            ? R.string.battery_chart_slot_status_selected
+                                            : R.string.battery_chart_slot_status_unselected),
                             slotTimeInfo,
                             batteryLevelInfo));
             childInfo.setAccessibilityFocused(virtualViewId == mAccessibilityFocusNodeViewId);
diff --git a/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceController.java b/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceController.java
new file mode 100644
index 0000000..bb91b3c
--- /dev/null
+++ b/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2025 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.app.settings.SettingsEnums;
+import android.content.Context;
+import android.hardware.input.InputSettings;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.SliderPreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+public class MousePointerSpeedPreferenceController extends SliderPreferenceController {
+
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+
+    public MousePointerSpeedPreferenceController(@NonNull Context context, @NonNull String key) {
+        super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        SeekBarPreference preference = screen.findPreference(getPreferenceKey());
+        preference.setMax(getMax());
+        preference.setMin(getMin());
+        preference.setProgress(getSliderPosition());
+        updateState(preference);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public boolean setSliderPosition(int position) {
+        if (position < getMin() || position > getMax()) {
+            return false;
+        }
+        InputSettings.setPointerSpeed(mContext, position);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_POINTER_SPEED_CHANGED, position);
+        return true;
+    }
+
+    @Override
+    public int getSliderPosition() {
+        return InputSettings.getPointerSpeed(mContext);
+    }
+
+    @Override
+    public int getMin() {
+        return InputSettings.MIN_POINTER_SPEED;
+    }
+
+    @Override
+    public int getMax() {
+        return InputSettings.MAX_POINTER_SPEED;
+    }
+}
diff --git a/src/com/android/settings/localepicker/LocaleDialogFragment.java b/src/com/android/settings/localepicker/LocaleDialogFragment.java
index a3a4b8f..5c7958a 100644
--- a/src/com/android/settings/localepicker/LocaleDialogFragment.java
+++ b/src/com/android/settings/localepicker/LocaleDialogFragment.java
@@ -26,11 +26,6 @@
 import android.content.Intent;
 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 android.widget.TextView;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 
@@ -57,6 +52,7 @@
     static final String ARG_DIALOG_TYPE = "arg_dialog_type";
     static final String ARG_TARGET_LOCALE = "arg_target_locale";
     static final String ARG_SHOW_DIALOG = "arg_show_dialog";
+    static final String ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED = "arg_show_dialog_for_not_translated";
 
     private boolean mShouldKeepDialog;
     private OnBackInvokedDispatcher mBackDispatcher;
@@ -185,6 +181,7 @@
         private final int mDialogType;
         private final LocaleStore.LocaleInfo mLocaleInfo;
         private final MetricsFeatureProvider mMetricsFeatureProvider;
+        private final boolean mShowDialogForNotTranslated;
 
         private LocaleListEditor mParent;
 
@@ -194,6 +191,7 @@
             mContext = context;
             Bundle arguments = dialogFragment.getArguments();
             mDialogType = arguments.getInt(ARG_DIALOG_TYPE);
+            mShowDialogForNotTranslated = arguments.getBoolean(ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED);
             mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable(ARG_TARGET_LOCALE);
             mMetricsFeatureProvider =
                     FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
@@ -215,6 +213,7 @@
                 bundle.putInt(ARG_DIALOG_TYPE, mDialogType);
                 bundle.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, mLocaleInfo);
                 intent.putExtras(bundle);
+                intent.putExtra(ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED, mShowDialogForNotTranslated);
                 mParent.onActivityResult(mDialogType, result, intent);
                 mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE,
                         changed);
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index 907fe7b..af8b668 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -364,12 +364,25 @@
     }
 
     public void notifyListChanged(LocaleStore.LocaleInfo localeInfo) {
-        if (!localeInfo.getLocale().equals(mCacheItemList.get(0).getLocale())) {
+        if (listChanged()) {
             mFeedItemList = new ArrayList<>(mCacheItemList);
             notifyDataSetChanged();
         }
     }
 
+    private boolean listChanged() {
+        if (mFeedItemList.size() == mCacheItemList.size()) {
+            for (int i = 0; i < mFeedItemList.size(); i++) {
+                if (!mFeedItemList.get(i).getLocale().equals(mCacheItemList.get(i).getLocale())) {
+                    return true;
+                }
+            }
+            return false;
+        } else {
+            return true;
+        }
+    }
+
     public void setCacheItemList() {
         mCacheItemList = new ArrayList<>(mFeedItemList);
     }
diff --git a/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
index a7ebe32..df0af63 100644
--- a/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
+++ b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
@@ -151,7 +151,7 @@
         }
 
         if (result) {
-            mLocaleListEditor.showConfirmDialog(false, mAdapter.getFeedItemList().get(0));
+            mLocaleListEditor.showConfirmDialog(mAdapter.getFeedItemList().get(0), null);
         }
         return result;
     }
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index b1f005a..e2da851 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -44,6 +44,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.fragment.app.FragmentManager;
 import androidx.preference.Preference;
@@ -235,7 +236,9 @@
             localeInfo = mAdapter.getFeedItemList().get(0);
             if (resultCode == Activity.RESULT_OK) {
                 mAdapter.doTheUpdate();
-                if (!localeInfo.isTranslated()) {
+                boolean showNotTranslatedDialog = data.getBooleanExtra(
+                        LocaleDialogFragment.ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED, true);
+                if (showNotTranslatedDialog && !localeInfo.isTranslated()) {
                     Bundle args = new Bundle();
                     args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE,
                             LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE);
@@ -428,13 +431,10 @@
                             // to remove.
                             mRemoveMode = false;
                             mShowingRemoveDialog = false;
-                            LocaleStore.LocaleInfo firstLocale =
-                                    mAdapter.getFeedItemList().get(0);
+                            Locale defaultBeforeRemoval = Locale.getDefault();
                             mAdapter.removeChecked();
-                            boolean isFirstRemoved =
-                                    firstLocale != mAdapter.getFeedItemList().get(0);
-                            showConfirmDialog(isFirstRemoved, isFirstRemoved ? firstLocale
-                                    : mAdapter.getFeedItemList().get(0));
+                            showConfirmDialog(mAdapter.getFeedItemList().get(0),
+                                    defaultBeforeRemoval);
                             setRemoveMode(false);
                             dialogHelper.getDialog().dismiss();
                         })
@@ -520,27 +520,73 @@
     public boolean onTouch(View v, MotionEvent event) {
         if (event.getAction() == MotionEvent.ACTION_UP
                 || event.getAction() == MotionEvent.ACTION_CANCEL) {
-            showConfirmDialog(false, mAdapter.getFeedItemList().get(0));
+            showConfirmDialog(mAdapter.getFeedItemList().get(0), null);
         }
         return false;
     }
 
-    public void showConfirmDialog(boolean isFirstRemoved, LocaleStore.LocaleInfo localeInfo) {
+    protected void showConfirmDialog(LocaleStore.LocaleInfo localeInfo,
+            @Nullable Locale defaultLocaleBeforeRemoval) {
         Locale currentSystemLocale = LocalePicker.getLocales().get(0);
         if (!localeInfo.getLocale().equals(currentSystemLocale)) {
-            final LocaleDialogFragment localeDialogFragment =
-                    LocaleDialogFragment.newInstance();
-            Bundle args = new Bundle();
-            args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
-            args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE,
-                    isFirstRemoved ? LocaleStore.getLocaleInfo(currentSystemLocale) : localeInfo);
-            localeDialogFragment.setArguments(args);
-            localeDialogFragment.show(mFragmentManager, TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT);
+            displayDialogFragment(localeInfo, true);
         } else {
-            mAdapter.doTheUpdate();
+            if (!localeInfo.isTranslated()) {
+                if (defaultLocaleBeforeRemoval == null) {
+                    showDialogDueToDragAndDrop();
+                } else {
+                    showDialogDueToRemoval(defaultLocaleBeforeRemoval);
+                }
+            } else {
+                mAdapter.doTheUpdate();
+            }
         }
     }
 
+    private void showDialogDueToDragAndDrop() {
+        LocaleStore.LocaleInfo newLocale = mAdapter.getFeedItemList().stream().filter(
+                i -> i.isTranslated()).findFirst().orElse(null);
+        if (newLocale == null) {
+            return;
+        }
+        LocaleStore.LocaleInfo oldLocale = null;
+        final LocaleList localeList = LocalePicker.getLocales();
+        for (int i = 0; i < localeList.size(); i++) {
+            LocaleStore.LocaleInfo temp = LocaleStore.getLocaleInfo(localeList.get(i));
+            if (temp.isTranslated()) {
+                oldLocale = temp;
+                break;
+            }
+        }
+        if (oldLocale != null && !newLocale.getLocale().equals(
+                oldLocale.getLocale())) {
+            displayDialogFragment(newLocale, false);
+        }
+    }
+
+    private void showDialogDueToRemoval(Locale preDefault) {
+        if (preDefault == null) {
+            return;
+        }
+        LocaleStore.LocaleInfo currentDefault = mAdapter.getFeedItemList().stream().filter(
+                i -> i.isTranslated()).findFirst().orElse(null);
+        if (currentDefault != null && !preDefault.equals(currentDefault.getLocale())) {
+            displayDialogFragment(currentDefault, false);
+        }
+    }
+
+    private void displayDialogFragment(LocaleStore.LocaleInfo localeInfo,
+            boolean showDialogForNotTranslated) {
+        final LocaleDialogFragment localeDialogFragment = LocaleDialogFragment.newInstance();
+        Bundle args = new Bundle();
+        args.putBoolean(LocaleDialogFragment.ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED,
+                showDialogForNotTranslated);
+        args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
+        args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, localeInfo);
+        localeDialogFragment.setArguments(args);
+        localeDialogFragment.show(mFragmentManager, TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT);
+    }
+
     // Hide the "Remove" menu if there is only one locale in the list, show it otherwise
     // This is called when the menu is first created, and then one add / remove locale
     private void updateVisibilityOfRemoveMenu() {
diff --git a/src/com/android/settings/network/NetworkResetPreferenceController.java b/src/com/android/settings/network/NetworkResetPreferenceController.java
index af288fa..ef3dca2 100644
--- a/src/com/android/settings/network/NetworkResetPreferenceController.java
+++ b/src/com/android/settings/network/NetworkResetPreferenceController.java
@@ -20,6 +20,7 @@
 
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settings.network.SubscriptionUtil;
+import com.android.settingslib.Utils;
 import com.android.settingslib.core.AbstractPreferenceController;
 
 public class NetworkResetPreferenceController extends AbstractPreferenceController
@@ -34,8 +35,9 @@
 
     @Override
     public boolean isAvailable() {
-        return (SubscriptionUtil.isSimHardwareVisible(mContext) &&
-                (!mRestrictionChecker.hasUserRestriction()));
+        return (SubscriptionUtil.isSimHardwareVisible(mContext)
+                && !Utils.isWifiOnly(mContext)
+                && !mRestrictionChecker.hasUserRestriction());
     }
 
     @Override
diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt
index 46aa19b..7e04f0d 100644
--- a/src/com/android/settings/overlay/FeatureFactory.kt
+++ b/src/com/android/settings/overlay/FeatureFactory.kt
@@ -17,7 +17,7 @@
 
 import android.content.Context
 import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider
 import com.android.settings.accessibility.AccessibilitySearchFeatureProvider
 import com.android.settings.accounts.AccountFeatureProvider
 import com.android.settings.applications.ApplicationFeatureProvider
@@ -145,9 +145,9 @@
     abstract val accessibilitySearchFeatureProvider: AccessibilitySearchFeatureProvider
 
     /**
-     * Retrieves implementation for Accessibility metrics category feature.
+     * Retrieves implementation for Accessibility page id category feature.
      */
-    abstract val accessibilityMetricsFeatureProvider: AccessibilityMetricsFeatureProvider
+    abstract val accessibilityPageIdFeatureProvider: AccessibilityPageIdFeatureProvider
 
     /**
      * Retrieves implementation for advanced vpn feature.
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
index 08abf2b..4949c3f 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
@@ -22,8 +22,8 @@
 import android.os.UserManager
 import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider
 import com.android.settings.accessibility.AccessibilityFeedbackFeatureProviderImpl
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProviderImpl
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProviderImpl
 import com.android.settings.accessibility.AccessibilitySearchFeatureProvider
 import com.android.settings.accessibility.AccessibilitySearchFeatureProviderImpl
 import com.android.settings.accounts.AccountFeatureProvider
@@ -174,8 +174,8 @@
         AccessibilitySearchFeatureProviderImpl()
     }
 
-    override val accessibilityMetricsFeatureProvider: AccessibilityMetricsFeatureProvider by lazy {
-        AccessibilityMetricsFeatureProviderImpl()
+    override val accessibilityPageIdFeatureProvider: AccessibilityPageIdFeatureProvider by lazy {
+        AccessibilityPageIdFeatureProviderImpl()
     }
 
     override val advancedVpnFeatureProvider by lazy { AdvancedVpnFeatureProviderImpl() }
diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
index 00a4c67..61f05f7 100644
--- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java
+++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
@@ -131,6 +131,7 @@
         if (Flags.biometricsOnboardingEducation()) {
             FaceSafetySource.onBiometricsChanged(context);
             FingerprintSafetySource.onBiometricsChanged(context);
+            WearSafetySource.onBiometricsChanged(context);
         } else {
             BiometricsSafetySource.onBiometricsChanged(context);
         }
diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
index a49b7e0..4cf40dd 100644
--- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
+++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
@@ -86,6 +86,9 @@
         if (sourceIds.contains(FingerprintSafetySource.SAFETY_SOURCE_ID)) {
             FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
         }
+        if (sourceIds.contains(WearSafetySource.SAFETY_SOURCE_ID)) {
+            WearSafetySource.setSafetySourceData(context, safetyEvent);
+        }
     }
 
     private static void refreshAllSafetySources(Context context, SafetyEvent safetyEvent) {
@@ -95,5 +98,6 @@
         PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent);
         FaceSafetySource.setSafetySourceData(context, safetyEvent);
         FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
+        WearSafetySource.setSafetySourceData(context, safetyEvent);
     }
 }
diff --git a/src/com/android/settings/safetycenter/WearSafetySource.java b/src/com/android/settings/safetycenter/WearSafetySource.java
new file mode 100644
index 0000000..a345096
--- /dev/null
+++ b/src/com/android/settings/safetycenter/WearSafetySource.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2025 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.safetycenter;
+
+import static com.android.settings.biometrics.combination.BiometricsSettingsBase.ACTIVE_UNLOCK_REQUEST;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.UserManager;
+import android.safetycenter.SafetyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
+import com.android.settings.flags.Flags;
+
+/** Wear Safety Source for Safety Center. */
+public final class WearSafetySource {
+
+    private static final String TAG = "WearSafetySource";
+    public static final String SAFETY_SOURCE_ID = "AndroidWearUnlock";
+    private static boolean sIsTestingEnv = false;
+    private static String sSummaryForTesting = "";
+    private static boolean sHasEnrolledForTesting;
+
+    private WearSafetySource() {}
+
+    /** Sets test value for summary. */
+    @VisibleForTesting
+    public static void setSummaryForTesting(@NonNull String summary) {
+        sIsTestingEnv = true;
+        sSummaryForTesting = summary;
+    }
+
+    /** Sets test value for hasEnrolled. */
+    @VisibleForTesting
+    public static void setHasEnrolledForTesting(boolean hasEnrolled) {
+        sIsTestingEnv = true;
+        sHasEnrolledForTesting = hasEnrolled;
+    }
+
+    /** Sets biometric safety data for Safety Center. */
+    public static void setSafetySourceData(
+            @NonNull Context context, @NonNull SafetyEvent safetyEvent) {
+        if (!SafetyCenterManagerWrapper.get().isEnabled(context)) {
+            return;
+        }
+        if (!Flags.biometricsOnboardingEducation()) { // this source is effectively turned off
+            sendNullData(context, safetyEvent);
+            return;
+        }
+
+        // Handle private profile case.
+        UserManager userManager = UserManager.get(context);
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                && userManager.isPrivateProfile()) {
+            // SC always expects a response from the source if the broadcast has been sent for this
+            // source, therefore, we need to send a null SafetySourceData.
+            sendNullData(context, safetyEvent);
+            return;
+        }
+
+        ActiveUnlockStatusUtils activeUnlockStatusUtils = new ActiveUnlockStatusUtils(context);
+        if (!userManager.isProfile() && activeUnlockStatusUtils.isAvailable()) {
+            boolean hasEnrolled = false;
+            String summary = "";
+
+            if (sIsTestingEnv) {
+                hasEnrolled = sHasEnrolledForTesting;
+                summary = sSummaryForTesting;
+            } else {
+                String authority = new ActiveUnlockStatusUtils(context).getAuthority();
+                hasEnrolled = getHasEnrolledFromContentProvider(context, authority);
+                summary = getSummaryFromContentProvider(context, authority);
+            }
+
+            BiometricSourcesUtils.setBiometricSafetySourceData(
+                    SAFETY_SOURCE_ID,
+                    context,
+                    activeUnlockStatusUtils.getTitleForActiveUnlockOnly(),
+                    summary,
+                    PendingIntent.getActivity(context, ACTIVE_UNLOCK_REQUEST,
+                            activeUnlockStatusUtils.getIntent(),
+                            PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT),
+                    /* enabled= */ true,
+                    hasEnrolled,
+                    safetyEvent);
+            return;
+        }
+
+        sendNullData(context, safetyEvent);
+    }
+
+    private static void sendNullData(Context context, SafetyEvent safetyEvent) {
+        SafetyCenterManagerWrapper.get()
+                .setSafetySourceData(
+                        context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent);
+    }
+
+    /** Notifies Safety Center of a change in wear biometrics settings. */
+    public static void onBiometricsChanged(@NonNull Context context) {
+        setSafetySourceData(
+                context,
+                new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED)
+                        .build());
+    }
+
+    private static boolean getHasEnrolledFromContentProvider(
+            @NonNull Context context, @Nullable String authority) {
+        if (authority == null) {
+            return false;
+        }
+        return ActiveUnlockStatusUtils.getDeviceNameFromContentProvider(context, authority, TAG)
+            != null;
+    }
+
+    private static String getSummaryFromContentProvider(
+            @NonNull Context context, @Nullable String authority) {
+        if (authority == null) {
+            return "";
+        }
+        String summary = ActiveUnlockStatusUtils.getSummaryFromContentProvider(
+                context, authority, TAG);
+        if (summary == null) {
+            return "";
+        }
+        return summary;
+    }
+
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
index 7e16096..9bb3051 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
@@ -91,10 +91,15 @@
         }
     }.sharedFlow()
 
-    val isAvailableFlow = installerLabelFlow.map { installerLabel ->
-        withContext(Dispatchers.IO) {
-            !AppUtils.isMainlineModule(packageManager, app.packageName) &&
-                    installerLabel != null
+    val isAvailableFlow = installerLabelFlow.map() { installerLabel ->
+        // Do not show the install info for the special case of the Play Store app.
+        if (app.packageName == context.getString(R.string.config_mainline_module_update_package)) {
+            false
+        } else {
+            withContext(Dispatchers.IO) {
+                val isMainlineModule = AppUtils.isMainlineModule(packageManager, app.packageName)
+                !isMainlineModule && installerLabel != null
+            }
         }
     }
 
diff --git a/src/com/android/settings/vpn2/ConfigDialog.java b/src/com/android/settings/vpn2/ConfigDialog.java
index 1c001cb..8dbcf94 100644
--- a/src/com/android/settings/vpn2/ConfigDialog.java
+++ b/src/com/android/settings/vpn2/ConfigDialog.java
@@ -40,6 +40,7 @@
 import com.android.net.module.util.ProxyUtils;
 import com.android.settings.R;
 import com.android.settings.utils.AndroidKeystoreAliasLoader;
+import com.android.settings.wifi.utils.TextInputGroup;
 
 import java.util.Collection;
 import java.util.List;
@@ -70,16 +71,17 @@
 
     private View mView;
 
-    private TextView mName;
+    private TextInputGroup mNameInput;
     private Spinner mType;
-    private TextView mServer;
-    private TextView mUsername;
+    private TextInputGroup mServerInput;
+    private TextInputGroup mUsernameInput;
+    private TextInputGroup mPasswordInput;
     private TextView mPassword;
     private Spinner mProxySettings;
     private TextView mProxyHost;
     private TextView mProxyPort;
-    private TextView mIpsecIdentifier;
-    private TextView mIpsecSecret;
+    private TextInputGroup mIpsecIdentifierInput;
+    private TextInputGroup mIpsecSecretInput;
     private Spinner mIpsecUserCert;
     private Spinner mIpsecCaCert;
     private Spinner mIpsecServerCert;
@@ -106,16 +108,22 @@
         Context context = getContext();
 
         // First, find out all the fields.
-        mName = (TextView) mView.findViewById(R.id.name);
+        mNameInput = new TextInputGroup(mView, R.id.name_layout, R.id.name,
+                R.string.vpn_field_required);
         mType = (Spinner) mView.findViewById(R.id.type);
-        mServer = (TextView) mView.findViewById(R.id.server);
-        mUsername = (TextView) mView.findViewById(R.id.username);
-        mPassword = (TextView) mView.findViewById(R.id.password);
+        mServerInput = new TextInputGroup(mView, R.id.server_layout, R.id.server,
+                R.string.vpn_field_required);
+        mUsernameInput = new TextInputGroup(mView, R.id.username_layout, R.id.username,
+                R.string.vpn_field_required);
+        mPasswordInput = new TextInputGroup(mView, R.id.password_layout, R.id.password,
+                R.string.vpn_field_required);
         mProxySettings = (Spinner) mView.findViewById(R.id.vpn_proxy_settings);
         mProxyHost = (TextView) mView.findViewById(R.id.vpn_proxy_host);
         mProxyPort = (TextView) mView.findViewById(R.id.vpn_proxy_port);
-        mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
-        mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
+        mIpsecIdentifierInput = new TextInputGroup(mView, R.id.ipsec_identifier_layout,
+                R.id.ipsec_identifier, R.string.vpn_field_required);
+        mIpsecSecretInput = new TextInputGroup(mView, R.id.ipsec_secret_layout, R.id.ipsec_secret,
+                R.string.vpn_field_required);
         mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
         mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
         mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
@@ -125,21 +133,21 @@
         mAlwaysOnInvalidReason = (TextView) mView.findViewById(R.id.always_on_invalid_reason);
 
         // Second, copy values from the profile.
-        mName.setText(mProfile.name);
+        mNameInput.setText(mProfile.name);
         setTypesByFeature(mType);
         mType.setSelection(convertVpnProfileConstantToTypeIndex(mProfile.type));
-        mServer.setText(mProfile.server);
+        mServerInput.setText(mProfile.server);
         if (mProfile.saveLogin) {
-            mUsername.setText(mProfile.username);
-            mPassword.setText(mProfile.password);
+            mUsernameInput.setText(mProfile.username);
+            mPasswordInput.setText(mProfile.password);
         }
         if (mProfile.proxy != null) {
             mProxyHost.setText(mProfile.proxy.getHost());
             int port = mProfile.proxy.getPort();
             mProxyPort.setText(port == 0 ? "" : Integer.toString(port));
         }
-        mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
-        mIpsecSecret.setText(mProfile.ipsecSecret);
+        mIpsecIdentifierInput.setText(mProfile.ipsecIdentifier);
+        mIpsecSecretInput.setText(mProfile.ipsecSecret);
         final AndroidKeystoreAliasLoader androidKeystoreAliasLoader =
                 new AndroidKeystoreAliasLoader(null);
         loadCertificates(mIpsecUserCert, androidKeystoreAliasLoader.getKeyCertAliases(), 0,
@@ -150,7 +158,8 @@
                 R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
         mSaveLogin.setChecked(mProfile.saveLogin);
         mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
-        mPassword.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
+        mPasswordInput.getEditText()
+                .setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
 
         // Hide lockdown VPN on devices that require IMS authentication
         if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
@@ -158,16 +167,16 @@
         }
 
         // Third, add listeners to required fields.
-        mName.addTextChangedListener(this);
+        mNameInput.addTextChangedListener(this);
         mType.setOnItemSelectedListener(this);
-        mServer.addTextChangedListener(this);
-        mUsername.addTextChangedListener(this);
-        mPassword.addTextChangedListener(this);
+        mServerInput.addTextChangedListener(this);
+        mUsernameInput.addTextChangedListener(this);
+        mPasswordInput.addTextChangedListener(this);
         mProxySettings.setOnItemSelectedListener(this);
         mProxyHost.addTextChangedListener(this);
         mProxyPort.addTextChangedListener(this);
-        mIpsecIdentifier.addTextChangedListener(this);
-        mIpsecSecret.addTextChangedListener(this);
+        mIpsecIdentifierInput.addTextChangedListener(this);
+        mIpsecSecretInput.addTextChangedListener(this);
         mIpsecUserCert.setOnItemSelectedListener(this);
         mShowOptions.setOnClickListener(this);
         mAlwaysOnVpn.setOnCheckedChangeListener(this);
@@ -202,6 +211,8 @@
             setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
 
             setUsernamePasswordVisibility(mProfile.type);
+            mUsernameInput.setHelperText(context.getString(R.string.vpn_required));
+            mPasswordInput.setHelperText(context.getString(R.string.vpn_required));
 
             // Create a button to connect the network.
             setButton(DialogInterface.BUTTON_POSITIVE,
@@ -260,6 +271,10 @@
             updateProxyFieldsVisibility(position);
         }
         updateUiControls();
+        mNameInput.setError("");
+        mServerInput.setError("");
+        mIpsecIdentifierInput.setError("");
+        mIpsecSecretInput.setError("");
     }
 
     @Override
@@ -375,30 +390,16 @@
             return false;
         }
 
-        final int position = mType.getSelectedItemPosition();
-        final int type = VPN_TYPES.get(position);
-        if (!editing && requiresUsernamePassword(type)) {
-            return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
-        }
-        if (mName.getText().length() == 0 || mServer.getText().length() == 0) {
-            return false;
-        }
-
-        // All IKEv2 methods require an identifier
-        if (mIpsecIdentifier.getText().length() == 0) {
-            return false;
-        }
-
         if (!validateProxy()) {
             return false;
         }
 
-        switch (type) {
+        switch (getVpnType()) {
             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
                 return true;
 
             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
-                return mIpsecSecret.getText().length() != 0;
+                return true;
 
             case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
                 return mIpsecUserCert.getSelectedItemPosition() != 0;
@@ -406,6 +407,29 @@
         return false;
     }
 
+    public boolean validate() {
+        boolean isValidate = true;
+        int type = getVpnType();
+        if (!mEditing && requiresUsernamePassword(type)) {
+            if (!mUsernameInput.validate()) isValidate = false;
+            if (!mPasswordInput.validate()) isValidate = false;
+            return isValidate;
+        }
+
+        if (!mNameInput.validate()) isValidate = false;
+        if (!mServerInput.validate()) isValidate = false;
+        if (!mIpsecIdentifierInput.validate()) isValidate = false;
+        if (type == VpnProfile.TYPE_IKEV2_IPSEC_PSK && !mIpsecSecretInput.validate()) {
+            isValidate = false;
+        }
+        if (!isValidate) Log.w(TAG, "Failed to validate VPN profile!");
+        return isValidate;
+    }
+
+    private int getVpnType() {
+        return VPN_TYPES.get(mType.getSelectedItemPosition());
+    }
+
     private void setTypesByFeature(Spinner typeSpinner) {
         String[] types = getContext().getResources().getStringArray(R.array.vpn_types);
         if (types.length != VPN_TYPES.size()) {
@@ -487,15 +511,14 @@
     VpnProfile getProfile() {
         // First, save common fields.
         VpnProfile profile = new VpnProfile(mProfile.key);
-        profile.name = mName.getText().toString();
-        final int position = mType.getSelectedItemPosition();
-        profile.type = VPN_TYPES.get(position);
-        profile.server = mServer.getText().toString().trim();
-        profile.username = mUsername.getText().toString();
-        profile.password = mPassword.getText().toString();
+        profile.name = mNameInput.getText();
+        profile.type = getVpnType();
+        profile.server = mServerInput.getText().trim();
+        profile.username = mUsernameInput.getText();
+        profile.password = mPasswordInput.getText();
 
         // Save fields based on VPN type.
-        profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
+        profile.ipsecIdentifier = mIpsecIdentifierInput.getText();
 
         if (hasProxy()) {
             String proxyHost = mProxyHost.getText().toString().trim();
@@ -517,7 +540,7 @@
         // Then, save type-specific fields.
         switch (profile.type) {
             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
-                profile.ipsecSecret = mIpsecSecret.getText().toString();
+                profile.ipsecSecret = mIpsecSecretInput.getText();
                 break;
 
             case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
diff --git a/src/com/android/settings/vpn2/ConfigDialogFragment.java b/src/com/android/settings/vpn2/ConfigDialogFragment.java
index 559003a..6bffef7 100644
--- a/src/com/android/settings/vpn2/ConfigDialogFragment.java
+++ b/src/com/android/settings/vpn2/ConfigDialogFragment.java
@@ -124,6 +124,7 @@
         VpnProfile profile = dialog.getProfile();
 
         if (button == DialogInterface.BUTTON_POSITIVE) {
+            if (!dialog.validate()) return;
             // Possibly throw up a dialog to explain lockdown VPN.
             final boolean shouldLockdown = dialog.isVpnAlwaysOn();
             final boolean shouldConnect = shouldLockdown || !dialog.isEditing();
diff --git a/src/com/android/settings/wifi/WifiConfigController2.java b/src/com/android/settings/wifi/WifiConfigController2.java
index 1bf1102..a080fc8 100644
--- a/src/com/android/settings/wifi/WifiConfigController2.java
+++ b/src/com/android/settings/wifi/WifiConfigController2.java
@@ -77,7 +77,7 @@
 import com.android.settings.wifi.details2.WifiPrivacyPreferenceController;
 import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2;
 import com.android.settings.wifi.dpp.WifiDppUtils;
-import com.android.settings.wifi.utils.SsidInputGroup;
+import com.android.settings.wifi.utils.TextInputGroup;
 import com.android.settingslib.Utils;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.wifi.flags.Flags;
@@ -229,7 +229,7 @@
     private final boolean mHideMeteredAndPrivacy;
     private final WifiManager mWifiManager;
     private final AndroidKeystoreAliasLoader mAndroidKeystoreAliasLoader;
-    private SsidInputGroup mSsidInputGroup;
+    private TextInputGroup mSsidInputGroup;
 
     private final Context mContext;
 
@@ -299,7 +299,8 @@
             wepWarningLayout.setVisibility(View.VISIBLE);
         }
 
-        mSsidInputGroup = new SsidInputGroup(mContext, mView, R.id.ssid_layout, R.id.ssid);
+        mSsidInputGroup = new TextInputGroup(mView, R.id.ssid_layout, R.id.ssid,
+                R.string.wifi_ssid_hint);
         mSsidScanButton = (ImageButton) mView.findViewById(R.id.ssid_scanner_button);
         mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings);
         mIpSettingsSpinner.setOnItemSelectedListener(this);
diff --git a/src/com/android/settings/wifi/WifiDialog.java b/src/com/android/settings/wifi/WifiDialog.java
index 40d22e6..38c99b6 100644
--- a/src/com/android/settings/wifi/WifiDialog.java
+++ b/src/com/android/settings/wifi/WifiDialog.java
@@ -28,7 +28,7 @@
 import androidx.appcompat.app.AlertDialog;
 
 import com.android.settings.R;
-import com.android.settings.wifi.utils.SsidInputGroup;
+import com.android.settings.wifi.utils.TextInputGroup;
 import com.android.settings.wifi.utils.WifiDialogHelper;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -120,7 +120,8 @@
         }
 
         mDialogHelper = new WifiDialogHelper(this,
-                new SsidInputGroup(getContext(), mView, R.id.ssid_layout, R.id.ssid));
+                new TextInputGroup(mView, R.id.ssid_layout, R.id.ssid,
+                        R.string.vpn_field_required));
     }
 
     @SuppressWarnings("MissingSuperCall") // TODO: Fix me
diff --git a/src/com/android/settings/wifi/utils/SsidInputGroup.kt b/src/com/android/settings/wifi/utils/SsidInputGroup.kt
deleted file mode 100644
index 5d8f8d4..0000000
--- a/src/com/android/settings/wifi/utils/SsidInputGroup.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2025 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.wifi.utils
-
-import android.content.Context
-import android.view.View
-import com.android.settings.R
-
-/** TextInputGroup for Wi-Fi SSID. */
-class SsidInputGroup(private val context: Context, view: View, layoutId: Int, editTextId: Int) :
-    TextInputGroup(view, layoutId, editTextId) {
-
-    fun validate(): Boolean {
-        if (getText().isEmpty()) {
-            setError(context.getString(R.string.wifi_ssid_hint))
-            return false
-        }
-        return true
-    }
-}
diff --git a/src/com/android/settings/wifi/utils/TextInputGroup.kt b/src/com/android/settings/wifi/utils/TextInputGroup.kt
index 8006dad..53c80ff 100644
--- a/src/com/android/settings/wifi/utils/TextInputGroup.kt
+++ b/src/com/android/settings/wifi/utils/TextInputGroup.kt
@@ -18,6 +18,7 @@
 
 import android.text.Editable
 import android.text.TextWatcher
+import android.util.Log
 import android.view.View
 import android.widget.EditText
 import com.google.android.material.textfield.TextInputLayout
@@ -27,13 +28,17 @@
     private val view: View,
     private val layoutId: Int,
     private val editTextId: Int,
+    private val errorMessageId: Int,
 ) {
 
-    private val View.layout: TextInputLayout?
-        get() = findViewById(layoutId)
+    val layout: TextInputLayout
+        get() = view.requireViewById(layoutId)
 
-    private val View.editText: EditText?
-        get() = findViewById(editTextId)
+    val editText: EditText
+        get() = view.requireViewById(editTextId)
+
+    val errorMessage: String
+        get() = view.context.getString(errorMessageId)
 
     private val textWatcher =
         object : TextWatcher {
@@ -42,7 +47,7 @@
             override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
 
             override fun afterTextChanged(s: Editable?) {
-                view.layout?.isErrorEnabled = false
+                layout.isErrorEnabled = false
             }
         }
 
@@ -51,18 +56,37 @@
     }
 
     fun addTextChangedListener(watcher: TextWatcher) {
-        view.editText?.addTextChangedListener(watcher)
+        editText.addTextChangedListener(watcher)
     }
 
-    fun getText(): String {
-        return view.editText?.text?.toString() ?: ""
+    var text: String
+        get() = editText.text?.toString() ?: ""
+        set(value) {
+            editText.setText(value)
+        }
+
+    var helperText: String
+        get() = layout.helperText?.toString() ?: ""
+        set(value) {
+            layout.setHelperText(value)
+        }
+
+    var error: String
+        get() = layout.error?.toString() ?: ""
+        set(value) {
+            layout.setError(value)
+        }
+
+    open fun validate(): Boolean {
+        val isValid = text.isNotEmpty()
+        if (!isValid) {
+            Log.w(TAG, "validate failed in ${layout.hint ?: "unknown"}")
+            error = errorMessage.toString()
+        }
+        return isValid
     }
 
-    fun setText(text: String) {
-        view.editText?.setText(text)
-    }
-
-    fun setError(errorMessage: String?) {
-        view.layout?.apply { error = errorMessage }
+    companion object {
+        const val TAG = "TextInputGroup"
     }
 }
diff --git a/src/com/android/settings/wifi/utils/WifiDialogHelper.kt b/src/com/android/settings/wifi/utils/WifiDialogHelper.kt
index 3b23b1a..aa41b96 100644
--- a/src/com/android/settings/wifi/utils/WifiDialogHelper.kt
+++ b/src/com/android/settings/wifi/utils/WifiDialogHelper.kt
@@ -21,7 +21,7 @@
 
 class WifiDialogHelper(
     alertDialog: AlertDialog,
-    private val ssidInputGroup: SsidInputGroup? = null,
+    private val ssidInputGroup: TextInputGroup? = null,
 ) : AlertDialogHelper(alertDialog) {
 
     override fun canDismiss(): Boolean {
diff --git a/tests/componenttests/src/com/android/settings/biometrics/BiometricEnrollActivityTest.java b/tests/componenttests/src/com/android/settings/biometrics/BiometricEnrollActivityTest.java
index eb28dfb..267b5bc 100644
--- a/tests/componenttests/src/com/android/settings/biometrics/BiometricEnrollActivityTest.java
+++ b/tests/componenttests/src/com/android/settings/biometrics/BiometricEnrollActivityTest.java
@@ -23,6 +23,7 @@
 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
 import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
 
+import static com.android.settings.biometrics.BiometricEnrollActivity.EXTRA_LAUNCH_FACE_ENROLL_FIRST;
 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS;
 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE;
 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT;
@@ -39,6 +40,7 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
 
@@ -145,7 +147,7 @@
         assumeTrue(mHasFace || mHasFingerprint);
 
         setPin();
-        final Intent intent = getIntent(true /* useInternal */);
+        final Intent intent = getIntent(true /* useInternal */, null);
         LockPatternChecker.verifyCredential(new LockPatternUtils(mContext),
                 LockscreenCredential.createPin(TEST_PIN), UserHandle.myUserId(),
                 LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, (response, timeoutMs) -> {
@@ -163,6 +165,26 @@
     }
 
     @Test
+    public void launchWithPinAndPwHandle_confirmsPin_firstEnrollmentIsFace() throws Exception {
+        assumeTrue(mHasFace && mHasFingerprint);
+
+        setPin();
+        final Intent intent = getFaceEnrollFirstIntent();
+        LockPatternChecker.verifyCredential(new LockPatternUtils(mContext),
+                LockscreenCredential.createPin(TEST_PIN), UserHandle.myUserId(),
+                LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, (response, timeoutMs) -> {
+                    assertThat(response.containsGatekeeperPasswordHandle()).isTrue();
+                    intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
+                            response.getGatekeeperPasswordHandle());
+                }).get();
+
+        try (ActivityScenario<BiometricEnrollActivity> scenario =
+                     ActivityScenario.launch(intent)) {
+            intended(hasComponent(FaceEnroll.class.getName()));
+        }
+    }
+
+    @Test
     public void launchWithStrongBiometricAllowed_doNotEnrollWeak() throws Exception {
         assumeTrue(mHasFace || mHasFingerprint);
 
@@ -184,13 +206,22 @@
     }
 
     private Intent getIntent() {
-        return getIntent(false /* useInternal */);
+        return getIntent(false /* useInternal */, null);
     }
 
-    private Intent getIntent(boolean useInternal) {
+    private Intent getFaceEnrollFirstIntent() {
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(EXTRA_LAUNCH_FACE_ENROLL_FIRST, true);
+        return getIntent(true /* useInternal */, bundle);
+    }
+
+    private Intent getIntent(boolean useInternal, Bundle bundle) {
         final Intent intent = new Intent(mContext, useInternal
                 ? BiometricEnrollActivity.InternalActivity.class : BiometricEnrollActivity.class);
         intent.setAction(ACTION_BIOMETRIC_ENROLL);
+        if (bundle != null && !bundle.isEmpty()) {
+            intent.putExtras(bundle);
+        }
         return intent;
     }
 
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
index e590a80..6710da9 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
@@ -22,7 +22,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -458,12 +457,10 @@
         setupFragment();
         mFragment.setFeedbackManager(
                 new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
-        when(mMenu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mMenuItem);
 
         mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
 
-        verify(mMenu).add(anyInt(), eq(AccessibilitySettings.MENU_ID_SEND_FEEDBACK),
-                anyInt(), eq(mContext.getText(R.string.accessibility_send_feedback_title)));
+        verify(mMenu).add(anyInt(), anyInt(), anyInt(), anyInt());
     }
 
     @Test
@@ -472,12 +469,10 @@
         setupFragment();
         mFragment.setFeedbackManager(
                 new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
-        when(mMenu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mMenuItem);
 
         mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
 
-        verify(mMenu, never()).add(anyInt(), eq(AccessibilitySettings.MENU_ID_SEND_FEEDBACK),
-                anyInt(), eq(mContext.getText(R.string.accessibility_send_feedback_title)));
+        verify(mMenu, never()).add(anyInt(), anyInt(), anyInt(), anyInt());
     }
 
     @Test
@@ -486,8 +481,6 @@
         setupFragment();
         mFragment.setFeedbackManager(
                 new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
-        when(mMenu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mMenuItem);
-        mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
         when(mMenuItem.getItemId()).thenReturn(AccessibilitySettings.MENU_ID_SEND_FEEDBACK);
 
         mFragment.onOptionsItemSelected(mMenuItem);
@@ -502,8 +495,6 @@
         setupFragment();
         mFragment.setFeedbackManager(
                 new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
-        when(mMenu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mMenuItem);
-        mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
         when(mMenuItem.getItemId()).thenReturn(AccessibilitySettings.MENU_ID_SEND_FEEDBACK);
 
         mFragment.onOptionsItemSelected(mMenuItem);
diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java
new file mode 100644
index 0000000..42efdfe
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2025 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.provider.Settings;
+import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode;
+import android.text.TextUtils;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.DialogCreatable;
+import com.android.settings.R;
+import com.android.settings.accessibility.MagnificationCursorFollowingModePreferenceController.ModeInfo;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link MagnificationCursorFollowingModePreferenceController}. */
+@RunWith(RobolectricTestRunner.class)
+public class MagnificationCursorFollowingModePreferenceControllerTest {
+    private static final String PREF_KEY =
+            Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
+
+    @Rule
+    public MockitoRule mocks = MockitoJUnit.rule();
+
+    @Spy
+    private TestDialogHelper mDialogHelper = new TestDialogHelper();
+
+    private PreferenceScreen mScreen;
+    private Context mContext;
+    private MagnificationCursorFollowingModePreferenceController mController;
+    private Preference mModePreference;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat);
+        final PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        mScreen = preferenceManager.createPreferenceScreen(mContext);
+        mModePreference = new Preference(mContext);
+        mModePreference.setKey(PREF_KEY);
+        mScreen.addPreference(mModePreference);
+        mController = new MagnificationCursorFollowingModePreferenceController(mContext, PREF_KEY);
+        mController.setDialogHelper(mDialogHelper);
+        mDialogHelper.setDialogDelegate(mController);
+        showPreferenceOnTheScreen();
+    }
+
+    private void showPreferenceOnTheScreen() {
+        mController.displayPreference(mScreen);
+    }
+
+    @AccessibilityMagnificationCursorFollowingMode
+    private int getCheckedModeFromDialog() {
+        final ListView listView = mController.mModeListView;
+        assertThat(listView).isNotNull();
+
+        final int checkedPosition = listView.getCheckedItemPosition();
+        assertWithMessage("No mode is checked").that(checkedPosition)
+                .isNotEqualTo(AdapterView.INVALID_POSITION);
+
+        final ModeInfo modeInfo = (ModeInfo) listView.getAdapter().getItem(checkedPosition);
+        return modeInfo.mMode;
+    }
+
+    private void performItemClickWith(@AccessibilityMagnificationCursorFollowingMode int mode) {
+        final ListView listView = mController.mModeListView;
+        assertThat(listView).isNotNull();
+
+        int modeIndex = AdapterView.NO_ID;
+        for (int i = 0; i < listView.getAdapter().getCount(); i++) {
+            final ModeInfo modeInfo = (ModeInfo) listView.getAdapter().getItem(i);
+            if (modeInfo != null && modeInfo.mMode == mode) {
+                modeIndex = i;
+                break;
+            }
+        }
+        assertWithMessage("The mode could not be found").that(modeIndex)
+                .isNotEqualTo(AdapterView.NO_ID);
+
+        listView.performItemClick(listView.getChildAt(modeIndex), modeIndex, modeIndex);
+    }
+
+    @Test
+    public void clickPreference_defaultMode_selectionIsDefault() {
+        mController.handlePreferenceTreeClick(mModePreference);
+
+        assertThat(getCheckedModeFromDialog()).isEqualTo(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
+    }
+
+    @Test
+    public void clickPreference_nonDefaultMode_selectionIsExpected() {
+        Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER);
+
+        mController.handlePreferenceTreeClick(mModePreference);
+
+        assertThat(getCheckedModeFromDialog()).isEqualTo(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER);
+    }
+
+    @Test
+    public void selectItemInDialog_selectionIsExpected() {
+        mController.handlePreferenceTreeClick(mModePreference);
+
+        performItemClickWith(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+
+        assertThat(getCheckedModeFromDialog()).isEqualTo(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+    }
+
+    @Test
+    public void selectItemInDialog_dismissWithoutSave_selectionNotPersists() {
+        mController.handlePreferenceTreeClick(mModePreference);
+
+        performItemClickWith(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+
+        showPreferenceOnTheScreen();
+
+        mController.handlePreferenceTreeClick(mModePreference);
+
+        assertThat(getCheckedModeFromDialog()).isEqualTo(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
+        assertThat(TextUtils.equals(mController.getSummary(), mContext.getString(
+                R.string.accessibility_magnification_cursor_following_continuous))).isTrue();
+    }
+
+    @Test
+    public void selectItemInDialog_saveAndDismiss_selectionPersists() {
+        mController.handlePreferenceTreeClick(mModePreference);
+
+        performItemClickWith(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+        mController.onMagnificationCursorFollowingModeDialogPositiveButtonClicked(
+                mDialogHelper.getDialog(), DialogInterface.BUTTON_POSITIVE);
+
+        showPreferenceOnTheScreen();
+
+        mController.handlePreferenceTreeClick(mModePreference);
+
+        assertThat(getCheckedModeFromDialog()).isEqualTo(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+        assertThat(TextUtils.equals(mController.getSummary(), mContext.getString(
+                R.string.accessibility_magnification_cursor_following_edge))).isTrue();
+    }
+
+    private static class TestDialogHelper implements DialogHelper {
+        private DialogCreatable mDialogDelegate;
+        private Dialog mDialog;
+
+        @Override
+        public void showDialog(int dialogId) {
+            mDialog = mDialogDelegate.onCreateDialog(dialogId);
+        }
+
+        public void setDialogDelegate(@NonNull DialogCreatable delegate) {
+            mDialogDelegate = delegate;
+        }
+
+        public Dialog getDialog() {
+            return mDialog;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
index 8f9d2e1..f72b591 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
@@ -23,9 +23,12 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -36,11 +39,15 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.icu.text.CaseMap;
+import android.net.Uri;
 import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
@@ -50,12 +57,14 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.R;
 import com.android.settings.flags.Flags;
 import com.android.settings.testutils.shadow.ShadowAccessibilityManager;
 import com.android.settings.testutils.shadow.ShadowFragment;
+import com.android.settingslib.widget.IllustrationPreference;
 import com.android.settingslib.widget.TopIntroPreference;
 
 import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -66,12 +75,14 @@
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowLooper;
 
 import java.util.List;
 import java.util.Locale;
@@ -84,6 +95,8 @@
 })
 public class ToggleFeaturePreferenceFragmentTest {
     @Rule
+    public final MockitoRule mocks = MockitoJUnit.rule();
+    @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private static final String PLACEHOLDER_PACKAGE_NAME = "com.placeholder.example";
@@ -96,6 +109,7 @@
             PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_TILE_CLASS_NAME);
     private static final String PLACEHOLDER_TILE_TOOLTIP_CONTENT =
             PLACEHOLDER_PACKAGE_NAME + "tooltip_content";
+    private static final String PLACEHOLDER_CATEGORY = "category";
     private static final String PLACEHOLDER_DIALOG_TITLE = "title";
     private static final String DEFAULT_SUMMARY = "default summary";
     private static final String DEFAULT_DESCRIPTION = "default description";
@@ -120,10 +134,13 @@
     private ContentResolver mContentResolver;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private Menu mMenu;
+    @Mock
+    private MenuItem mMenuItem;
 
     @Before
     public void setUpTestFragment() {
-        MockitoAnnotations.initMocks(this);
         mShadowAccessibilityManager = Shadow.extract(
                 mContext.getSystemService(AccessibilityManager.class));
 
@@ -170,6 +187,61 @@
     }
 
     @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
+    public void onCreateOptionsMenu_enableLowVisionGenericFeedback_shouldAddSendFeedbackMenu() {
+        mFragment.setFeedbackManager(
+                new FeedbackManager(mActivity, PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CATEGORY));
+
+        mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
+
+        verify(mMenu).add(anyInt(), eq(ToggleFeaturePreferenceFragment.MENU_ID_SEND_FEEDBACK),
+                anyInt(), eq(R.string.accessibility_send_feedback_title));
+    }
+
+    @Test
+    @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
+    public void onCreateOptionsMenu_disableLowVisionGenericFeedback_shouldNotAddSendFeedbackMenu() {
+        mFragment.setFeedbackManager(
+                new FeedbackManager(mActivity, PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CATEGORY));
+
+        mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
+
+        verify(mMenu, never()).add(anyInt(),
+                eq(ToggleFeaturePreferenceFragment.MENU_ID_SEND_FEEDBACK), anyInt(),
+                        eq(R.string.accessibility_send_feedback_title));
+    }
+
+    @Test
+    @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
+    public void onOptionsItemSelected_enableLowVisionGenericFeedback_shouldStartSendFeedback() {
+        mFragment.setFeedbackManager(
+                new FeedbackManager(mActivity, PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CATEGORY));
+        when(mMenuItem.getItemId()).thenReturn(
+                ToggleFeaturePreferenceFragment.MENU_ID_SEND_FEEDBACK);
+
+        mFragment.onOptionsItemSelected(mMenuItem);
+
+        verify(mActivity).startActivityForResult(
+                argThat(intent -> intent != null
+                        && Intent.ACTION_BUG_REPORT.equals(intent.getAction())), anyInt());
+    }
+
+    @Test
+    @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
+    public void onOptionsItemSelected_disableLowVisionGenericFeedback_shouldNotStartSendFeedback() {
+        mFragment.setFeedbackManager(
+                new FeedbackManager(mActivity, PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CATEGORY));
+        when(mMenuItem.getItemId()).thenReturn(
+                ToggleFeaturePreferenceFragment.MENU_ID_SEND_FEEDBACK);
+
+        mFragment.onOptionsItemSelected(mMenuItem);
+
+        verify(mActivity, never()).startActivityForResult(
+                argThat(intent -> intent != null
+                        && Intent.ACTION_BUG_REPORT.equals(intent.getAction())), anyInt());
+    }
+
+    @Test
     public void updateShortcutPreferenceData_assignDefaultValueToVariable() {
         mFragment.mComponentName = PLACEHOLDER_COMPONENT_NAME;
 
@@ -248,6 +320,45 @@
     }
 
     @Test
+    public void initAnimatedImagePreference_isAnimatable_setContentDescription() {
+        mFragment.mFeatureName = "Test Feature";
+        final View view =
+                LayoutInflater.from(mContext).inflate(
+                        com.android.settingslib.widget.preference.illustration
+                                .R.layout.illustration_preference,
+                        null);
+        IllustrationPreference preference = spy(new IllustrationPreference(mFragment.getContext()));
+        when(preference.isAnimatable()).thenReturn(true);
+        mFragment.initAnimatedImagePreference(mock(Uri.class), preference);
+
+        preference.onBindViewHolder(PreferenceViewHolder.createInstanceForTests(view));
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+        String expectedContentDescription = mFragment.getString(
+                R.string.accessibility_illustration_content_description, mFragment.mFeatureName);
+        assertThat(preference.getContentDescription().toString())
+                .isEqualTo(expectedContentDescription);
+    }
+
+    @Test
+    public void initAnimatedImagePreference_isNotAnimatable_notSetContentDescription() {
+        mFragment.mFeatureName = "Test Feature";
+        final View view =
+                LayoutInflater.from(mContext).inflate(
+                        com.android.settingslib.widget.preference.illustration
+                                .R.layout.illustration_preference,
+                        null);
+        IllustrationPreference preference = spy(new IllustrationPreference(mFragment.getContext()));
+        when(preference.isAnimatable()).thenReturn(false);
+        mFragment.initAnimatedImagePreference(mock(Uri.class), preference);
+
+        preference.onBindViewHolder(PreferenceViewHolder.createInstanceForTests(view));
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+        verify(preference, never()).setContentDescription(any());
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ACCESSIBILITY_SHOW_APP_INFO_BUTTON)
     public void createAppInfoPreference_withValidComponentName() {
         when(mPackageManager.isPackageAvailable(PLACEHOLDER_PACKAGE_NAME)).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java
index 3c136f0..6407c08 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java
@@ -614,6 +614,24 @@
     }
 
     @Test
+    @EnableFlags(com.android.settings.accessibility.Flags
+                .FLAG_ENABLE_MAGNIFICATION_CURSOR_FOLLOWING_DIALOG)
+    public void onCreateDialog_setCursorFollowingModeDialogDelegate_invokeDialogDelegate() {
+        ToggleScreenMagnificationPreferenceFragment fragment =
+                mFragController.create(
+                        R.id.main_content, /* bundle= */ null).start().resume().get();
+        final DialogCreatable dialogDelegate = mock(DialogCreatable.class, RETURNS_DEEP_STUBS);
+        final int dialogId = DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
+        when(dialogDelegate.getDialogMetricsCategory(anyInt())).thenReturn(dialogId);
+        fragment.setMagnificationCursorFollowingModeDialogDelegate(dialogDelegate);
+
+        fragment.onCreateDialog(dialogId);
+        fragment.getDialogMetricsCategory(dialogId);
+        verify(dialogDelegate).onCreateDialog(dialogId);
+        verify(dialogDelegate).getDialogMetricsCategory(dialogId);
+    }
+
+    @Test
     public void getMetricsCategory_returnsCorrectCategory() {
         ToggleScreenMagnificationPreferenceFragment fragment =
                 mFragController.create(
@@ -826,6 +844,7 @@
                 MagnificationOneFingerPanningPreferenceController.PREF_KEY,
                 MagnificationAlwaysOnPreferenceController.PREF_KEY,
                 MagnificationJoystickPreferenceController.PREF_KEY,
+                MagnificationCursorFollowingModePreferenceController.PREF_KEY,
                 MagnificationFeedbackPreferenceController.PREF_KEY);
 
         final List<SearchIndexableRaw> rawData = ToggleScreenMagnificationPreferenceFragment
@@ -881,7 +900,9 @@
     @EnableFlags({
             com.android.settings.accessibility.Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH,
             Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE,
-            Flags.FLAG_ENABLE_LOW_VISION_HATS})
+            Flags.FLAG_ENABLE_LOW_VISION_HATS,
+            com.android.settings.accessibility.Flags
+                    .FLAG_ENABLE_MAGNIFICATION_CURSOR_FOLLOWING_DIALOG})
     public void getNonIndexableKeys_hasShortcutAndAllFeaturesEnabled_allItemsSearchable() {
         mShadowAccessibilityManager.setAccessibilityShortcutTargets(
                 TRIPLETAP, List.of(MAGNIFICATION_CONTROLLER_NAME));
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java
index 65c9caf..7149d4f 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java
@@ -333,7 +333,7 @@
 
     @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_FEATURE_DISABLE_INSTALL_UNKNOWN_SOURCES)
     @Test
-    public void getPreferenceSummary_restrictedGlobally_adminString() {
+    public void getPreferenceSummary_restrictedGloballyByAdmin_adminString() {
         final EnforcingAdmin nonAdvancedProtectionEnforcingAdmin = new EnforcingAdmin("test.pkg",
                 UnknownAuthority.UNKNOWN_AUTHORITY, mUserHandle, new ComponentName("", ""));
 
@@ -353,7 +353,7 @@
 
     @RequiresFlagsEnabled(Flags.FLAG_AAPM_FEATURE_DISABLE_INSTALL_UNKNOWN_SOURCES)
     @Test
-    public void getPreferenceSummary_restrictedGlobally_advancedProtectionString() {
+    public void getPreferenceSummary_restrictedGloballyByAdvancedProtection_disabledString() {
         final EnforcingAdmin advancedProtectionEnforcingAdmin = new EnforcingAdmin("test.pkg",
                 new UnknownAuthority(ADVANCED_PROTECTION_SYSTEM_ENTITY), mUserHandle,
                 new ComponentName("", ""));
@@ -363,12 +363,10 @@
                         advancedProtectionEnforcingAdmin);
         when(mUserManager.hasUserRestrictionForUser(DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
                 mUserHandle)).thenReturn(true);
-        when(mContext.getString(
-                com.android.settingslib.widget.restricted.R.string.disabled_by_advanced_protection))
-                .thenReturn("disabled_by_advanced_protection");
+        when(mContext.getString(com.android.settingslib.R.string.disabled)).thenReturn("disabled");
 
         CharSequence summary = ExternalSourcesDetails.getPreferenceSummary(mContext, mAppEntry);
 
-        assertEquals("disabled_by_advanced_protection", summary.toString());
+        assertEquals("disabled", summary.toString());
     }
 }
diff --git a/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
index 563974d..6da6aa7 100644
--- a/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
@@ -243,4 +243,11 @@
                 .isEqualTo(mApplicationContext.getString(
                         R.string.biometric_settings_use_watch_for));
     }
+
+    @Test
+    public void getTitleForActiveUnlockOnly_returnsTile() {
+        assertThat(mActiveUnlockStatusUtils.getTitleForActiveUnlockOnly())
+                .isEqualTo(mApplicationContext.getString(
+                        R.string.security_settings_activeunlock));
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java
index 6a72c7d..d318e06 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java
@@ -34,6 +34,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Looper;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Pair;
@@ -175,6 +176,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI)
     public void onClicked_deviceNotBonded_shouldLogBluetoothPairEvent() {
         when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
         when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
@@ -192,6 +194,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI)
     public void onClicked_deviceNotBonded_shouldLogBluetoothPairEventAndPairWithoutNameEvent() {
         when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
         when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
index a0e971b..c82c978 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
@@ -28,7 +28,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -52,7 +51,9 @@
 import android.media.session.ISessionController;
 import android.media.session.MediaSessionManager;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.DisplayMetrics;
@@ -81,14 +82,12 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.android.util.concurrent.InlineExecutorService;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(
@@ -122,6 +121,7 @@
     @Mock private PackageManager mPackageManager;
     @Mock private DisplayMetrics mDisplayMetrics;
     @Mock private Context mContext;
+    @Mock private Handler mHandler;
     private FakeFeatureFactory mFeatureFactory;
     private AudioStreamMediaService mAudioStreamMediaService;
 
@@ -145,11 +145,18 @@
         when(mCachedBluetoothDevice.getName()).thenReturn(DEVICE_NAME);
         when(mLocalBluetoothProfileManager.getVolumeControlProfile())
                 .thenReturn(mVolumeControlProfile);
-
-        mAudioStreamMediaService = spy(new AudioStreamMediaService());
+        when(mHandler.post(any(Runnable.class))).thenAnswer(invocation -> {
+            ((Runnable) invocation.getArgument(0)).run();
+            return null;
+        });
+        when(mHandler.getLooper()).thenReturn(Looper.getMainLooper());
+        mAudioStreamMediaService = spy(new AudioStreamMediaService() {
+            @Override
+            Handler getHandler() {
+                return mHandler;
+            }
+        });
         ReflectionHelpers.setField(mAudioStreamMediaService, "mBase", mContext);
-        ReflectionHelpers.setField(
-                mAudioStreamMediaService, "mExecutor", new InlineExecutorService());
         when(mAudioStreamMediaService.getSystemService(anyString()))
                 .thenReturn(mMediaSessionManager);
         when(mMediaSessionManager.createSession(any(), anyString(), any())).thenReturn(mISession);
@@ -392,31 +399,6 @@
     }
 
     @Test
-    public void bluetoothCallback_onMemberDeviceDisconnect_stopSelf() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
-        when(mCachedBluetoothDevice.getDevice()).thenReturn(mock(BluetoothDevice.class));
-        CachedBluetoothDevice member = mock(CachedBluetoothDevice.class);
-        when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(Set.of(member));
-        when(member.getDevice()).thenReturn(mDevice);
-        var devices = new ArrayList<BluetoothDevice>();
-        devices.add(mDevice);
-
-        Intent intent = new Intent();
-        intent.putExtra(BROADCAST_ID, 1);
-        intent.putParcelableArrayListExtra(DEVICES, devices);
-
-        mAudioStreamMediaService.onCreate();
-        assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
-        mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
-        mAudioStreamMediaService.mBluetoothCallback.onProfileConnectionStateChanged(
-                mCachedBluetoothDevice,
-                BluetoothAdapter.STATE_DISCONNECTED,
-                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
-
-        verify(mAudioStreamMediaService).stopSelf();
-    }
-
-    @Test
     public void mediaSessionCallback_onPause_setVolume() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
 
diff --git a/tests/robotests/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceControllerTest.java
new file mode 100644
index 0000000..82afec2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceControllerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2025 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.hardware.input.InputSettings;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+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 MousePointerSpeedPreferenceController} */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+        com.android.settings.testutils.shadow.ShadowSystemSettings.class,
+})
+public class MousePointerSpeedPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    private static final String PREFERENCE_KEY = "pointer_speed";
+    private static final String SETTING_KEY = Settings.System.POINTER_SPEED;
+
+    private MousePointerSpeedPreferenceController mController;
+    private int mDefaultSpeed;
+    private FakeFeatureFactory mFeatureFactory;
+
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
+        mController = new MousePointerSpeedPreferenceController(context, PREFERENCE_KEY);
+        mDefaultSpeed = Settings.System.getIntForUser(
+                context.getContentResolver(),
+                SETTING_KEY,
+                InputSettings.DEFAULT_POINTER_SPEED,
+                UserHandle.USER_CURRENT);
+    }
+
+    @Test
+    public void setSliderPosition_speedValue1_shouldReturnTrue() {
+        int inputSpeed = 1;
+
+        boolean result = mController.setSliderPosition(inputSpeed);
+
+        assertThat(result).isTrue();
+        assertThat(mController.getSliderPosition()).isEqualTo(inputSpeed);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_POINTER_SPEED_CHANGED),
+                eq(1));
+    }
+
+    @Test
+    public void setSliderPosition_speedValueOverMaxValue_shouldReturnFalse() {
+        int inputSpeed = InputSettings.MAX_POINTER_SPEED + 1;
+
+        boolean result = mController.setSliderPosition(inputSpeed);
+
+        assertThat(result).isFalse();
+        assertThat(mController.getSliderPosition()).isEqualTo(mDefaultSpeed);
+    }
+
+    @Test
+    public void setSliderPosition_speedValueOverMinValue_shouldReturnFalse() {
+        int inputSpeed = InputSettings.MIN_POINTER_SPEED - 1;
+
+        boolean result = mController.setSliderPosition(inputSpeed);
+
+        assertThat(result).isFalse();
+        assertThat(mController.getSliderPosition()).isEqualTo(mDefaultSpeed);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
index 4272afe..22d39e3 100644
--- a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
@@ -36,7 +36,6 @@
 import android.app.Dialog;
 import android.app.IActivityManager;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -100,6 +99,8 @@
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     private static final String ARG_DIALOG_TYPE = "arg_dialog_type";
+    private static final String
+            ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED = "arg_show_dialog_for_not_translated";
     private static final String TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT = "dialog_confirm_system_default";
     private static final String TAG_DIALOG_NOT_AVAILABLE = "dialog_not_available_locale";
     private static final String TAG_DIALOG_ADD_SYSTEM_LOCALE = "dialog_add_system_locale";
@@ -123,6 +124,10 @@
     @Mock
     private LocaleStore.LocaleInfo mLocaleInfo;
     @Mock
+    private LocaleStore.LocaleInfo mLocaleInfo1;
+    @Mock
+    private LocaleStore.LocaleInfo mLocaleInfo2;
+    @Mock
     private FragmentManager mFragmentManager;
     @Mock
     private FragmentTransaction mFragmentTransaction;
@@ -270,7 +275,7 @@
     public void showConfirmDialog_systemLocaleSelected_shouldShowLocaleChangeDialog()
             throws Exception {
         //pre-condition
-        setUpLocaleConditions();
+        setUpLocaleConditions(true);
         final Configuration config = new Configuration();
         config.setLocales((LocaleList.forLanguageTags("zh-TW,en-US")));
         when(mActivityService.getConfiguration()).thenReturn(config);
@@ -300,6 +305,41 @@
     }
 
     @Test
+    public void showConfirmDialog_2ndLocaleSelected_shouldShowLocaleChangeDialog()
+            throws Exception {
+        //pre-condition
+        Locale.setDefault(Locale.forLanguageTag("en-US"));
+        setUpLocaleConditions2();
+        final Configuration config = new Configuration();
+        config.setLocales((LocaleList.forLanguageTags("blo-BJ,en-US,zh-TW")));
+        when(mActivityService.getConfiguration()).thenReturn(config);
+        when(mAdapter.getFeedItemList()).thenReturn(mLocaleList);
+        when(mAdapter.getCheckedCount()).thenReturn(1);
+        when(mAdapter.getItemCount()).thenReturn(3);
+        when(mAdapter.isFirstLocaleChecked()).thenReturn(false);
+        ReflectionHelpers.setField(mLocaleListEditor, "mRemoveMode", true);
+        ReflectionHelpers.setField(mLocaleListEditor, "mShowingRemoveDialog", true);
+
+        //launch the first dialog
+        mLocaleListEditor.showRemoveLocaleWarningDialog();
+
+        final Dialog dialog = ShadowDialog.getLatestDialog();
+
+        assertThat(dialog).isNotNull();
+
+        // click the remove button
+        dialog.findViewById(R.id.button_ok).performClick();
+        ShadowLooper.idleMainLooper();
+
+        assertThat(dialog.isShowing()).isFalse();
+
+        // check the second dialog is showing
+        verify(mFragmentTransaction).add(any(LocaleDialogFragment.class),
+                eq(TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT));
+    }
+
+
+    @Test
     public void mayAppendUnicodeTags_appendUnicodeTags_success() {
         LocaleStore.LocaleInfo localeInfo = LocaleStore.fromLocale(Locale.forLanguageTag("en-US"));
 
@@ -315,7 +355,8 @@
         Bundle bundle = new Bundle();
         bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
         mIntent.putExtras(bundle);
-        setUpLocaleConditions();
+        mIntent.putExtra(ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED, true);
+        setUpLocaleConditions(false);
         mLocaleListEditor.onActivityResult(REQUEST_CONFIRM_SYSTEM_DEFAULT, Activity.RESULT_OK,
                 mIntent);
 
@@ -328,7 +369,7 @@
         Bundle bundle = new Bundle();
         bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
         mIntent.putExtras(bundle);
-        setUpLocaleConditions();
+        setUpLocaleConditions(true);
         mLocaleListEditor.onActivityResult(REQUEST_CONFIRM_SYSTEM_DEFAULT, Activity.RESULT_CANCELED,
                 mIntent);
 
@@ -338,7 +379,7 @@
     @Test
     public void onTouch_dragDifferentLocaleToTop_showConfirmDialog() throws Exception {
         MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
-        setUpLocaleConditions();
+        setUpLocaleConditions(true);
         final Configuration config = new Configuration();
         config.setLocales((LocaleList.forLanguageTags("zh-TW,en-US")));
         when(mActivityService.getConfiguration()).thenReturn(config);
@@ -352,7 +393,7 @@
     @Test
     public void onTouch_dragSameLocaleToTop_updateAdapter() throws Exception {
         MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
-        setUpLocaleConditions();
+        setUpLocaleConditions(true);
         final Configuration config = new Configuration();
         config.setLocales((LocaleList.forLanguageTags("en-US,zh-TW")));
         when(mActivityService.getConfiguration()).thenReturn(config);
@@ -490,12 +531,26 @@
         verify(mAdapter).setCheckBoxDescription(any(LocaleDragCell.class), any(), anyBoolean());
     }
 
-    private void setUpLocaleConditions() {
+    private void setUpLocaleConditions(boolean isTranslated) {
         ShadowActivityManager.setService(mActivityService);
         mLocaleList = new ArrayList<>();
         mLocaleList.add(mLocaleInfo);
         when(mLocaleInfo.getFullNameNative()).thenReturn("English");
         when(mLocaleInfo.getLocale()).thenReturn(LocaleList.forLanguageTags("en-US").get(0));
+        when(mLocaleInfo.isTranslated()).thenReturn(isTranslated);
+        when(mAdapter.getFeedItemList()).thenReturn(mLocaleList);
+    }
+
+    private void setUpLocaleConditions2() {
+        ShadowActivityManager.setService(mActivityService);
+        mLocaleList = new ArrayList<>();
+        mLocaleList.add(mLocaleInfo);
+        mLocaleList.add(mLocaleInfo1);
+        mLocaleList.add(mLocaleInfo2);
+        when(mLocaleInfo.getLocale()).thenReturn(Locale.forLanguageTag("blo-BJ"));
+        when(mLocaleInfo.isTranslated()).thenReturn(false);
+        when(mLocaleInfo2.getLocale()).thenReturn(Locale.forLanguageTag("zh-TW"));
+        when(mLocaleInfo2.isTranslated()).thenReturn(true);
         when(mAdapter.getFeedItemList()).thenReturn(mLocaleList);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/network/NetworkResetPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/NetworkResetPreferenceControllerTest.java
index 73f4b6a..e263ea7 100644
--- a/tests/robotests/src/com/android/settings/network/NetworkResetPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/NetworkResetPreferenceControllerTest.java
@@ -19,9 +19,16 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.res.Resources;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.R;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,27 +42,65 @@
 public class NetworkResetPreferenceControllerTest {
 
     @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
     private NetworkResetRestrictionChecker mRestrictionChecker;
     private NetworkResetPreferenceController mController;
+    private Context mContext;
+    private Resources mResources;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new NetworkResetPreferenceController(RuntimeEnvironment.application);
+        mContext = spy(RuntimeEnvironment.application);
+
+        mResources = spy(mContext.getResources());
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+
+        mController = new NetworkResetPreferenceController(mContext);
         ReflectionHelpers.setField(mController, "mRestrictionChecker", mRestrictionChecker);
+
+        // Availability defaults
+        when(mTelephonyManager.isDataCapable()).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
+        when(mRestrictionChecker.isRestrictionEnforcedByAdmin()).thenReturn(false);
     }
 
     @Test
-    public void testIsAvailable_shouldReturnTrueWhenNoUserRestriction() {
-        when(mRestrictionChecker.isRestrictionEnforcedByAdmin()).thenReturn(true);
+    public void testIsAvailable_showSimInfo_notWifiOnly() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
 
+    @Test
+    public void testIsAvailable_hideSimInfo_notWifiOnly() {
+        when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvailable_showSimInfo_wifiOnly() {
+        when(mTelephonyManager.isDataCapable()).thenReturn(false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvailable_userRestriction() {
+        when(mRestrictionChecker.isRestrictionEnforcedByAdmin()).thenReturn(true);
         when(mRestrictionChecker.hasUserRestriction()).thenReturn(true);
 
         assertThat(mController.isAvailable()).isFalse();
 
+        verify(mRestrictionChecker, never()).isRestrictionEnforcedByAdmin();
+    }
+
+    @Test
+    public void testIsAvailable_noUserRestriction() {
+        when(mRestrictionChecker.isRestrictionEnforcedByAdmin()).thenReturn(true);
         when(mRestrictionChecker.hasUserRestriction()).thenReturn(false);
 
         assertThat(mController.isAvailable()).isTrue();
+
         verify(mRestrictionChecker, never()).isRestrictionEnforcedByAdmin();
     }
 }
diff --git a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
index c5d4c36..e002de1 100644
--- a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
@@ -20,7 +20,7 @@
 import android.content.Context;
 
 import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider;
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider;
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider;
 import com.android.settings.accessibility.AccessibilitySearchFeatureProvider;
 import com.android.settings.accounts.AccountFeatureProvider;
 import com.android.settings.applications.ApplicationFeatureProvider;
@@ -94,7 +94,7 @@
     public WifiTrackerLibProvider wifiTrackerLibProvider;
     public SecuritySettingsFeatureProvider securitySettingsFeatureProvider;
     public AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider;
-    public AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider;
+    public AccessibilityPageIdFeatureProvider mAccessibilityPageIdFeatureProvider;
     public AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider;
     public WifiFeatureProvider mWifiFeatureProvider;
     public KeyboardSettingsFeatureProvider mKeyboardSettingsFeatureProvider;
@@ -145,7 +145,7 @@
         wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
         securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
         mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
-        mAccessibilityMetricsFeatureProvider = mock(AccessibilityMetricsFeatureProvider.class);
+        mAccessibilityPageIdFeatureProvider = mock(AccessibilityPageIdFeatureProvider.class);
         mAdvancedVpnFeatureProvider = mock(AdvancedVpnFeatureProvider.class);
         mWifiFeatureProvider = mock(WifiFeatureProvider.class);
         mKeyboardSettingsFeatureProvider = mock(KeyboardSettingsFeatureProvider.class);
@@ -294,8 +294,8 @@
     }
 
     @Override
-    public AccessibilityMetricsFeatureProvider getAccessibilityMetricsFeatureProvider() {
-        return mAccessibilityMetricsFeatureProvider;
+    public AccessibilityPageIdFeatureProvider getAccessibilityPageIdFeatureProvider() {
+        return mAccessibilityPageIdFeatureProvider;
     }
 
     @Override
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
index 6297c62..52ee077 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
@@ -122,6 +122,25 @@
     }
 
     @Test
+    fun whenIsPlayStoreApp_notDisplayed() {
+        val playStorePackageName = "com.android.vending"
+        whenever(
+            AppStoreUtil.getInstallerPackageNameAndInstallSourceInfo(
+                any(),
+                eq(playStorePackageName)
+            )
+        )
+            .thenReturn(Pair(INSTALLER_PACKAGE_NAME, INSTALL_SOURCE_INFO))
+        val playStoreApp = ApplicationInfo().apply {
+            packageName = playStorePackageName
+            uid = UID
+        }
+        setContent(playStoreApp)
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
     fun whenStoreLinkIsNull_disabled() {
         whenever(AppStoreUtil.getAppStoreLink(context, INSTALLER_PACKAGE_NAME, PACKAGE_NAME))
             .thenReturn(null)
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index 56dd444..7b1bdc0 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -18,7 +18,7 @@
 
 import android.content.Context
 import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider
 import com.android.settings.accessibility.AccessibilitySearchFeatureProvider
 import com.android.settings.accounts.AccountFeatureProvider
 import com.android.settings.applications.ApplicationFeatureProvider
@@ -130,7 +130,7 @@
         get() = TODO("Not yet implemented")
     override val accessibilitySearchFeatureProvider: AccessibilitySearchFeatureProvider
         get() = TODO("Not yet implemented")
-    override val accessibilityMetricsFeatureProvider: AccessibilityMetricsFeatureProvider
+    override val accessibilityPageIdFeatureProvider: AccessibilityPageIdFeatureProvider
         get() = TODO("Not yet implemented")
     override val advancedVpnFeatureProvider: AdvancedVpnFeatureProvider
         get() = TODO("Not yet implemented")
diff --git a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
index f16113a..6e46d2b 100644
--- a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
@@ -527,6 +527,9 @@
         verify(mSafetyCenterManagerWrapper)
                 .setSafetySourceData(
                         any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), any(), any());
+        verify(mSafetyCenterManagerWrapper)
+                .setSafetySourceData(
+                        any(), eq(WearSafetySource.SAFETY_SOURCE_ID), any(), any());
     }
 
     @Test
diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
index e65d041..836247c 100644
--- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
@@ -246,6 +246,25 @@
     }
 
     @Test
+    public void onReceive_onRefresh_withWearUnlockSourceId_setsWearUnlockData() {
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+        Intent intent =
+                new Intent()
+                        .setAction(ACTION_REFRESH_SAFETY_SOURCES)
+                        .putExtra(
+                                EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+                                new String[] {WearSafetySource.SAFETY_SOURCE_ID})
+                        .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID);
+
+        new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        verify(mSafetyCenterManagerWrapper, times(1))
+                .setSafetySourceData(any(), captor.capture(), any(), any());
+
+        assertThat(captor.getValue()).isEqualTo(WearSafetySource.SAFETY_SOURCE_ID);
+    }
+
+    @Test
     public void onReceive_onRefresh_withFingerprintUnlockSourceId_setsFingerprintUnlockData() {
         when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent =
@@ -332,7 +351,7 @@
 
         new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
         ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
-        verify(mSafetyCenterManagerWrapper, times(5))
+        verify(mSafetyCenterManagerWrapper, times(6))
                 .setSafetySourceData(any(), captor.capture(), any(), any());
         List<String> safetySourceIdList = captor.getAllValues();
 
@@ -356,6 +375,11 @@
         assertThat(
                         safetySourceIdList.stream()
                                 .anyMatch(
+                                        id -> id.equals(WearSafetySource.SAFETY_SOURCE_ID)))
+                .isTrue();
+        assertThat(
+                        safetySourceIdList.stream()
+                                .anyMatch(
                                         id -> id.equals(PrivateSpaceSafetySource.SAFETY_SOURCE_ID)))
                 .isTrue();
     }
diff --git a/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java
new file mode 100644
index 0000000..c0c982d
--- /dev/null
+++ b/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2025 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.safetycenter;
+
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.safetycenter.SafetyEvent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceStatus;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
+import com.android.settings.flags.Flags;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.ResourcesUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class WearSafetySourceTest {
+
+    private static final ComponentName COMPONENT_NAME = new ComponentName("package", "class");
+    private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId());
+    private static final SafetyEvent EVENT_SOURCE_STATE_CHANGED =
+            new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build();
+    public static final String TARGET = "com.active.unlock.target";
+    public static final String PROVIDER = "com.active.unlock.provider";
+    public static final String TARGET_SETTING = "active_unlock_target";
+    public static final String PROVIDER_SETTING = "active_unlock_provider";
+    public static final String SUMMARY = "Wear Summary";
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private Context mApplicationContext;
+
+    @Mock private PackageManager mPackageManager;
+    @Mock private DevicePolicyManager mDevicePolicyManager;
+    @Mock private FingerprintManager mFingerprintManager;
+    @Mock private LockPatternUtils mLockPatternUtils;
+    @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mApplicationContext = spy(ApplicationProvider.getApplicationContext());
+        when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(USER_HANDLE))
+                .thenReturn(COMPONENT_NAME);
+        when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE))
+                .thenReturn(mFingerprintManager);
+        when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
+                .thenReturn(mDevicePolicyManager);
+        FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+        when(featureFactory.securityFeatureProvider.getLockPatternUtils(mApplicationContext))
+                .thenReturn(mLockPatternUtils);
+        doReturn(true).when(mLockPatternUtils).isSecure(anyInt());
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
+    }
+
+    @After
+    public void tearDown() {
+        SafetyCenterManagerWrapper.sInstance = null;
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void setSafetyData_whenSafetyCenterIsDisabled_doesNotSetData() {
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false);
+
+        WearSafetySource.setSafetySourceData(
+                mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+        verify(mSafetyCenterManagerWrapper, never())
+                .setSafetySourceData(any(), any(), any(), any());
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void setSafetySourceData_whenSeparateBiometricsFlagOff_setsNullData() {
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+        WearSafetySource.setSafetySourceData(
+                mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+        verify(mSafetyCenterManagerWrapper)
+                .setSafetySourceData(
+                        any(), eq(WearSafetySource.SAFETY_SOURCE_ID), eq(null), any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void setSafetySourceData_whenSafetyCenterIsEnabled_activeUnlockDisabled_setsNullData() {
+        disableActiveUnlock(mApplicationContext);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+        WearSafetySource.setSafetySourceData(
+                mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+        verify(mSafetyCenterManagerWrapper)
+                .setSafetySourceData(
+                        any(), eq(WearSafetySource.SAFETY_SOURCE_ID), eq(null), any());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void setSafetySourceData_setsDataWithCorrectSafetyEvent() {
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+        WearSafetySource.setSafetySourceData(
+                mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+        verify(mSafetyCenterManagerWrapper)
+                .setSafetySourceData(any(), any(), any(), eq(EVENT_SOURCE_STATE_CHANGED));
+    }
+
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void setSafetySourceData_withWearEnabled_whenWearEnrolled_setsData() {
+        enableActiveUnlock(mApplicationContext);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
+
+        WearSafetySource.setHasEnrolledForTesting(true);
+        WearSafetySource.setSummaryForTesting(SUMMARY);
+
+        WearSafetySource.setSafetySourceData(
+                mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+        assertSafetySourceEnabledDataSet(
+                ResourcesUtils.getResourcesString(mApplicationContext,
+                "security_settings_activeunlock"),
+                SUMMARY);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void setSafetySourceData_withWearEnabled_whenWearNotEnrolled_setsData() {
+        enableActiveUnlock(mApplicationContext);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0);
+
+        WearSafetySource.setHasEnrolledForTesting(false);
+        WearSafetySource.setSummaryForTesting(SUMMARY);
+
+        WearSafetySource.setSafetySourceData(
+                mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+        assertSafetySourceDisabledDataSet(
+                ResourcesUtils.getResourcesString(mApplicationContext,
+                "security_settings_activeunlock"),
+                SUMMARY);
+    }
+
+    private static void disableActiveUnlock(Context context) {
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_REMOTE_AUTH,
+                ActiveUnlockStatusUtils.CONFIG_FLAG_NAME,
+                /* value= */ null,
+                /* makeDefault=*/ false);
+        Settings.Secure.putString(context.getContentResolver(), TARGET_SETTING, null);
+        Settings.Secure.putString(context.getContentResolver(), PROVIDER_SETTING, null);
+    }
+
+    private static void enableActiveUnlock(Context context) {
+        Settings.Secure.putString(
+                context.getContentResolver(), TARGET_SETTING, TARGET);
+        Settings.Secure.putString(
+                context.getContentResolver(), PROVIDER_SETTING, PROVIDER);
+
+        PackageManager packageManager = context.getPackageManager();
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = applicationInfo;
+        when(packageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo);
+
+        ProviderInfo providerInfo = new ProviderInfo();
+        providerInfo.authority = PROVIDER;
+        providerInfo.applicationInfo = applicationInfo;
+        when(packageManager.resolveContentProvider(anyString(), any())).thenReturn(providerInfo);
+
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_REMOTE_AUTH,
+                ActiveUnlockStatusUtils.CONFIG_FLAG_NAME,
+                "unlock_intent_layout",
+                false /* makeDefault */);
+    }
+
+    private void assertSafetySourceDisabledDataSet(String expectedTitle, String expectedSummary) {
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper)
+                .setSafetySourceData(
+                        any(),
+                        eq(WearSafetySource.SAFETY_SOURCE_ID),
+                        captor.capture(),
+                        any());
+        SafetySourceData safetySourceData = captor.getValue();
+        SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+        assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle);
+        assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary);
+        assertThat(safetySourceStatus.isEnabled()).isTrue();
+        assertThat(safetySourceStatus.getSeverityLevel())
+                .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED);
+
+        Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent();
+        assertThat(clickIntent).isNotNull();
+        assertThat(clickIntent.getAction()).isEqualTo(TARGET);
+    }
+
+    private void assertSafetySourceEnabledDataSet(
+            String expectedTitle, String expectedSummary) {
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper)
+                .setSafetySourceData(
+                        any(),
+                        eq(WearSafetySource.SAFETY_SOURCE_ID),
+                        captor.capture(),
+                        any());
+        SafetySourceData safetySourceData = captor.getValue();
+        SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+        assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle);
+        assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary);
+        assertThat(safetySourceStatus.isEnabled()).isTrue();
+        assertThat(safetySourceStatus.getSeverityLevel())
+                .isEqualTo(SafetySourceData.SEVERITY_LEVEL_INFORMATION);
+        Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent();
+        assertThat(clickIntent).isNotNull();
+    }
+}
diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
index d77d7a4..eda0aeb 100644
--- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -20,7 +20,7 @@
 import android.content.Context;
 
 import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider;
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider;
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider;
 import com.android.settings.accessibility.AccessibilitySearchFeatureProvider;
 import com.android.settings.accounts.AccountFeatureProvider;
 import com.android.settings.applications.ApplicationFeatureProvider;
@@ -93,7 +93,7 @@
     public WifiTrackerLibProvider wifiTrackerLibProvider;
     public SecuritySettingsFeatureProvider securitySettingsFeatureProvider;
     public AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider;
-    public AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider;
+    public AccessibilityPageIdFeatureProvider mAccessibilityPageIdFeatureProvider;
     public AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider;
     public WifiFeatureProvider mWifiFeatureProvider;
     public KeyboardSettingsFeatureProvider mKeyboardSettingsFeatureProvider;
@@ -146,7 +146,7 @@
         wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
         securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
         mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
-        mAccessibilityMetricsFeatureProvider = mock(AccessibilityMetricsFeatureProvider.class);
+        mAccessibilityPageIdFeatureProvider = mock(AccessibilityPageIdFeatureProvider.class);
         mAdvancedVpnFeatureProvider = mock(AdvancedVpnFeatureProvider.class);
         mWifiFeatureProvider = mock(WifiFeatureProvider.class);
         mKeyboardSettingsFeatureProvider = mock(KeyboardSettingsFeatureProvider.class);
@@ -295,8 +295,8 @@
     }
 
     @Override
-    public AccessibilityMetricsFeatureProvider getAccessibilityMetricsFeatureProvider() {
-        return mAccessibilityMetricsFeatureProvider;
+    public AccessibilityPageIdFeatureProvider getAccessibilityPageIdFeatureProvider() {
+        return mAccessibilityPageIdFeatureProvider;
     }
 
     @Override