Merge "Increase character limit on PSS toggle translation" into main
diff --git a/aconfig/settings_display_flag_declarations.aconfig b/aconfig/settings_display_flag_declarations.aconfig
new file mode 100644
index 0000000..52a326d
--- /dev/null
+++ b/aconfig/settings_display_flag_declarations.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.settings.flags"
+
+flag {
+    name: "protect_screen_timeout_with_auth"
+    namespace: "safety_center"
+    description: "Require an auth challenge for increasing screen timeout."
+    bug: "315937886"
+}
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dedebc7..6f389ba 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -88,6 +88,11 @@
     <string name="selective_stay_awake_title">Only games, videos, and more</string>
     <!-- Summary for selective stay awake radio button. [CHAR_LIMIT=NONE] -->
     <string name="selective_stay_awake_summary">Front display turns on for apps that stop your screen going idle</string>
+    <!-- Title for fold grace period radio button that, on fold, goes to the lockscreen. [CHAR_LIMIT=NONE] -->
+    <string name="stay_awake_on_lockscreen_title">Swipe up to continue</string>
+    <!-- Summary for folding grace period radio button that, on fold, goes to the lockscreen. [CHAR_LIMIT=NONE] -->
+    <string name="stay_awake_on_lockscreen_summary">Fold your phone and swipe up on the front display to continue using the app, or wait a few seconds for the screen to lock</string>
+
     <!-- Title for sleep on fold radio button. [CHAR_LIMIT=NONE] -->
     <string name="sleep_on_fold_title">Never</string>
     <!-- Summary for sleep on fold radio button. [CHAR_LIMIT=NONE] -->
@@ -4254,12 +4259,22 @@
     <string name="add_virtual_keyboard">Manage on-screen keyboards</string>
     <!-- Title for the 'keyboard options' preference category. [CHAR LIMIT=35] -->
     <string name="keyboard_options_category">Options</string>
+    <!-- Title for the 'keyboard accessibility options' preference category. [CHAR LIMIT=35] -->
+    <string name="keyboard_a11y_category">Accessibility</string>
     <!-- Title for the 'physical keyboard' settings screen. [CHAR LIMIT=35] -->
     <string name="physical_keyboard_title">Physical keyboard</string>
     <!-- Title for the 'show virtual keyboard' preference switch. [CHAR LIMIT=35] -->
     <string name="show_ime">Use on-screen keyboard</string>
     <!-- Summary text for the 'add virtual keyboard' preference sub-screen. [CHAR LIMIT=100] -->
     <string name="show_ime_summary">Keep it on screen while physical keyboard is active</string>
+    <!-- Title for the 'Bounce keys' preference switch. [CHAR LIMIT=35] -->
+    <string name="bounce_keys">Bounce keys</string>
+    <!-- Summary text for the 'Bounce keys' preference sub-screen. [CHAR LIMIT=100] -->
+    <string name="bounce_keys_summary">Enable Bounce keys for physical keyboard accessibility</string>
+    <!-- Title for the 'Sticky keys' preference switch. [CHAR LIMIT=35] -->
+    <string name="sticky_keys">Sticky keys</string>
+    <!-- Summary text for the 'Sticky keys' preference sub-screen. [CHAR LIMIT=100] -->
+    <string name="sticky_keys_summary">Enable Sticky keys for physical keyboard accessibility</string>
     <!-- Title for the button to trigger the 'keyboard shortcuts helper' dialog. [CHAR LIMIT=35] -->
     <string name="keyboard_shortcuts_helper">Keyboard shortcuts</string>
     <!-- Summary text for the 'keyboard shortcuts helper' dialog. [CHAR LIMIT=100] -->
diff --git a/res/xml/physical_keyboard_settings.xml b/res/xml/physical_keyboard_settings.xml
index d8e66bb..dc424d1 100644
--- a/res/xml/physical_keyboard_settings.xml
+++ b/res/xml/physical_keyboard_settings.xml
@@ -38,4 +38,22 @@
             android:summary="@string/modifier_keys_settings_summary"
             android:fragment="com.android.settings.inputmethod.ModifierKeysSettings" />
     </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="keyboard_a11y_category"
+        android:title="@string/keyboard_a11y_category">
+
+        <SwitchPreference
+            android:key="accessibility_bounce_keys"
+            android:title="@string/bounce_keys"
+            android:summary="@string/bounce_keys_summary"
+            android:defaultValue="false" />
+
+        <SwitchPreference
+            android:key="accessibility_sticky_keys"
+            android:title="@string/sticky_keys"
+            android:summary="@string/sticky_keys_summary"
+            android:defaultValue="false" />
+
+    </PreferenceCategory>
 </PreferenceScreen>
diff --git a/src/com/android/settings/display/FoldLockBehaviorPreferenceController.java b/src/com/android/settings/display/FoldLockBehaviorPreferenceController.java
index 661eb99..bee3a22 100644
--- a/src/com/android/settings/display/FoldLockBehaviorPreferenceController.java
+++ b/src/com/android/settings/display/FoldLockBehaviorPreferenceController.java
@@ -29,6 +29,7 @@
 
 import androidx.preference.Preference;
 
+import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider;
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
@@ -58,8 +59,13 @@
         mFoldLockSettingAvailabilityProvider = foldLockSettingAvailabilityProvider;
         KEY_TO_TEXT.put(SETTING_VALUE_STAY_AWAKE_ON_FOLD,
                 resourceToString(R.string.stay_awake_on_fold_title));
-        KEY_TO_TEXT.put(SETTING_VALUE_SELECTIVE_STAY_AWAKE,
-                resourceToString(R.string.selective_stay_awake_title));
+        if (new FoldGracePeriodProvider().isEnabled()) {
+            KEY_TO_TEXT.put(SETTING_VALUE_SELECTIVE_STAY_AWAKE,
+                    resourceToString(R.string.stay_awake_on_lockscreen_title));
+        } else {
+            KEY_TO_TEXT.put(SETTING_VALUE_SELECTIVE_STAY_AWAKE,
+                    resourceToString(R.string.selective_stay_awake_title));
+        }
         KEY_TO_TEXT.put(SETTING_VALUE_SLEEP_ON_FOLD,
                 resourceToString(R.string.sleep_on_fold_title));
     }
diff --git a/src/com/android/settings/display/FoldLockBehaviorSettings.java b/src/com/android/settings/display/FoldLockBehaviorSettings.java
index e94b17e..432c230 100644
--- a/src/com/android/settings/display/FoldLockBehaviorSettings.java
+++ b/src/com/android/settings/display/FoldLockBehaviorSettings.java
@@ -24,6 +24,7 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.settings.R;
 import com.android.settings.support.actionbar.HelpResourceProvider;
 import com.android.settings.utils.CandidateInfoExtra;
@@ -54,6 +55,7 @@
                     SETTING_VALUE_SLEEP_ON_FOLD));
     private static final String SETTING_VALUE_DEFAULT = SETTING_VALUE_SELECTIVE_STAY_AWAKE;
     private Context mContext;
+    private final FoldGracePeriodProvider mFoldGracePeriodProvider = new FoldGracePeriodProvider();
 
     @Override
     public void onAttach(Context context) {
@@ -69,10 +71,17 @@
                 resourceToString(R.string.stay_awake_on_fold_title),
                 resourceToString(R.string.stay_awake_on_fold_summary),
                 SETTING_VALUE_STAY_AWAKE_ON_FOLD, /* enabled */ true));
-        candidates.add(new CandidateInfoExtra(
-                resourceToString(R.string.selective_stay_awake_title),
-                resourceToString(R.string.selective_stay_awake_summary),
-                SETTING_VALUE_SELECTIVE_STAY_AWAKE, /* enabled */ true));
+        if (mFoldGracePeriodProvider.isEnabled()) {
+            candidates.add(new CandidateInfoExtra(
+                    resourceToString(R.string.stay_awake_on_lockscreen_title),
+                    resourceToString(R.string.stay_awake_on_lockscreen_summary),
+                    SETTING_VALUE_SELECTIVE_STAY_AWAKE, /* enabled */ true));
+        } else {
+            candidates.add(new CandidateInfoExtra(
+                    resourceToString(R.string.selective_stay_awake_title),
+                    resourceToString(R.string.selective_stay_awake_summary),
+                    SETTING_VALUE_SELECTIVE_STAY_AWAKE, /* enabled */ true));
+        }
         candidates.add(new CandidateInfoExtra(
                 resourceToString(R.string.sleep_on_fold_title),
                 resourceToString(R.string.sleep_on_fold_summary),
diff --git a/src/com/android/settings/display/ScreenTimeoutSettings.java b/src/com/android/settings/display/ScreenTimeoutSettings.java
index f7be319..1c99d5f 100644
--- a/src/com/android/settings/display/ScreenTimeoutSettings.java
+++ b/src/com/android/settings/display/ScreenTimeoutSettings.java
@@ -37,10 +37,12 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
+import com.android.settings.flags.Flags;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.support.actionbar.HelpResourceProvider;
 import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settings.wifi.dpp.WifiDppUtils;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
@@ -55,13 +57,12 @@
 import java.util.ArrayList;
 import java.util.List;
 
-/**
- * Fragment that is used to control screen timeout.
- */
+/** Fragment that is used to control screen timeout. */
 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
-public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements
-        HelpResourceProvider {
+public class ScreenTimeoutSettings extends RadioButtonPickerFragment
+        implements HelpResourceProvider {
     private static final String TAG = "ScreenTimeout";
+
     /** If there is no setting in the provider, use this. */
     public static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000;
 
@@ -72,25 +73,24 @@
     private FooterPreference mPrivacyPreference;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private SensorPrivacyManager mPrivacyManager;
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mAdaptiveSleepBatterySaverPreferenceController.updateVisibility();
-            mAdaptiveSleepController.updatePreference();
-        }
-    };
+    private final BroadcastReceiver mReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    mAdaptiveSleepBatterySaverPreferenceController.updateVisibility();
+                    mAdaptiveSleepController.updatePreference();
+                }
+            };
 
     private DevicePolicyManager mDevicePolicyManager;
     private SensorPrivacyManager.OnSensorPrivacyChangedListener mPrivacyChangedListener;
+    private boolean mIsUserAuthenticated = false;
 
-    @VisibleForTesting
-    Context mContext;
+    @VisibleForTesting Context mContext;
 
-    @VisibleForTesting
-    RestrictedLockUtils.EnforcedAdmin mAdmin;
+    @VisibleForTesting RestrictedLockUtils.EnforcedAdmin mAdmin;
 
-    @VisibleForTesting
-    FooterPreference mDisableOptionsPreference;
+    @VisibleForTesting FooterPreference mDisableOptionsPreference;
 
     @VisibleForTesting
     FooterPreference mPowerConsumptionPreference;
@@ -101,16 +101,14 @@
     @VisibleForTesting
     AdaptiveSleepCameraStatePreferenceController mAdaptiveSleepCameraStatePreferenceController;
 
-    @VisibleForTesting
-    AdaptiveSleepPreferenceController mAdaptiveSleepController;
+    @VisibleForTesting AdaptiveSleepPreferenceController mAdaptiveSleepController;
 
     @VisibleForTesting
     AdaptiveSleepBatterySaverPreferenceController mAdaptiveSleepBatterySaverPreferenceController;
 
     public ScreenTimeoutSettings() {
         super();
-        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory()
-                .getMetricsFeatureProvider();
+        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
     }
 
     @Override
@@ -121,8 +119,8 @@
         mInitialEntries = getResources().getStringArray(R.array.screen_timeout_entries);
         mInitialValues = getResources().getStringArray(R.array.screen_timeout_values);
         mAdaptiveSleepController = new AdaptiveSleepPreferenceController(context);
-        mAdaptiveSleepPermissionController = new AdaptiveSleepPermissionPreferenceController(
-                context);
+        mAdaptiveSleepPermissionController =
+                new AdaptiveSleepPermissionPreferenceController(context);
         mAdaptiveSleepCameraStatePreferenceController =
                 new AdaptiveSleepCameraStatePreferenceController(context, getLifecycle());
         mAdaptiveSleepBatterySaverPreferenceController =
@@ -144,8 +142,9 @@
         if (mInitialValues != null) {
             for (int i = 0; i < mInitialValues.length; ++i) {
                 if (Long.parseLong(mInitialValues[i].toString()) <= maxTimeout) {
-                    candidates.add(new TimeoutCandidateInfo(mInitialEntries[i],
-                            mInitialValues[i].toString(), true));
+                    candidates.add(
+                            new TimeoutCandidateInfo(
+                                    mInitialEntries[i], mInitialValues[i].toString(), true));
                 }
             }
         } else {
@@ -161,9 +160,10 @@
         mAdaptiveSleepCameraStatePreferenceController.updateVisibility();
         mAdaptiveSleepBatterySaverPreferenceController.updateVisibility();
         mAdaptiveSleepController.updatePreference();
-        mContext.registerReceiver(mReceiver,
-                new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+        mContext.registerReceiver(
+                mReceiver, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
         mPrivacyManager.addSensorPrivacyListener(CAMERA, mPrivacyChangedListener);
+        mIsUserAuthenticated = false;
     }
 
     @Override
@@ -185,19 +185,21 @@
         }
 
         for (CandidateInfo info : candidateList) {
-            SelectorWithWidgetPreference pref =
-                    new SelectorWithWidgetPreference(getPrefContext());
+            ProtectedSelectorWithWidgetPreference pref =
+                    new ProtectedSelectorWithWidgetPreference(
+                            getPrefContext(), info.getKey(), this);
             bindPreference(pref, info.getKey(), info, defaultKey);
             screen.addPreference(pref);
         }
 
-        final long selectedTimeout = Long.parseLong(defaultKey);
+        final long selectedTimeout = getTimeoutFromKey(defaultKey);
         final long maxTimeout = getMaxScreenTimeout(getContext());
         if (!candidateList.isEmpty() && (selectedTimeout > maxTimeout)) {
             // The selected time out value is longer than the max timeout allowed by the admin.
             // Select the largest value from the list by default.
-            final SelectorWithWidgetPreference preferenceWithLargestTimeout =
-                    (SelectorWithWidgetPreference) screen.getPreference(candidateList.size() - 1);
+            final ProtectedSelectorWithWidgetPreference preferenceWithLargestTimeout =
+                    (ProtectedSelectorWithWidgetPreference)
+                            screen.getPreference(candidateList.size() - 1);
             preferenceWithLargestTimeout.setChecked(true);
         }
 
@@ -225,20 +227,34 @@
         }
     }
 
+    boolean isUserAuthenticated() {
+        return mIsUserAuthenticated;
+    }
+
+    void setUserAuthenticated(boolean isUserAuthenticated) {
+        mIsUserAuthenticated = isUserAuthenticated;
+    }
+
     @VisibleForTesting
     void setupDisabledFooterPreference() {
-        final String textDisabledByAdmin = mDevicePolicyManager.getResources().getString(
-                OTHER_OPTIONS_DISABLED_BY_ADMIN, () -> getResources().getString(
-                        R.string.admin_disabled_other_options));
+        final String textDisabledByAdmin =
+                mDevicePolicyManager
+                        .getResources()
+                        .getString(
+                                OTHER_OPTIONS_DISABLED_BY_ADMIN,
+                                () ->
+                                        getResources()
+                                                .getString(R.string.admin_disabled_other_options));
         final String textMoreDetails = getResources().getString(R.string.admin_more_details);
 
         mDisableOptionsPreference = new FooterPreference(getContext());
         mDisableOptionsPreference.setTitle(textDisabledByAdmin);
         mDisableOptionsPreference.setSelectable(false);
         mDisableOptionsPreference.setLearnMoreText(textMoreDetails);
-        mDisableOptionsPreference.setLearnMoreAction(v -> {
-            RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), mAdmin);
-        });
+        mDisableOptionsPreference.setLearnMoreAction(
+                v -> {
+                    RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), mAdmin);
+                });
         mDisableOptionsPreference.setIcon(R.drawable.ic_info_outline_24dp);
 
         // The 'disabled by admin' preference should always be at the end of the setting page.
@@ -303,17 +319,20 @@
         if (context == null) {
             return Long.toString(FALLBACK_SCREEN_TIMEOUT_VALUE);
         } else {
-            return Long.toString(Settings.System.getLong(context.getContentResolver(),
-                    SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE));
+            return Long.toString(
+                    Settings.System.getLong(
+                            context.getContentResolver(),
+                            SCREEN_OFF_TIMEOUT,
+                            FALLBACK_SCREEN_TIMEOUT_VALUE));
         }
     }
 
     private void setCurrentSystemScreenTimeout(Context context, String key) {
         try {
             if (context != null) {
-                final long value = Long.parseLong(key);
-                mMetricsFeatureProvider.action(context, SettingsEnums.ACTION_SCREEN_TIMEOUT_CHANGED,
-                        (int) value);
+                final long value = getTimeoutFromKey(key);
+                mMetricsFeatureProvider.action(
+                        context, SettingsEnums.ACTION_SCREEN_TIMEOUT_CHANGED, (int) value);
                 Settings.System.putLong(context.getContentResolver(), SCREEN_OFF_TIMEOUT, value);
             }
         } catch (NumberFormatException e) {
@@ -325,7 +344,12 @@
         return AdaptiveSleepPreferenceController.isAdaptiveSleepSupported(context);
     }
 
-    private static class TimeoutCandidateInfo extends CandidateInfo {
+    private static long getTimeoutFromKey(String key) {
+        return Long.parseLong(key);
+    }
+
+    @VisibleForTesting
+    static class TimeoutCandidateInfo extends CandidateInfo {
         private final CharSequence mLabel;
         private final String mKey;
 
@@ -351,10 +375,42 @@
         }
     }
 
+    @VisibleForTesting
+    static class ProtectedSelectorWithWidgetPreference
+            extends SelectorWithWidgetPreference {
+
+        private final long mTimeoutMs;
+        private final ScreenTimeoutSettings mScreenTimeoutSettings;
+
+        ProtectedSelectorWithWidgetPreference(
+                Context context, String key, ScreenTimeoutSettings screenTimeoutSettings) {
+            super(context);
+            mTimeoutMs = getTimeoutFromKey(key);
+            mScreenTimeoutSettings = screenTimeoutSettings;
+        }
+
+        @Override
+        public void onClick() {
+            if (Flags.protectScreenTimeoutWithAuth()
+                    && !mScreenTimeoutSettings.isUserAuthenticated()
+                    && !isChecked()
+                    && mTimeoutMs > getTimeoutFromKey(mScreenTimeoutSettings.getDefaultKey())) {
+                WifiDppUtils.showLockScreen(
+                        getContext(),
+                        () -> {
+                            mScreenTimeoutSettings.setUserAuthenticated(true);
+                            super.onClick();
+                        });
+            } else {
+                super.onClick();
+            }
+        }
+    }
+
     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider(R.xml.screen_timeout_settings) {
-                public List<SearchIndexableRaw> getRawDataToIndex(Context context,
-                        boolean enabled) {
+                public List<SearchIndexableRaw> getRawDataToIndex(
+                        Context context, boolean enabled) {
                     if (!isScreenAttentionAvailable(context)) {
                         return null;
                     }
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
index b5ec522..047bf13 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
@@ -83,17 +83,8 @@
         }
 
         cardPreference.setSelectable(false);
-        cardPreference.setPrimaryButtonText(
-                context.getString(R.string.battery_tip_charge_to_full_button));
+        cardPreference.setPrimaryButtonText(context.getString(R.string.learn_more));
         cardPreference.setPrimaryButtonClickListener(
-                unused -> {
-                    resumeCharging(context);
-                    preference.setVisible(false);
-                });
-        cardPreference.setPrimaryButtonVisible(mIsPluggedIn);
-
-        cardPreference.setSecondaryButtonText(context.getString(R.string.learn_more));
-        cardPreference.setSecondaryButtonClickListener(
                 button ->
                         button.startActivityForResult(
                                 HelpUtils.getHelpIntent(
@@ -101,10 +92,19 @@
                                         context.getString(R.string.help_url_battery_defender),
                                         /* backupContext */ ""), /* requestCode */
                                 0));
-        cardPreference.setSecondaryButtonVisible(true);
-        cardPreference.setSecondaryButtonContentDescription(
+        cardPreference.setPrimaryButtonVisible(true);
+        cardPreference.setPrimaryButtonContentDescription(
                 context.getString(
                         R.string.battery_tip_limited_temporarily_sec_button_content_description));
+
+        cardPreference.setSecondaryButtonText(
+                context.getString(R.string.battery_tip_charge_to_full_button));
+        cardPreference.setSecondaryButtonClickListener(
+                unused -> {
+                    resumeCharging(context);
+                    preference.setVisible(false);
+                });
+        cardPreference.setSecondaryButtonVisible(mIsPluggedIn);
     }
 
     private void resumeCharging(Context context) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
index dd48483..e407c63 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
@@ -26,7 +26,6 @@
 import com.android.settings.core.instrumentation.ElapsedTimeUtils;
 import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
 import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
-import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.fuelgauge.BatteryUtils;
 
 import java.time.Duration;
@@ -34,9 +33,7 @@
 /** Receives broadcasts to start or stop the periodic fetching job. */
 public final class BootBroadcastReceiver extends BroadcastReceiver {
     private static final String TAG = "BootBroadcastReceiver";
-    private static final long RESCHEDULE_FOR_BOOT_ACTION_WITH_DELAY =
-            Duration.ofMinutes(40).toMillis();
-    private static final long RESCHEDULE_FOR_BOOT_ACTION_WITHOUT_DELAY =
+    private static final long RESCHEDULE_FOR_BOOT_ACTION_DELAY_MILLIS =
             Duration.ofSeconds(6).toMillis();
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -71,7 +68,7 @@
                 break;
             case Intent.ACTION_TIME_CHANGED:
                 Log.d(TAG, "refresh job and clear all data from action=" + action);
-                DatabaseUtils.clearDataAfterTimeChangedIfNeeded(context);
+                DatabaseUtils.clearDataAfterTimeChangedIfNeeded(context, intent);
                 break;
             default:
                 Log.w(TAG, "receive unsupported action=" + action);
@@ -81,7 +78,7 @@
         if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
             final Intent recheckIntent = new Intent(ACTION_PERIODIC_JOB_RECHECK);
             recheckIntent.setClass(context, BootBroadcastReceiver.class);
-            final long delayedTime = getRescheduleTimeForBootAction(context);
+            final long delayedTime = RESCHEDULE_FOR_BOOT_ACTION_DELAY_MILLIS;
             mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), delayedTime);
 
             // Refreshes the usage source from UsageStatsManager when booting.
@@ -93,16 +90,6 @@
         }
     }
 
-    private long getRescheduleTimeForBootAction(Context context) {
-        final boolean delayHourlyJobWhenBooting =
-                FeatureFactory.getFeatureFactory()
-                        .getPowerUsageFeatureProvider()
-                        .delayHourlyJobWhenBooting();
-        return delayHourlyJobWhenBooting
-                ? RESCHEDULE_FOR_BOOT_ACTION_WITH_DELAY
-                : RESCHEDULE_FOR_BOOT_ACTION_WITHOUT_DELAY;
-    }
-
     private static void refreshJobs(Context context) {
         PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ true);
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index ee0e449..d489252 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.fuelgauge.batteryusage;
 
+import static android.content.Intent.FLAG_RECEIVER_REPLACE_PENDING;
+
 import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging;
 
 import android.app.usage.IUsageStatsManager;
@@ -150,6 +152,7 @@
                     .authority(AUTHORITY)
                     .appendPath(BATTERY_USAGE_SLOT_TABLE)
                     .build();
+
     /** A list of level record event types to access battery usage data. */
     public static final List<BatteryEventType> BATTERY_LEVEL_RECORD_EVENTS =
             List.of(BatteryEventType.FULL_CHARGED, BatteryEventType.EVEN_HOUR);
@@ -454,32 +457,58 @@
     }
 
     /** Clears all data and jobs if current timestamp is out of the range of last recorded job. */
-    public static void clearDataAfterTimeChangedIfNeeded(Context context) {
-        AsyncTask.execute(() -> {
-            try {
-                final List<BatteryEvent> batteryLevelRecordEvents =
-                        DatabaseUtils.getBatteryEvents(context, Calendar.getInstance(),
-                                getLastFullChargeTime(context), BATTERY_LEVEL_RECORD_EVENTS);
-                final long lastRecordTimestamp = batteryLevelRecordEvents.isEmpty()
-                        ? INVALID_TIMESTAMP : batteryLevelRecordEvents.get(0).getTimestamp();
-                final long nextRecordTimestamp =
-                        TimestampUtils.getNextEvenHourTimestamp(lastRecordTimestamp);
-                final long currentTime = System.currentTimeMillis();
-                final boolean isOutOfTimeRange = lastRecordTimestamp == INVALID_TIMESTAMP
-                        || currentTime < lastRecordTimestamp || currentTime > nextRecordTimestamp;
-                final String logInfo = String.format(Locale.ENGLISH,
-                        "clear database = %b, current time = %d, last record time = %d",
-                        isOutOfTimeRange, currentTime, lastRecordTimestamp);
-                Log.d(TAG, logInfo);
-                BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo);
-                if (isOutOfTimeRange) {
-                    DatabaseUtils.clearAll(context);
-                    PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ false);
-                }
-            } catch (RuntimeException e) {
-                Log.e(TAG, "refreshDataAndJobIfNeededAfterTimeChanged() failed", e);
-            }
-        });
+    public static void clearDataAfterTimeChangedIfNeeded(Context context, Intent intent) {
+        AsyncTask.execute(
+                () -> {
+                    try {
+                        if ((intent.getFlags() & FLAG_RECEIVER_REPLACE_PENDING) != 0) {
+                            BatteryUsageLogUtils.writeLog(
+                                    context,
+                                    Action.TIME_UPDATED,
+                                    "Database is not cleared because the time change intent is only"
+                                            + " for the existing pending receiver.");
+                            return;
+                        }
+                        final List<BatteryEvent> batteryLevelRecordEvents =
+                                DatabaseUtils.getBatteryEvents(
+                                        context,
+                                        Calendar.getInstance(),
+                                        getLastFullChargeTime(context),
+                                        BATTERY_LEVEL_RECORD_EVENTS);
+                        final long lastRecordTimestamp =
+                                batteryLevelRecordEvents.isEmpty()
+                                        ? INVALID_TIMESTAMP
+                                        : batteryLevelRecordEvents.get(0).getTimestamp();
+                        final long nextRecordTimestamp =
+                                TimestampUtils.getNextEvenHourTimestamp(lastRecordTimestamp);
+                        final long currentTime = System.currentTimeMillis();
+                        final boolean isOutOfTimeRange =
+                                lastRecordTimestamp == INVALID_TIMESTAMP
+                                        || currentTime < lastRecordTimestamp
+                                        || currentTime > nextRecordTimestamp;
+                        final String logInfo =
+                                String.format(
+                                        Locale.ENGLISH,
+                                        "clear database = %b, current time = %d, "
+                                                + "last record time = %d",
+                                        isOutOfTimeRange,
+                                        currentTime,
+                                        lastRecordTimestamp);
+                        Log.d(TAG, logInfo);
+                        BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo);
+                        if (isOutOfTimeRange) {
+                            DatabaseUtils.clearAll(context);
+                            PeriodicJobManager.getInstance(context)
+                                    .refreshJob(/* fromBoot= */ false);
+                        }
+                    } catch (RuntimeException e) {
+                        Log.e(TAG, "refreshDataAndJobIfNeededAfterTimeChanged() failed", e);
+                        BatteryUsageLogUtils.writeLog(
+                                context,
+                                Action.TIME_UPDATED,
+                                "refreshDataAndJobIfNeededAfterTimeChanged() failed" + e);
+                    }
+                });
     }
 
     /** Returns the timestamp for 00:00 6 days before the calendar date. */
diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
index 0e95840..38de93e 100644
--- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
@@ -24,7 +24,9 @@
 import android.database.ContentObserver;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
 import android.hardware.input.KeyboardLayout;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -65,10 +67,19 @@
         KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener {
 
     private static final String KEYBOARD_OPTIONS_CATEGORY = "keyboard_options_category";
+    private static final String KEYBOARD_A11Y_CATEGORY = "keyboard_a11y_category";
     private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch";
+    private static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
+    private static final String ACCESSIBILITY_STICKY_KEYS = "accessibility_sticky_keys";
     private static final String KEYBOARD_SHORTCUTS_HELPER = "keyboard_shortcuts_helper";
     private static final String MODIFIER_KEYS_SETTINGS = "modifier_keys_settings";
     private static final String EXTRA_AUTO_SELECTION = "auto_selection";
+    private static final Uri sVirtualKeyboardSettingsUri = Secure.getUriFor(
+            Secure.SHOW_IME_WITH_HARD_KEYBOARD);
+    private static final Uri sAccessibilityBounceKeysUri = Secure.getUriFor(
+            Secure.ACCESSIBILITY_BOUNCE_KEYS);
+    private static final Uri sAccessibilityStickyKeysUri = Secure.getUriFor(
+            Secure.ACCESSIBILITY_STICKY_KEYS);
 
     @NonNull
     private final ArrayList<HardKeyboardDeviceInfo> mLastHardKeyboards = new ArrayList<>();
@@ -80,7 +91,14 @@
     @NonNull
     private PreferenceCategory mKeyboardAssistanceCategory;
     @Nullable
+    private PreferenceCategory mKeyboardA11yCategory = null;
+    @Nullable
     private TwoStatePreference mShowVirtualKeyboardSwitch = null;
+    @Nullable
+    private TwoStatePreference mAccessibilityBounceKeys = null;
+    @Nullable
+    private TwoStatePreference mAccessibilityStickyKeys = null;
+
 
     private Intent mIntentWaitingForResult;
     private boolean mIsNewKeyboardSettings;
@@ -102,10 +120,15 @@
         mIm = Preconditions.checkNotNull(activity.getSystemService(InputManager.class));
         mImm = Preconditions.checkNotNull(activity.getSystemService(InputMethodManager.class));
         mKeyboardAssistanceCategory = Preconditions.checkNotNull(
-                (PreferenceCategory) findPreference(KEYBOARD_OPTIONS_CATEGORY));
-        mShowVirtualKeyboardSwitch = Preconditions.checkNotNull(
-                (TwoStatePreference) mKeyboardAssistanceCategory.findPreference(
-                        SHOW_VIRTUAL_KEYBOARD_SWITCH));
+                findPreference(KEYBOARD_OPTIONS_CATEGORY));
+        mShowVirtualKeyboardSwitch = Objects.requireNonNull(
+                mKeyboardAssistanceCategory.findPreference(SHOW_VIRTUAL_KEYBOARD_SWITCH));
+
+        mKeyboardA11yCategory = Objects.requireNonNull(findPreference(KEYBOARD_A11Y_CATEGORY));
+        mAccessibilityBounceKeys = Objects.requireNonNull(
+                mKeyboardA11yCategory.findPreference(ACCESSIBILITY_BOUNCE_KEYS));
+        mAccessibilityStickyKeys = Objects.requireNonNull(
+                mKeyboardA11yCategory.findPreference(ACCESSIBILITY_STICKY_KEYS));
 
         FeatureFactory featureFactory = FeatureFactory.getFeatureFactory();
         mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider();
@@ -121,6 +144,12 @@
         if (!isModifierKeySettingsEnabled) {
             mKeyboardAssistanceCategory.removePreference(findPreference(MODIFIER_KEYS_SETTINGS));
         }
+        if (!InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+            mKeyboardA11yCategory.removePreference(mAccessibilityBounceKeys);
+        }
+        if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+            mKeyboardA11yCategory.removePreference(mAccessibilityStickyKeys);
+        }
         InputDeviceIdentifier inputDeviceIdentifier = activity.getIntent().getParcelableExtra(
                 KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER);
         int intentFromWhere =
@@ -161,9 +190,13 @@
         mLastHardKeyboards.clear();
         scheduleUpdateHardKeyboards();
         mIm.registerInputDeviceListener(this, null);
-        mShowVirtualKeyboardSwitch.setOnPreferenceChangeListener(
+        Objects.requireNonNull(mShowVirtualKeyboardSwitch).setOnPreferenceChangeListener(
                 mShowVirtualKeyboardSwitchPreferenceChangeListener);
-        registerShowVirtualKeyboardSettingsObserver();
+        Objects.requireNonNull(mAccessibilityBounceKeys).setOnPreferenceChangeListener(
+                mAccessibilityBounceKeysSwitchPreferenceChangeListener);
+        Objects.requireNonNull(mAccessibilityStickyKeys).setOnPreferenceChangeListener(
+                mAccessibilityStickyKeysSwitchPreferenceChangeListener);
+        registerSettingsObserver();
     }
 
     @Override
@@ -171,8 +204,10 @@
         super.onPause();
         mLastHardKeyboards.clear();
         mIm.unregisterInputDeviceListener(this);
-        mShowVirtualKeyboardSwitch.setOnPreferenceChangeListener(null);
-        unregisterShowVirtualKeyboardSettingsObserver();
+        Objects.requireNonNull(mShowVirtualKeyboardSwitch).setOnPreferenceChangeListener(null);
+        Objects.requireNonNull(mAccessibilityBounceKeys).setOnPreferenceChangeListener(null);
+        Objects.requireNonNull(mAccessibilityStickyKeys).setOnPreferenceChangeListener(null);
+        unregisterSettingsObserver();
     }
 
     @Override
@@ -276,6 +311,14 @@
             mFeatureProvider.addFirmwareUpdateCategory(getPrefContext(), preferenceScreen);
         }
         updateShowVirtualKeyboardSwitch();
+
+        if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
+                || InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+            Objects.requireNonNull(mKeyboardA11yCategory).setOrder(2);
+            preferenceScreen.addPreference(mKeyboardA11yCategory);
+            updateAccessibilityBounceKeysSwitch();
+            updateAccessibilityStickyKeysSwitch();
+        }
     }
 
     private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
@@ -296,25 +339,58 @@
                 .launch();
     }
 
-    private void registerShowVirtualKeyboardSettingsObserver() {
-        unregisterShowVirtualKeyboardSettingsObserver();
-        getActivity().getContentResolver().registerContentObserver(
-                Secure.getUriFor(Secure.SHOW_IME_WITH_HARD_KEYBOARD),
+    private void registerSettingsObserver() {
+        unregisterSettingsObserver();
+        ContentResolver contentResolver = getActivity().getContentResolver();
+        contentResolver.registerContentObserver(
+                sVirtualKeyboardSettingsUri,
                 false,
                 mContentObserver,
                 UserHandle.myUserId());
+        if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+            contentResolver.registerContentObserver(
+                    sAccessibilityBounceKeysUri,
+                    false,
+                    mContentObserver,
+                    UserHandle.myUserId());
+        }
+        if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+            contentResolver.registerContentObserver(
+                    sAccessibilityStickyKeysUri,
+                    false,
+                    mContentObserver,
+                    UserHandle.myUserId());
+        }
         updateShowVirtualKeyboardSwitch();
+        updateAccessibilityBounceKeysSwitch();
+        updateAccessibilityStickyKeysSwitch();
     }
 
-    private void unregisterShowVirtualKeyboardSettingsObserver() {
+    private void unregisterSettingsObserver() {
         getActivity().getContentResolver().unregisterContentObserver(mContentObserver);
     }
 
     private void updateShowVirtualKeyboardSwitch() {
-        mShowVirtualKeyboardSwitch.setChecked(
+        Objects.requireNonNull(mShowVirtualKeyboardSwitch).setChecked(
                 Secure.getInt(getContentResolver(), Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0);
     }
 
+    private void updateAccessibilityBounceKeysSwitch() {
+        if (!InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+            return;
+        }
+        Objects.requireNonNull(mAccessibilityBounceKeys).setChecked(
+                InputSettings.isAccessibilityBounceKeysEnabled(getContext()));
+    }
+
+    private void updateAccessibilityStickyKeysSwitch() {
+        if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+            return;
+        }
+        Objects.requireNonNull(mAccessibilityStickyKeys).setChecked(
+                InputSettings.isAccessibilityStickyKeysEnabled(getContext()));
+    }
+
     private void toggleKeyboardShortcutsMenu() {
         getActivity().requestShowKeyboardShortcuts();
     }
@@ -328,10 +404,29 @@
                 return true;
             };
 
+    private final OnPreferenceChangeListener
+            mAccessibilityBounceKeysSwitchPreferenceChangeListener = (preference, newValue) -> {
+                InputSettings.setAccessibilityBounceKeysThreshold(getContext(),
+                        ((Boolean) newValue) ? 500 : 0);
+                return true;
+            };
+
+    private final OnPreferenceChangeListener
+            mAccessibilityStickyKeysSwitchPreferenceChangeListener = (preference, newValue) -> {
+                InputSettings.setAccessibilityStickyKeysEnabled(getContext(), (Boolean) newValue);
+                return true;
+            };
+
     private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) {
         @Override
-        public void onChange(boolean selfChange) {
-            updateShowVirtualKeyboardSwitch();
+        public void onChange(boolean selfChange, Uri uri) {
+            if (sVirtualKeyboardSettingsUri.equals(uri)) {
+                updateShowVirtualKeyboardSwitch();
+            } else if (sAccessibilityBounceKeysUri.equals(uri)) {
+                updateAccessibilityBounceKeysSwitch();
+            } else if (sAccessibilityStickyKeysUri.equals(uri)) {
+                updateAccessibilityStickyKeysSwitch();
+            }
         }
     };
 
diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
index c0f3adc..b284c8d 100644
--- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java
+++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
@@ -158,13 +158,14 @@
 
     private void broadcastAppLocaleChange(LocaleStore.LocaleInfo localeInfo) {
         if (!localeNotificationEnabled()) {
+            Log.w(TAG, "Locale notification is not enabled");
             return;
         }
-        String localeTag = localeInfo.getLocale().toLanguageTag();
-        if (LocaleUtils.isInSystemLocale(localeTag) || localeInfo.isAppCurrentLocale()) {
+        if (localeInfo.isAppCurrentLocale()) {
             return;
         }
         try {
+            String localeTag = localeInfo.getLocale().toLanguageTag();
             int uid = getPackageManager().getApplicationInfo(mPackageName,
                     PackageManager.GET_META_DATA).uid;
             boolean launchNotification = mNotificationController.shouldTriggerNotification(
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index 1b3a4f2..24d9927 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -270,12 +270,14 @@
     void removeChecked() {
         int itemCount = mFeedItemList.size();
         LocaleStore.LocaleInfo localeInfo;
+        NotificationController controller = NotificationController.getInstance(mContext);
         for (int i = itemCount - 1; i >= 0; i--) {
             localeInfo = mFeedItemList.get(i);
             if (localeInfo.getChecked()) {
                 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
                         .action(mContext, SettingsEnums.ACTION_REMOVE_LANGUAGE);
                 mFeedItemList.remove(i);
+                controller.removeNotificationInfo(localeInfo.getLocale().toLanguageTag());
             }
         }
         notifyDataSetChanged();
diff --git a/src/com/android/settings/localepicker/LocaleNotificationDataManager.java b/src/com/android/settings/localepicker/LocaleNotificationDataManager.java
index 4d948f1..0e89366 100644
--- a/src/com/android/settings/localepicker/LocaleNotificationDataManager.java
+++ b/src/com/android/settings/localepicker/LocaleNotificationDataManager.java
@@ -63,6 +63,17 @@
     }
 
     /**
+     * Removes one entry with the corresponding locale from the {@link SharedPreferences}.
+     *
+     * @param locale A locale which the application sets to
+     */
+    public void removeNotificationInfo(String locale) {
+        SharedPreferences.Editor editor = getSharedPreferences(mContext).edit();
+        editor.remove(locale);
+        editor.apply();
+    }
+
+    /**
      * Gets the {@link NotificationInfo} with the associated locale from the
      * {@link SharedPreferences}.
      *
diff --git a/src/com/android/settings/localepicker/NotificationController.java b/src/com/android/settings/localepicker/NotificationController.java
index 2d36189..4096705 100644
--- a/src/com/android/settings/localepicker/NotificationController.java
+++ b/src/com/android/settings/localepicker/NotificationController.java
@@ -110,6 +110,15 @@
         return (info != null) ? info.getNotificationId() : -1;
     }
 
+    /**
+     * Remove the {@link NotificationInfo} with the corresponding locale
+     *
+     * @param locale The locale which the application sets to
+     */
+    public void removeNotificationInfo(@NonNull String locale) {
+        mDataManager.removeNotificationInfo(locale);
+    }
+
     private boolean updateLocaleNotificationInfo(int uid, String locale) {
         NotificationInfo info = mDataManager.getNotificationInfo(locale);
         if (info == null) {
@@ -135,20 +144,20 @@
         int notificationCount = info.getNotificationCount();
         long lastNotificationTime = info.getLastNotificationTimeMs();
         int notificationId = info.getNotificationId();
-
-        // Add the uid into the locale's uid list
-        uidSet.add(uid);
         if (dismissCount < DISMISS_COUNT_THRESHOLD
-                && notificationCount < NOTIFICATION_COUNT_THRESHOLD
-                // Notification should fire on multiples of 2 apps using the locale.
-                && uidSet.size() % MULTIPLE_BASE == 0
-                && !isNotificationFrequent(lastNotificationTime)) {
-            // Increment the count because the notification can be triggered.
-            notificationCount = info.getNotificationCount() + 1;
-            lastNotificationTime = Calendar.getInstance().getTimeInMillis();
-            Log.i(TAG, "notificationCount:" + notificationCount);
-            if (notificationCount == 1) {
-                notificationId = (int) SystemClock.uptimeMillis();
+                && notificationCount < NOTIFICATION_COUNT_THRESHOLD) {
+            // Add the uid into the locale's uid list
+            uidSet.add(uid);
+            // Notification should fire on multiples of 2 apps using the locale.
+            if (uidSet.size() % MULTIPLE_BASE == 0
+                    && !isNotificationFrequent(lastNotificationTime)) {
+                // Increment the count because the notification can be triggered.
+                notificationCount = info.getNotificationCount() + 1;
+                lastNotificationTime = Calendar.getInstance().getTimeInMillis();
+                Log.i(TAG, "notificationCount:" + notificationCount);
+                if (notificationCount == 1) {
+                    notificationId = (int) SystemClock.uptimeMillis();
+                }
             }
         }
         return new NotificationInfo(uidSet, notificationCount, dismissCount, lastNotificationTime,
diff --git a/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java b/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java
deleted file mode 100644
index f836415..0000000
--- a/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 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.network.telephony;
-
-import android.content.Context;
-
-import com.android.settings.widget.PreferenceCategoryController;
-
-/**
- * Preference controller for "Calling" category
- */
-public class CallingPreferenceCategoryController extends PreferenceCategoryController {
-
-    public CallingPreferenceCategoryController(Context context, String key) {
-        super(context, key);
-    }
-}
diff --git a/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.kt b/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.kt
new file mode 100644
index 0000000..5356a41
--- /dev/null
+++ b/src/com/android/settings/network/telephony/CallingPreferenceCategoryController.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.Context
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.core.BasePreferenceController
+
+/**
+ * Preference controller for "Calling" category
+ */
+class CallingPreferenceCategoryController(context: Context, key: String) :
+    BasePreferenceController(context, key) {
+
+    private val visibleChildren = mutableSetOf<String>()
+    private var preference: Preference? = null
+
+    override fun getAvailabilityStatus() = AVAILABLE
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        // Not call super here, to avoid preference.isVisible changed unexpectedly
+        preference = screen.findPreference(preferenceKey)
+    }
+
+    fun updateChildVisible(key: String, isVisible: Boolean) {
+        if (isVisible) {
+            visibleChildren.add(key)
+        } else {
+            visibleChildren.remove(key)
+        }
+        preference?.isVisible = visibleChildren.isNotEmpty()
+    }
+}
diff --git a/src/com/android/settings/network/telephony/CellInfoUtil.java b/src/com/android/settings/network/telephony/CellInfoUtil.java
deleted file mode 100644
index 8889586..0000000
--- a/src/com/android/settings/network/telephony/CellInfoUtil.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2018 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.network.telephony;
-
-import android.telephony.CellIdentity;
-import android.telephony.CellIdentityGsm;
-import android.telephony.CellIdentityLte;
-import android.telephony.CellIdentityNr;
-import android.telephony.CellIdentityTdscdma;
-import android.telephony.CellIdentityWcdma;
-import android.telephony.CellInfo;
-import android.telephony.CellInfoCdma;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellInfoLte;
-import android.telephony.CellInfoNr;
-import android.telephony.CellInfoTdscdma;
-import android.telephony.CellInfoWcdma;
-import android.text.BidiFormatter;
-import android.text.TextDirectionHeuristics;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.OperatorInfo;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-/**
- * Add static Utility functions to get information from the CellInfo object.
- * TODO: Modify {@link CellInfo} for simplify those functions
- */
-public final class CellInfoUtil {
-    private static final String TAG = "NetworkSelectSetting";
-
-    private CellInfoUtil() {
-    }
-
-    /**
-     * Returns the title of the network obtained in the manual search.
-     *
-     * @param cellId contains the identity of the network.
-     * @param networkMccMnc contains the MCCMNC string of the network
-     * @return Long Name if not null/empty, otherwise Short Name if not null/empty,
-     * else MCCMNC string.
-     */
-    public static String getNetworkTitle(CellIdentity cellId, String networkMccMnc) {
-        if (cellId != null) {
-            String title = Objects.toString(cellId.getOperatorAlphaLong(), "");
-            if (TextUtils.isEmpty(title)) {
-                title = Objects.toString(cellId.getOperatorAlphaShort(), "");
-            }
-            if (!TextUtils.isEmpty(title)) {
-                return title;
-            }
-        }
-        if (TextUtils.isEmpty(networkMccMnc)) {
-            return "";
-        }
-        final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
-        return bidiFormatter.unicodeWrap(networkMccMnc, TextDirectionHeuristics.LTR);
-    }
-
-    /**
-     * Returns the CellIdentity from CellInfo
-     *
-     * @param cellInfo contains the information of the network.
-     * @return CellIdentity within CellInfo
-     */
-    public static CellIdentity getCellIdentity(CellInfo cellInfo) {
-        if (cellInfo == null) {
-            return null;
-        }
-        CellIdentity cellId = null;
-        if (cellInfo instanceof CellInfoGsm) {
-            cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
-        } else if (cellInfo instanceof CellInfoCdma) {
-            cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
-        } else if (cellInfo instanceof CellInfoWcdma) {
-            cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
-        } else if (cellInfo instanceof CellInfoTdscdma) {
-            cellId = ((CellInfoTdscdma) cellInfo).getCellIdentity();
-        } else if (cellInfo instanceof CellInfoLte) {
-            cellId = ((CellInfoLte) cellInfo).getCellIdentity();
-        } else if (cellInfo instanceof CellInfoNr) {
-            cellId = ((CellInfoNr) cellInfo).getCellIdentity();
-        }
-        return cellId;
-    }
-
-    /**
-     * Creates a CellInfo object from OperatorInfo. GsmCellInfo is used here only because
-     * operatorInfo does not contain technology type while CellInfo is an abstract object that
-     * requires to specify technology type. It doesn't matter which CellInfo type to use here, since
-     * we only want to wrap the operator info and PLMN to a CellInfo object.
-     */
-    public static CellInfo convertOperatorInfoToCellInfo(OperatorInfo operatorInfo) {
-        final String operatorNumeric = operatorInfo.getOperatorNumeric();
-        String mcc = null;
-        String mnc = null;
-        if (operatorNumeric != null && operatorNumeric.matches("^[0-9]{5,6}$")) {
-            mcc = operatorNumeric.substring(0, 3);
-            mnc = operatorNumeric.substring(3);
-        }
-        final CellIdentityGsm cig = new CellIdentityGsm(
-                Integer.MAX_VALUE /* lac */,
-                Integer.MAX_VALUE /* cid */,
-                Integer.MAX_VALUE /* arfcn */,
-                Integer.MAX_VALUE /* bsic */,
-                mcc,
-                mnc,
-                operatorInfo.getOperatorAlphaLong(),
-                operatorInfo.getOperatorAlphaShort(),
-                Collections.emptyList());
-
-        final CellInfoGsm ci = new CellInfoGsm();
-        ci.setCellIdentity(cig);
-        return ci;
-    }
-
-    /** Convert a list of cellInfos to readable string without sensitive info. */
-    public static String cellInfoListToString(List<CellInfo> cellInfos) {
-        return cellInfos.stream()
-                .map(cellInfo -> cellInfoToString(cellInfo))
-                .collect(Collectors.joining(", "));
-    }
-
-    /** Convert {@code cellInfo} to a readable string without sensitive info. */
-    public static String cellInfoToString(CellInfo cellInfo) {
-        final String cellType = cellInfo.getClass().getSimpleName();
-        final CellIdentity cid = getCellIdentity(cellInfo);
-        String mcc = getCellIdentityMcc(cid);
-        String mnc = getCellIdentityMnc(cid);
-        CharSequence alphaLong = null;
-        CharSequence alphaShort = null;
-        if (cid != null) {
-            alphaLong = cid.getOperatorAlphaLong();
-            alphaShort = cid.getOperatorAlphaShort();
-        }
-        return String.format(
-                "{CellType = %s, isRegistered = %b, mcc = %s, mnc = %s, alphaL = %s, alphaS = %s}",
-                cellType, cellInfo.isRegistered(), mcc, mnc,
-                alphaLong, alphaShort);
-    }
-
-    /**
-     * Returns the MccMnc.
-     *
-     * @param cid contains the identity of the network.
-     * @return MccMnc string.
-     */
-    public static String getCellIdentityMccMnc(CellIdentity cid) {
-        String mcc = getCellIdentityMcc(cid);
-        String mnc = getCellIdentityMnc(cid);
-        return (mcc == null || mnc == null) ? null : mcc + mnc;
-    }
-
-    /**
-     * Returns the Mcc.
-     *
-     * @param cid contains the identity of the network.
-     * @return Mcc string.
-     */
-    public static String getCellIdentityMcc(CellIdentity cid) {
-        String mcc = null;
-        if (cid != null) {
-            if (cid instanceof CellIdentityGsm) {
-                mcc = ((CellIdentityGsm) cid).getMccString();
-            } else if (cid instanceof CellIdentityWcdma) {
-                mcc = ((CellIdentityWcdma) cid).getMccString();
-            } else if (cid instanceof CellIdentityTdscdma) {
-                mcc = ((CellIdentityTdscdma) cid).getMccString();
-            } else if (cid instanceof CellIdentityLte) {
-                mcc = ((CellIdentityLte) cid).getMccString();
-            } else if (cid instanceof CellIdentityNr) {
-                mcc = ((CellIdentityNr) cid).getMccString();
-            }
-        }
-        return (mcc == null) ? null : mcc;
-    }
-
-    /**
-     * Returns the Mnc.
-     *
-     * @param cid contains the identity of the network.
-     * @return Mcc string.
-     */
-    public static String getCellIdentityMnc(CellIdentity cid) {
-        String mnc = null;
-        if (cid != null) {
-            if (cid instanceof CellIdentityGsm) {
-                mnc = ((CellIdentityGsm) cid).getMncString();
-            } else if (cid instanceof CellIdentityWcdma) {
-                mnc = ((CellIdentityWcdma) cid).getMncString();
-            } else if (cid instanceof CellIdentityTdscdma) {
-                mnc = ((CellIdentityTdscdma) cid).getMncString();
-            } else if (cid instanceof CellIdentityLte) {
-                mnc = ((CellIdentityLte) cid).getMncString();
-            } else if (cid instanceof CellIdentityNr) {
-                mnc = ((CellIdentityNr) cid).getMncString();
-            }
-        }
-        return (mnc == null) ? null : mnc;
-    }
-}
diff --git a/src/com/android/settings/network/telephony/CellInfoUtil.kt b/src/com/android/settings/network/telephony/CellInfoUtil.kt
new file mode 100644
index 0000000..c7b6b24
--- /dev/null
+++ b/src/com/android/settings/network/telephony/CellInfoUtil.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.telephony.CellIdentity
+import android.telephony.CellIdentityGsm
+import android.telephony.CellInfo
+import android.telephony.CellInfoGsm
+import android.text.BidiFormatter
+import android.text.TextDirectionHeuristics
+import com.android.internal.telephony.OperatorInfo
+
+/**
+ * Add static Utility functions to get information from the CellInfo object.
+ * TODO: Modify [CellInfo] for simplify those functions
+ */
+object CellInfoUtil {
+
+    /**
+     * Returns the title of the network obtained in the manual search.
+     *
+     * By the following order,
+     * 1. Long Name if not null/empty
+     * 2. Short Name if not null/empty
+     * 3. OperatorNumeric (MCCMNC) string
+     */
+    @JvmStatic
+    fun CellIdentity.getNetworkTitle(): String? {
+        operatorAlphaLong?.takeIf { it.isNotBlank() }?.let { return it.toString() }
+        operatorAlphaShort?.takeIf { it.isNotBlank() }?.let { return it.toString() }
+        val operatorNumeric = getOperatorNumeric() ?: return null
+        val bidiFormatter = BidiFormatter.getInstance()
+        return bidiFormatter.unicodeWrap(operatorNumeric, TextDirectionHeuristics.LTR)
+    }
+
+    /**
+     * Creates a CellInfo object from OperatorInfo. GsmCellInfo is used here only because
+     * operatorInfo does not contain technology type while CellInfo is an abstract object that
+     * requires to specify technology type. It doesn't matter which CellInfo type to use here, since
+     * we only want to wrap the operator info and PLMN to a CellInfo object.
+     */
+    @JvmStatic
+    fun convertOperatorInfoToCellInfo(operatorInfo: OperatorInfo): CellInfo {
+        val operatorNumeric = operatorInfo.operatorNumeric
+        var mcc: String? = null
+        var mnc: String? = null
+        if (operatorNumeric?.matches("^[0-9]{5,6}$".toRegex()) == true) {
+            mcc = operatorNumeric.substring(0, 3)
+            mnc = operatorNumeric.substring(3)
+        }
+        return CellInfoGsm().apply {
+            cellIdentity = CellIdentityGsm(
+                /* lac = */ Int.MAX_VALUE,
+                /* cid = */ Int.MAX_VALUE,
+                /* arfcn = */ Int.MAX_VALUE,
+                /* bsic = */ Int.MAX_VALUE,
+                /* mccStr = */ mcc,
+                /* mncStr = */ mnc,
+                /* alphal = */ operatorInfo.operatorAlphaLong,
+                /* alphas = */ operatorInfo.operatorAlphaShort,
+                /* additionalPlmns = */ emptyList(),
+            )
+        }
+    }
+
+    /**
+     * Convert a list of cellInfos to readable string without sensitive info.
+     */
+    @JvmStatic
+    fun cellInfoListToString(cellInfos: List<CellInfo>): String =
+        cellInfos.joinToString { cellInfo -> cellInfo.readableString() }
+
+    /**
+     * Convert [CellInfo] to a readable string without sensitive info.
+     */
+    private fun CellInfo.readableString(): String = buildString {
+        append("{CellType = ${this@readableString::class.simpleName}, ")
+        append("isRegistered = $isRegistered, ")
+        append(cellIdentity.readableString())
+        append("}")
+    }
+
+    private fun CellIdentity.readableString(): String = buildString {
+        append("mcc = $mccString, ")
+        append("mnc = $mncString, ")
+        append("alphaL = $operatorAlphaLong, ")
+        append("alphaS = $operatorAlphaShort")
+    }
+
+    /**
+     * Returns the MccMnc.
+     */
+    @JvmStatic
+    fun CellIdentity.getOperatorNumeric(): String? {
+        val mcc = mccString
+        val mnc = mncString
+        return if (mcc == null || mnc == null) null else mcc + mnc
+    }
+}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index 0812ccc..16b04aa 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -269,8 +269,10 @@
         use(Enable2gPreferenceController.class).init(mSubId);
         use(CarrierWifiTogglePreferenceController.class).init(getLifecycle(), mSubId);
 
-        final WifiCallingPreferenceController wifiCallingPreferenceController =
-                use(WifiCallingPreferenceController.class).init(mSubId);
+        final CallingPreferenceCategoryController callingPreferenceCategoryController =
+                use(CallingPreferenceCategoryController.class);
+        use(WifiCallingPreferenceController.class)
+                .init(mSubId, callingPreferenceCategoryController);
 
         final OpenNetworkSelectPagePreferenceController openNetworkSelectPagePreferenceController =
                 use(OpenNetworkSelectPagePreferenceController.class).init(mSubId);
@@ -286,9 +288,8 @@
         mCdmaSubscriptionPreferenceController.init(getPreferenceManager(), mSubId);
 
         final VideoCallingPreferenceController videoCallingPreferenceController =
-                use(VideoCallingPreferenceController.class).init(mSubId);
-        use(CallingPreferenceCategoryController.class).setChildren(
-                Arrays.asList(wifiCallingPreferenceController, videoCallingPreferenceController));
+                use(VideoCallingPreferenceController.class)
+                        .init(mSubId, callingPreferenceCategoryController);
         use(Enhanced4gLtePreferenceController.class).init(mSubId)
                 .addListener(videoCallingPreferenceController);
         use(Enhanced4gCallingPreferenceController.class).init(mSubId)
diff --git a/src/com/android/settings/network/telephony/NetworkOperatorPreference.java b/src/com/android/settings/network/telephony/NetworkOperatorPreference.java
index 7404aa4..7a58433 100644
--- a/src/com/android/settings/network/telephony/NetworkOperatorPreference.java
+++ b/src/com/android/settings/network/telephony/NetworkOperatorPreference.java
@@ -18,14 +18,11 @@
 
 import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
 
+import static com.android.settings.network.telephony.CellInfoUtil.getOperatorNumeric;
+
 import android.content.Context;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CellIdentity;
-import android.telephony.CellIdentityGsm;
-import android.telephony.CellIdentityLte;
-import android.telephony.CellIdentityNr;
-import android.telephony.CellIdentityTdscdma;
-import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoCdma;
 import android.telephony.CellInfoGsm;
@@ -36,6 +33,7 @@
 import android.telephony.CellSignalStrength;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
@@ -87,7 +85,7 @@
      * Change cell information
      */
     public void updateCell(CellInfo cellinfo) {
-        updateCell(cellinfo, CellInfoUtil.getCellIdentity(cellinfo));
+        updateCell(cellinfo, cellinfo.getCellIdentity());
     }
 
     @VisibleForTesting
@@ -104,14 +102,14 @@
         if (cellinfo == null) {
             return false;
         }
-        return mCellId.equals(CellInfoUtil.getCellIdentity(cellinfo));
+        return mCellId.equals(cellinfo.getCellIdentity());
     }
 
     /**
      * Return true when this preference is for forbidden network
      */
     public boolean isForbiddenNetwork() {
-        return ((mForbiddenPlmns != null) && mForbiddenPlmns.contains(getOperatorNumeric()));
+        return ((mForbiddenPlmns != null) && mForbiddenPlmns.contains(getOperatorNumeric(mCellId)));
     }
 
     /**
@@ -148,40 +146,11 @@
     }
 
     /**
-     * Operator numeric of this cell
-     */
-    public String getOperatorNumeric() {
-        final CellIdentity cellId = mCellId;
-        if (cellId == null) {
-            return null;
-        }
-        if (cellId instanceof CellIdentityGsm) {
-            return ((CellIdentityGsm) cellId).getMobileNetworkOperator();
-        }
-        if (cellId instanceof CellIdentityWcdma) {
-            return ((CellIdentityWcdma) cellId).getMobileNetworkOperator();
-        }
-        if (cellId instanceof CellIdentityTdscdma) {
-            return ((CellIdentityTdscdma) cellId).getMobileNetworkOperator();
-        }
-        if (cellId instanceof CellIdentityLte) {
-            return ((CellIdentityLte) cellId).getMobileNetworkOperator();
-        }
-        if (cellId instanceof CellIdentityNr) {
-            final String mcc = ((CellIdentityNr) cellId).getMccString();
-            if (mcc == null) {
-                return null;
-            }
-            return mcc.concat(((CellIdentityNr) cellId).getMncString());
-        }
-        return null;
-    }
-
-    /**
      * Operator name of this cell
      */
+    @Nullable
     public String getOperatorName() {
-        return CellInfoUtil.getNetworkTitle(mCellId, getOperatorNumeric());
+        return CellInfoUtil.getNetworkTitle(mCellId);
     }
 
     /**
@@ -190,7 +159,7 @@
     public OperatorInfo getOperatorInfo() {
         return new OperatorInfo(Objects.toString(mCellId.getOperatorAlphaLong(), ""),
                 Objects.toString(mCellId.getOperatorAlphaShort(), ""),
-                getOperatorNumeric(), getAccessNetworkTypeFromCellInfo(mCellInfo));
+                getOperatorNumeric(mCellId), getAccessNetworkTypeFromCellInfo(mCellInfo));
     }
 
     private int getIconIdForCell(CellInfo ci) {
diff --git a/src/com/android/settings/network/telephony/NetworkSelectSettings.java b/src/com/android/settings/network/telephony/NetworkSelectSettings.java
index 1cfa043..243b629 100644
--- a/src/com/android/settings/network/telephony/NetworkSelectSettings.java
+++ b/src/com/android/settings/network/telephony/NetworkSelectSettings.java
@@ -365,14 +365,12 @@
         }
         ArrayList<CellInfo> aggregatedList = new ArrayList<>();
         for (CellInfo cellInfo : cellInfoListInput) {
-            String plmn = CellInfoUtil.getNetworkTitle(cellInfo.getCellIdentity(),
-                    CellInfoUtil.getCellIdentityMccMnc(cellInfo.getCellIdentity()));
+            String plmn = CellInfoUtil.getNetworkTitle(cellInfo.getCellIdentity());
             Class className = cellInfo.getClass();
 
             Optional<CellInfo> itemInTheList = aggregatedList.stream().filter(
                     item -> {
-                        String itemPlmn = CellInfoUtil.getNetworkTitle(item.getCellIdentity(),
-                                CellInfoUtil.getCellIdentityMccMnc(item.getCellIdentity()));
+                        String itemPlmn = CellInfoUtil.getNetworkTitle(item.getCellIdentity());
                         return itemPlmn.equals(plmn) && item.getClass().equals(className);
                     })
                     .findFirst();
diff --git a/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java b/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java
index 1519bf0..5810510 100644
--- a/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java
+++ b/src/com/android/settings/network/telephony/VideoCallingPreferenceController.java
@@ -54,6 +54,7 @@
     @VisibleForTesting
     Integer mCallState;
     private MobileDataEnabledListener mDataContentObserver;
+    private CallingPreferenceCategoryController mCallingPreferenceCategoryController;
 
     public VideoCallingPreferenceController(Context context, String key) {
         super(context, key);
@@ -97,6 +98,8 @@
         final TwoStatePreference switchPreference = (TwoStatePreference) preference;
         final boolean videoCallEnabled = isVideoCallEnabled(mSubId);
         switchPreference.setVisible(videoCallEnabled);
+        mCallingPreferenceCategoryController
+                .updateChildVisible(getPreferenceKey(), videoCallEnabled);
         if (videoCallEnabled) {
             final boolean videoCallEditable = queryVoLteState(mSubId).isEnabledByUser()
                     && queryImsState(mSubId).isAllowUserControl();
@@ -136,8 +139,13 @@
                 PackageManager.FEATURE_TELEPHONY_IMS);
     }
 
-    public VideoCallingPreferenceController init(int subId) {
+    /**
+     * Init instance of VideoCallingPreferenceController.
+     */
+    public VideoCallingPreferenceController init(
+            int subId, CallingPreferenceCategoryController callingPreferenceCategoryController) {
         mSubId = subId;
+        mCallingPreferenceCategoryController = callingPreferenceCategoryController;
 
         return this;
     }
diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java
deleted file mode 100644
index 5503e95..0000000
--- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2018 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.network.telephony;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.PersistableBundle;
-import android.provider.Settings;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyCallback;
-import android.telephony.TelephonyManager;
-import android.telephony.ims.ImsMmTelManager;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.network.ims.WifiCallingQueryImsState;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnStart;
-import com.android.settingslib.core.lifecycle.events.OnStop;
-
-import java.util.List;
-
-/**
- * Preference controller for "Wifi Calling"
- */
-//TODO: Remove the class once Provider Model is always enabled in the future.
-public class WifiCallingPreferenceController extends TelephonyBasePreferenceController implements
-        LifecycleObserver, OnStart, OnStop {
-
-    private static final String TAG = "WifiCallingPreference";
-
-    @VisibleForTesting
-    Integer mCallState;
-    @VisibleForTesting
-    CarrierConfigManager mCarrierConfigManager;
-    private ImsMmTelManager mImsMmTelManager;
-    @VisibleForTesting
-    PhoneAccountHandle mSimCallManager;
-    private PhoneTelephonyCallback mTelephonyCallback;
-    private Preference mPreference;
-    private boolean mHasException;
-
-    public WifiCallingPreferenceController(Context context, String key) {
-        super(context, key);
-        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
-        mTelephonyCallback = new PhoneTelephonyCallback();
-    }
-
-    @Override
-    public int getAvailabilityStatus(int subId) {
-        return SubscriptionManager.isValidSubscriptionId(subId)
-                && MobileNetworkUtils.isWifiCallingEnabled(mContext, subId, null)
-                ? AVAILABLE
-                : UNSUPPORTED_ON_DEVICE;
-    }
-
-    @Override
-    public void onStart() {
-        mTelephonyCallback.register(mContext, mSubId);
-    }
-
-    @Override
-    public void onStop() {
-        mTelephonyCallback.unregister();
-    }
-
-    @Override
-    public void displayPreference(PreferenceScreen screen) {
-        super.displayPreference(screen);
-        mPreference = screen.findPreference(getPreferenceKey());
-        final Intent intent = mPreference.getIntent();
-        if (intent != null) {
-            intent.putExtra(Settings.EXTRA_SUB_ID, mSubId);
-        }
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        super.updateState(preference);
-        if ((mCallState == null) || (preference == null)) {
-            Log.d(TAG, "Skip update under mCallState=" + mCallState);
-            return;
-        }
-        mHasException = false;
-        CharSequence summaryText = null;
-        if (mSimCallManager != null) {
-            final Intent intent = MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext,
-                    mSimCallManager);
-            if (intent == null) {
-                // Do nothing in this case since preference is invisible
-                return;
-            }
-            final PackageManager pm = mContext.getPackageManager();
-            final List<ResolveInfo> resolutions = pm.queryIntentActivities(intent, 0);
-            preference.setTitle(resolutions.get(0).loadLabel(pm));
-            preference.setIntent(intent);
-        } else {
-            final String title = SubscriptionManager.getResourcesForSubId(mContext, mSubId)
-                    .getString(R.string.wifi_calling_settings_title);
-            preference.setTitle(title);
-            summaryText = getResourceIdForWfcMode(mSubId);
-        }
-        preference.setSummary(summaryText);
-        preference.setEnabled(mCallState == TelephonyManager.CALL_STATE_IDLE && !mHasException);
-    }
-
-    private CharSequence getResourceIdForWfcMode(int subId) {
-        int resId = com.android.internal.R.string.wifi_calling_off_summary;
-        if (queryImsState(subId).isEnabledByUser()) {
-            boolean useWfcHomeModeForRoaming = false;
-            if (mCarrierConfigManager != null) {
-                final PersistableBundle carrierConfig =
-                        mCarrierConfigManager.getConfigForSubId(subId);
-                if (carrierConfig != null) {
-                    useWfcHomeModeForRoaming = carrierConfig.getBoolean(
-                            CarrierConfigManager
-                                    .KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL);
-                }
-            }
-            final boolean isRoaming = getTelephonyManager(mContext, subId)
-                    .isNetworkRoaming();
-            int wfcMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
-            try {
-                wfcMode = (isRoaming && !useWfcHomeModeForRoaming)
-                        ? mImsMmTelManager.getVoWiFiRoamingModeSetting() :
-                        mImsMmTelManager.getVoWiFiModeSetting();
-            } catch (IllegalArgumentException e) {
-                mHasException = true;
-                Log.e(TAG, "getResourceIdForWfcMode: Exception", e);
-            }
-
-            switch (wfcMode) {
-                case ImsMmTelManager.WIFI_MODE_WIFI_ONLY:
-                    resId = com.android.internal.R.string.wfc_mode_wifi_only_summary;
-                    break;
-                case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED:
-                    resId = com.android.internal.R.string
-                            .wfc_mode_cellular_preferred_summary;
-                    break;
-                case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED:
-                    resId = com.android.internal.R.string.wfc_mode_wifi_preferred_summary;
-                    break;
-                default:
-                    break;
-            }
-        }
-        return SubscriptionManager.getResourcesForSubId(mContext, subId).getText(resId);
-    }
-
-    public WifiCallingPreferenceController init(int subId) {
-        mSubId = subId;
-        mImsMmTelManager = getImsMmTelManager(mSubId);
-        mSimCallManager = mContext.getSystemService(TelecomManager.class)
-                .getSimCallManagerForSubscription(mSubId);
-
-        return this;
-    }
-
-    @VisibleForTesting
-    WifiCallingQueryImsState queryImsState(int subId) {
-        return new WifiCallingQueryImsState(mContext, subId);
-    }
-
-    protected ImsMmTelManager getImsMmTelManager(int subId) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            return null;
-        }
-        return ImsMmTelManager.createForSubscriptionId(subId);
-    }
-
-    @VisibleForTesting
-    TelephonyManager getTelephonyManager(Context context, int subId) {
-        final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            return telephonyMgr;
-        }
-        final TelephonyManager subscriptionTelephonyMgr =
-                telephonyMgr.createForSubscriptionId(subId);
-        return (subscriptionTelephonyMgr == null) ? telephonyMgr : subscriptionTelephonyMgr;
-    }
-
-
-    private class PhoneTelephonyCallback extends TelephonyCallback implements
-            TelephonyCallback.CallStateListener {
-
-        private TelephonyManager mTelephonyManager;
-
-        @Override
-        public void onCallStateChanged(int state) {
-            mCallState = state;
-            updateState(mPreference);
-        }
-
-        public void register(Context context, int subId) {
-            mTelephonyManager = getTelephonyManager(context, subId);
-            // assign current call state so that it helps to show correct preference state even
-            // before first onCallStateChanged() by initial registration.
-            mCallState = mTelephonyManager.getCallStateForSubscription();
-            mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(), this);
-        }
-
-        public void unregister() {
-            mCallState = null;
-            mTelephonyManager.unregisterTelephonyCallback(this);
-        }
-    }
-}
diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
new file mode 100644
index 0000000..e7b8318
--- /dev/null
+++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.Context
+import android.provider.Settings
+import android.telecom.TelecomManager
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.telephony.ims.ImsMmTelManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Preference controller for "Wifi Calling".
+ *
+ * TODO: Remove the class once Provider Model is always enabled in the future.
+ */
+open class WifiCallingPreferenceController @JvmOverloads constructor(
+    context: Context,
+    key: String,
+    private val callStateFlowFactory: (subId: Int) -> Flow<Int> = context::callStateFlow,
+    private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId ->
+        ImsMmTelRepositoryImpl(context, subId)
+    },
+) : TelephonyBasePreferenceController(context, key) {
+
+    private lateinit var preference: Preference
+    private lateinit var callingPreferenceCategoryController: CallingPreferenceCategoryController
+
+    private val resourcesForSub by lazy {
+        SubscriptionManager.getResourcesForSubId(mContext, mSubId)
+    }
+
+    fun init(
+        subId: Int,
+        callingPreferenceCategoryController: CallingPreferenceCategoryController,
+    ): WifiCallingPreferenceController {
+        mSubId = subId
+        this.callingPreferenceCategoryController = callingPreferenceCategoryController
+        return this
+    }
+
+    /**
+     * Note: Visibility also controlled by [onViewCreated].
+     */
+    override fun getAvailabilityStatus(subId: Int) =
+        if (SubscriptionManager.isValidSubscriptionId(subId)) AVAILABLE
+        else CONDITIONALLY_UNAVAILABLE
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        // Not call super here, to avoid preference.isVisible changed unexpectedly
+        preference = screen.findPreference(preferenceKey)!!
+        preference.intent?.putExtra(Settings.EXTRA_SUB_ID, mSubId)
+    }
+
+    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                val isVisible = withContext(Dispatchers.Default) {
+                    MobileNetworkUtils.isWifiCallingEnabled(mContext, mSubId, null)
+                }
+                preference.isVisible = isVisible
+                callingPreferenceCategoryController.updateChildVisible(preferenceKey, isVisible)
+            }
+        }
+
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                update()
+            }
+        }
+
+        callStateFlowFactory(mSubId).collectLatestWithLifecycle(viewLifecycleOwner) {
+            preference.isEnabled = (it == TelephonyManager.CALL_STATE_IDLE)
+        }
+    }
+
+    private suspend fun update() {
+        val simCallManager = mContext.getSystemService(TelecomManager::class.java)
+            ?.getSimCallManagerForSubscription(mSubId)
+        if (simCallManager != null) {
+            val intent = withContext(Dispatchers.Default) {
+                MobileNetworkUtils.buildPhoneAccountConfigureIntent(mContext, simCallManager)
+            } ?: return // Do nothing in this case since preference is invisible
+            val title = withContext(Dispatchers.Default) {
+                mContext.packageManager.resolveActivity(intent, 0)
+                    ?.loadLabel(mContext.packageManager)
+            } ?: return
+            preference.intent = intent
+            preference.title = title
+            preference.summary = null
+        } else {
+            preference.title = resourcesForSub.getString(R.string.wifi_calling_settings_title)
+            preference.summary = withContext(Dispatchers.Default) { getSummaryForWfcMode() }
+        }
+    }
+
+    private fun getSummaryForWfcMode(): String {
+        val resId = when (imsMmTelRepositoryFactory(mSubId).getWiFiCallingMode()) {
+            ImsMmTelManager.WIFI_MODE_WIFI_ONLY ->
+                com.android.internal.R.string.wfc_mode_wifi_only_summary
+
+            ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED ->
+                com.android.internal.R.string.wfc_mode_cellular_preferred_summary
+
+            ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED ->
+                com.android.internal.R.string.wfc_mode_wifi_preferred_summary
+
+            else -> com.android.internal.R.string.wifi_calling_off_summary
+        }
+        return resourcesForSub.getString(resId)
+    }
+}
diff --git a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
new file mode 100644
index 0000000..44f09d1
--- /dev/null
+++ b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony.ims
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
+import android.telephony.TelephonyManager
+import android.telephony.ims.ImsManager
+import android.telephony.ims.ImsMmTelManager
+import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
+
+interface ImsMmTelRepository {
+    @WiFiCallingMode
+    fun getWiFiCallingMode(): Int
+}
+
+class ImsMmTelRepositoryImpl(
+    context: Context,
+    private val subId: Int,
+    private val imsMmTelManager: ImsMmTelManager = ImsManager(context).getImsMmTelManager(subId),
+) : ImsMmTelRepository {
+
+    private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
+        .createForSubscriptionId(subId)
+
+    private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
+
+    @WiFiCallingMode
+    override fun getWiFiCallingMode(): Int = when {
+        !imsMmTelManager.isVoWiFiSettingEnabled -> ImsMmTelManager.WIFI_MODE_UNKNOWN
+
+        telephonyManager.isNetworkRoaming && !useWfcHomeModeForRoaming() ->
+            imsMmTelManager.getVoWiFiRoamingModeSetting()
+
+        else -> imsMmTelManager.getVoWiFiModeSetting()
+    }
+
+    private fun useWfcHomeModeForRoaming(): Boolean =
+        carrierConfigManager
+            .getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+            .getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+}
diff --git a/src/com/android/settings/notification/RemoteVolumeGroupController.java b/src/com/android/settings/notification/RemoteVolumeGroupController.java
index 50f9ed5..919b6d0 100644
--- a/src/com/android/settings/notification/RemoteVolumeGroupController.java
+++ b/src/com/android/settings/notification/RemoteVolumeGroupController.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.notification;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.media.MediaRouter2Manager;
@@ -53,6 +55,7 @@
     @VisibleForTesting
     static final String SWITCHER_PREFIX = "OUTPUT_SWITCHER";
 
+    @Nullable
     private PreferenceCategory mPreferenceCategory;
     private final List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>();
 
@@ -61,6 +64,7 @@
     @VisibleForTesting
     MediaRouter2Manager mRouterManager;
 
+    // Called via reflection from BasePreferenceController#createInstance().
     public RemoteVolumeGroupController(Context context, String preferenceKey) {
         super(context, preferenceKey);
         if (mLocalMediaManager == null) {
@@ -71,6 +75,19 @@
         mRouterManager = MediaRouter2Manager.getInstance(context);
     }
 
+    @VisibleForTesting
+    /* package */ RemoteVolumeGroupController(
+            @NonNull Context context,
+            @NonNull String preferenceKey,
+            @NonNull LocalMediaManager localMediaManager,
+            @NonNull MediaRouter2Manager mediaRouter2Manager) {
+        super(context, preferenceKey);
+        mLocalMediaManager = localMediaManager;
+        mRouterManager = mediaRouter2Manager;
+        mLocalMediaManager.registerCallback(this);
+        mLocalMediaManager.startScan();
+    }
+
     @Override
     public int getAvailabilityStatus() {
         if (mRoutingSessionInfos.isEmpty()) {
diff --git a/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java
index 8b4ebca..91b240e 100644
--- a/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java
@@ -41,13 +41,12 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.ParameterizedRobolectricTestRunner;
-import org.robolectric.RobolectricTestRunner;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(ParameterizedRobolectricTestRunner.class)
 public class WorkModePreferenceControllerTest {
 
     private static final String PREF_KEY = "work_mode";
diff --git a/tests/robotests/src/com/android/settings/display/ScreenTimeoutSettingsTest.java b/tests/robotests/src/com/android/settings/display/ScreenTimeoutSettingsTest.java
index 9e193ff..1a6a112 100644
--- a/tests/robotests/src/com/android/settings/display/ScreenTimeoutSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/display/ScreenTimeoutSettingsTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -41,31 +42,44 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.SearchIndexableResource;
 import android.provider.Settings;
 
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
+import com.android.settings.flags.Flags;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.widget.CandidateInfo;
 import com.android.settingslib.widget.FooterPreference;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
 import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowKeyguardManager;
 
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {
         com.android.settings.testutils.shadow.ShadowFragment.class,
+        ShadowKeyguardManager.class
 })
 public class ScreenTimeoutSettingsTest {
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final String[] TIMEOUT_ENTRIES = new String[]{"15 secs", "30 secs"};
     private static final String[] TIMEOUT_VALUES = new String[]{"15000", "30000"};
 
@@ -218,4 +232,85 @@
 
         assertThat(Long.toString(timeout)).isEqualTo(TIMEOUT_VALUES[0]);
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PROTECT_SCREEN_TIMEOUT_WITH_AUTH)
+    public void onClick_whenUserAlreadyAuthenticated_buttonChecked() {
+        String key = "222";
+        String defaultKey = "1";
+        mSettings.setDefaultKey(defaultKey);
+        CandidateInfo info = new ScreenTimeoutSettings.TimeoutCandidateInfo("label", key, false);
+        ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference pref =
+                new ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference(
+                        mContext, info.getKey(), mSettings);
+        mSettings.bindPreference(pref, info.getKey(), info, defaultKey);
+        mSettings.setUserAuthenticated(true);
+
+        pref.onClick();
+
+        assertThat(mSettings.getDefaultKey()).isEqualTo(key);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PROTECT_SCREEN_TIMEOUT_WITH_AUTH)
+    public void onClick_whenButtonAlreadyChecked_noAuthNeeded() {
+        String key = "222";
+        mSettings.setDefaultKey(key);
+        CandidateInfo info = new ScreenTimeoutSettings.TimeoutCandidateInfo("label", key, false);
+        ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference pref =
+                new ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference(
+                        mContext, info.getKey(), mSettings);
+        mSettings.bindPreference(pref, info.getKey(), info, key);
+        mSettings.setUserAuthenticated(false);
+        setAuthPassesAutomatically();
+
+        pref.onClick();
+
+        assertThat(mSettings.isUserAuthenticated()).isFalse();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PROTECT_SCREEN_TIMEOUT_WITH_AUTH)
+    public void onClick_whenReducingTimeout_noAuthNeeded() {
+        String key = "1";
+        String defaultKey = "222";
+        mSettings.setDefaultKey(defaultKey);
+        CandidateInfo info = new ScreenTimeoutSettings.TimeoutCandidateInfo("label", key, false);
+        ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference pref =
+                new ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference(
+                        mContext, info.getKey(), mSettings);
+        mSettings.bindPreference(pref, info.getKey(), info, defaultKey);
+        mSettings.setUserAuthenticated(false);
+        setAuthPassesAutomatically();
+
+        pref.onClick();
+
+        assertThat(mSettings.isUserAuthenticated()).isFalse();
+        assertThat(mSettings.getDefaultKey()).isEqualTo(key);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PROTECT_SCREEN_TIMEOUT_WITH_AUTH)
+    public void onClick_whenIncreasingTimeout_authNeeded() {
+        String key = "222";
+        String defaultKey = "1";
+        mSettings.setDefaultKey(defaultKey);
+        CandidateInfo info = new ScreenTimeoutSettings.TimeoutCandidateInfo("label", key, false);
+        ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference pref =
+                new ScreenTimeoutSettings.ProtectedSelectorWithWidgetPreference(
+                        mContext, info.getKey(), mSettings);
+        mSettings.bindPreference(pref, info.getKey(), info, defaultKey);
+        mSettings.setUserAuthenticated(false);
+        setAuthPassesAutomatically();
+
+        pref.onClick();
+
+        assertThat(mSettings.getDefaultKey()).isEqualTo(key);
+        assertThat(mSettings.isUserAuthenticated()).isTrue();
+    }
+
+    private void setAuthPassesAutomatically() {
+        Shadows.shadowOf(mContext.getSystemService(KeyguardManager.class))
+                .setIsKeyguardSecure(false);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
index f8a4f28..296306d 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
@@ -104,7 +104,7 @@
 
     @Test
     public void updatePreference_shouldSetPrimaryButtonText() {
-        String expectedText = mContext.getString(R.string.battery_tip_charge_to_full_button);
+        String expectedText = mContext.getString(R.string.learn_more);
 
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
@@ -113,7 +113,7 @@
 
     @Test
     public void updatePreference_shouldSetSecondaryButtonText() {
-        String expected = mContext.getString(R.string.learn_more);
+        String expected = mContext.getString(R.string.battery_tip_charge_to_full_button);
 
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
@@ -121,10 +121,10 @@
     }
 
     @Test
-    public void updatePreference_shouldSetSecondaryButtonVisible() {
+    public void updatePreference_shouldSetPrimaryButtonVisible() {
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
-        verify(mCardPreference).setSecondaryButtonVisible(true);
+        verify(mCardPreference).setPrimaryButtonVisible(true);
     }
 
     @Test
@@ -138,19 +138,19 @@
     }
 
     @Test
-    public void updatePreference_whenNotCharging_setPrimaryButtonVisibleToBeFalse() {
+    public void updatePreference_whenNotCharging_setSecondaryButtonVisibleToBeFalse() {
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
-        verify(mCardPreference).setPrimaryButtonVisible(false);
+        verify(mCardPreference).setSecondaryButtonVisible(false);
     }
 
     @Test
-    public void updatePreference_whenGetChargingStatusFailed_setPrimaryButtonVisibleToBeFalse() {
+    public void updatePreference_whenGetChargingStatusFailed_setSecondaryButtonVisibleToBeFalse() {
         fakeGetChargingStatusFailed();
 
         mBatteryDefenderTip.updatePreference(mCardPreference);
 
-        verify(mCardPreference).setPrimaryButtonVisible(false);
+        verify(mCardPreference).setSecondaryButtonVisible(false);
     }
 
     private void fakeGetChargingStatusFailed() {
diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
index e91c388..2989324 100644
--- a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
@@ -369,7 +369,7 @@
         // In the proto file, en-US's uid list contains 103, the notificationCount equals 1, and
         // LastNotificationTime > 0.
         NotificationInfo info = mDataManager.getNotificationInfo(EN_US);
-        assertThat(info.getUidCollection().contains(sUid)).isTrue();
+        assertThat(info.getUidCollection()).contains(sUid);
         assertThat(info.getNotificationCount()).isEqualTo(1);
         assertThat(info.getDismissCount()).isEqualTo(0);
         assertThat(info.getLastNotificationTimeMs()).isNotEqualTo(0);
@@ -440,7 +440,7 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_LOCALE_NOTIFICATION_ENABLED)
-    public void testEvaluateLocaleNotification_localeUpdateReachThreshold_uidAddedNoNotification()
+    public void testEvaluateLocaleNotification_localeUpdateReachThreshold_noUidNorNotification()
             throws Exception {
         // App with uid 106 changed its locale from System to en-US.
         sUid = 106;
@@ -460,7 +460,7 @@
         // In the proto file, en-US's uid list contains 106, the notificationCount equals 2, and
         // LastNotificationTime > 0.
         NotificationInfo info = mDataManager.getNotificationInfo(EN_US);
-        assertThat(info.getUidCollection()).contains(sUid);
+        assertThat(info.getUidCollection().contains(sUid)).isFalse();
         assertThat(info.getNotificationCount()).isEqualTo(2);
         assertThat(info.getDismissCount()).isEqualTo(0);
         assertThat(info.getLastNotificationTimeMs()).isEqualTo(lastNotificationTime);
diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleNotificationDataManagerTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleNotificationDataManagerTest.java
index 99541b6..443c26d 100644
--- a/tests/robotests/src/com/android/settings/localepicker/LocaleNotificationDataManagerTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/LocaleNotificationDataManagerTest.java
@@ -69,6 +69,18 @@
     }
 
     @Test
+    public void testRemoveNotificationInfo() {
+        String locale = "en-US";
+        Set<Integer> uidSet = Set.of(101);
+        NotificationInfo info = new NotificationInfo(uidSet, 1, 1, 100L, 1000);
+
+        mDataManager.putNotificationInfo(locale, info);
+        assertThat(mDataManager.getNotificationInfo(locale)).isEqualTo(info);
+        mDataManager.removeNotificationInfo(locale);
+        assertThat(mDataManager.getNotificationInfo(locale)).isNull();
+    }
+
+    @Test
     public void testGetNotificationMap() {
         String enUS = "en-US";
         Set<Integer> uidSet1 = Set.of(101, 102);
diff --git a/tests/robotests/src/com/android/settings/localepicker/NotificationControllerTest.java b/tests/robotests/src/com/android/settings/localepicker/NotificationControllerTest.java
index 3e31c0c..1e37f9b 100644
--- a/tests/robotests/src/com/android/settings/localepicker/NotificationControllerTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/NotificationControllerTest.java
@@ -72,6 +72,19 @@
     }
 
     @Test
+    public void testRemoveNotificationInfo_removed() throws Exception {
+        String enUS = "en-US";
+        Set<Integer> uidSet = Set.of(100, 101);
+        long lastNotificationTime = Calendar.getInstance().getTimeInMillis();
+        int id = (int) SystemClock.uptimeMillis();
+        initSharedPreference(enUS, uidSet, 0, 1, lastNotificationTime, id);
+
+        mNotificationController.removeNotificationInfo(enUS);
+
+        assertThat(mDataManager.getNotificationInfo(enUS)).isNull();
+    }
+
+    @Test
     public void testShouldTriggerNotification_inSystemLocale_returnFalse() throws Exception {
         int uid = 102;
         // As checking whether app's locales exist in system locales, both app locales and system
diff --git a/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java
index 9b44faa..da8958d 100644
--- a/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/telephony/VideoCallingPreferenceControllerTest.java
@@ -86,7 +86,8 @@
 
         mPreference = new SwitchPreference(mContext);
         mController = spy(new VideoCallingPreferenceController(mContext, "wifi_calling"));
-        mController.init(SUB_ID);
+        mController.init(
+                SUB_ID, new CallingPreferenceCategoryController(mContext, "calling_category"));
         doReturn(mQueryImsState).when(mController).queryImsState(anyInt());
         doReturn(mQueryVoLteState).when(mController).queryVoLteState(anyInt());
         doReturn(true).when(mController).isImsSupported();
diff --git a/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java b/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java
index 1e42e18..06bd90b 100644
--- a/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java
@@ -103,9 +103,9 @@
         mContext = spy(RuntimeEnvironment.application);
         doReturn(mMediaSessionManager).when(mContext).getSystemService(
                 Context.MEDIA_SESSION_SERVICE);
-        mController = new RemoteVolumeGroupController(mContext, KEY_REMOTE_VOLUME_GROUP);
-        mController.mLocalMediaManager = mLocalMediaManager;
-        mController.mRouterManager = mRouterManager;
+        mController =
+                new RemoteVolumeGroupController(
+                        mContext, KEY_REMOTE_VOLUME_GROUP, mLocalMediaManager, mRouterManager);
         mPreferenceCategory = spy(new PreferenceCategory(mContext));
         mPreferenceCategory.setKey(mController.getPreferenceKey());
 
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CallingPreferenceCategoryControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CallingPreferenceCategoryControllerTest.kt
new file mode 100644
index 0000000..81d17d2
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/CallingPreferenceCategoryControllerTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.Context
+import androidx.preference.PreferenceManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.spa.preference.ComposePreference
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CallingPreferenceCategoryControllerTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val preference = ComposePreference(context).apply { key = TEST_KEY }
+    private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
+
+    private val controller = CallingPreferenceCategoryController(context, TEST_KEY)
+
+    @Before
+    fun setUp() {
+        preferenceScreen.addPreference(preference)
+        controller.displayPreference(preferenceScreen)
+    }
+
+    @Test
+    fun updateChildVisible_singleChildVisible_categoryVisible() {
+        controller.updateChildVisible(CHILD_A_KEY, true)
+
+        assertThat(preference.isVisible).isTrue()
+    }
+
+    @Test
+    fun updateChildVisible_singleChildNotVisible_categoryNotVisible() {
+        controller.updateChildVisible(CHILD_A_KEY, false)
+
+        assertThat(preference.isVisible).isFalse()
+    }
+
+    @Test
+    fun updateChildVisible_oneChildVisible_categoryVisible() {
+        controller.updateChildVisible(CHILD_A_KEY, true)
+        controller.updateChildVisible(CHILD_B_KEY, false)
+
+        assertThat(preference.isVisible).isTrue()
+    }
+
+    @Test
+    fun updateChildVisible_nonChildNotVisible_categoryNotVisible() {
+        controller.updateChildVisible(CHILD_A_KEY, false)
+        controller.updateChildVisible(CHILD_B_KEY, false)
+
+        assertThat(preference.isVisible).isFalse()
+    }
+
+    private companion object {
+        const val TEST_KEY = "test_key"
+        const val CHILD_A_KEY = "a"
+        const val CHILD_B_KEY = "b"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt
new file mode 100644
index 0000000..fc53049
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.telecom.PhoneAccountHandle
+import android.telecom.TelecomManager
+import android.telephony.TelephonyManager
+import android.telephony.ims.ImsMmTelManager
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class WifiCallingPreferenceControllerTest {
+    private val mockTelecomManager = mock<TelecomManager>()
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { getSystemService(TelecomManager::class.java) } doReturn mockTelecomManager
+    }
+
+    private val preferenceIntent = Intent()
+
+    private val preference = Preference(context).apply {
+        key = TEST_KEY
+        intent = preferenceIntent
+    }
+    private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
+
+    private var callState = TelephonyManager.CALL_STATE_IDLE
+
+    private object FakeImsMmTelRepository : ImsMmTelRepository {
+        var wiFiMode = ImsMmTelManager.WIFI_MODE_UNKNOWN
+        override fun getWiFiCallingMode() = wiFiMode
+    }
+
+    private val callingPreferenceCategoryController =
+        CallingPreferenceCategoryController(context, "calling_category")
+
+    private val controller = WifiCallingPreferenceController(
+        context = context,
+        key = TEST_KEY,
+        callStateFlowFactory = { flowOf(callState) },
+        imsMmTelRepositoryFactory = { FakeImsMmTelRepository },
+    ).init(subId = SUB_ID, callingPreferenceCategoryController)
+
+    @Before
+    fun setUp() {
+        preferenceScreen.addPreference(preference)
+        controller.displayPreference(preferenceScreen)
+    }
+
+    @Test
+    fun summary_noSimCallManager_setCorrectSummary() = runBlocking {
+        mockTelecomManager.stub {
+            on { getSimCallManagerForSubscription(SUB_ID) } doReturn null
+        }
+        FakeImsMmTelRepository.wiFiMode = ImsMmTelManager.WIFI_MODE_WIFI_ONLY
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.summary)
+            .isEqualTo(context.getString(com.android.internal.R.string.wfc_mode_wifi_only_summary))
+    }
+
+    @Test
+    fun summary_hasSimCallManager_summaryIsNull() = runBlocking {
+        mockTelecomManager.stub {
+            on { getSimCallManagerForSubscription(SUB_ID) } doReturn
+                PhoneAccountHandle(ComponentName("", ""), "")
+        }
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.summary).isNull()
+    }
+
+    @Test
+    fun isEnabled_callIdle_enabled() = runBlocking {
+        callState = TelephonyManager.CALL_STATE_IDLE
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.isEnabled).isTrue()
+    }
+
+    @Test
+    fun isEnabled_notCallIdle_disabled() = runBlocking {
+        callState = TelephonyManager.CALL_STATE_RINGING
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.isEnabled).isFalse()
+    }
+
+    @Test
+    fun displayPreference_setsSubscriptionIdOnIntent() = runBlocking {
+        assertThat(preference.intent!!.getIntExtra(Settings.EXTRA_SUB_ID, 0)).isEqualTo(SUB_ID)
+    }
+
+    private companion object {
+        const val TEST_KEY = "test_key"
+        const val SUB_ID = 2
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt
new file mode 100644
index 0000000..eba44ed
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony.ims
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
+import android.telephony.TelephonyManager
+import android.telephony.ims.ImsMmTelManager
+import androidx.core.os.persistableBundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class ImsMmTelRepositoryTest {
+    private val mockTelephonyManager = mock<TelephonyManager> {
+        on { createForSubscriptionId(SUB_ID) } doReturn mock
+    }
+
+    private val mockCarrierConfigManager = mock<CarrierConfigManager>()
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+        on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
+    }
+
+    private val mockImsMmTelManager = mock<ImsMmTelManager> {
+        on { isVoWiFiSettingEnabled } doReturn true
+        on { getVoWiFiRoamingModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
+        on { getVoWiFiModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED
+    }
+
+    private val repository = ImsMmTelRepositoryImpl(context, SUB_ID, mockImsMmTelManager)
+
+    @Test
+    fun getWiFiCallingMode_voWiFiSettingNotEnabled_returnUnknown() {
+        mockImsMmTelManager.stub {
+            on { isVoWiFiSettingEnabled } doReturn false
+        }
+
+        val wiFiCallingMode = repository.getWiFiCallingMode()
+
+        assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN)
+    }
+
+    @Test
+    fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() {
+        mockTelephonyManager.stub {
+            on { isNetworkRoaming } doReturn true
+        }
+        mockUseWfcHomeModeForRoaming(false)
+
+        val wiFiCallingMode = repository.getWiFiCallingMode()
+
+        assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiRoamingModeSetting())
+    }
+
+    @Test
+    fun getWiFiCallingMode_roamingAndUseWfcHomeModeForRoaming_returnHomeSetting() {
+        mockTelephonyManager.stub {
+            on { isNetworkRoaming } doReturn true
+        }
+        mockUseWfcHomeModeForRoaming(true)
+
+        val wiFiCallingMode = repository.getWiFiCallingMode()
+
+        assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting())
+    }
+
+    @Test
+    fun getWiFiCallingMode_notRoaming_returnHomeSetting() {
+        mockTelephonyManager.stub {
+            on { isNetworkRoaming } doReturn false
+        }
+
+        val wiFiCallingMode = repository.getWiFiCallingMode()
+
+        assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting())
+    }
+
+    private fun mockUseWfcHomeModeForRoaming(config: Boolean) {
+        mockCarrierConfigManager.stub {
+            on {
+                getConfigForSubId(SUB_ID, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+            } doReturn persistableBundleOf(
+                KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL to config,
+            )
+        }
+    }
+
+    private companion object {
+        const val SUB_ID = 1
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt b/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt
new file mode 100644
index 0000000..c3c6188
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.telephony.CellIdentityCdma
+import android.telephony.CellIdentityGsm
+import android.telephony.CellInfoCdma
+import android.telephony.CellInfoGsm
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.telephony.OperatorInfo
+import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle
+import com.android.settings.network.telephony.CellInfoUtil.getOperatorNumeric
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CellInfoUtilTest {
+
+    @Test
+    fun getNetworkTitle_alphaLong() {
+        val networkTitle = CELL_IDENTITY_GSM.getNetworkTitle()
+
+        assertThat(networkTitle).isEqualTo(LONG)
+    }
+
+    @Test
+    fun getNetworkTitle_alphaShort() {
+        val cellIdentity = CellIdentityGsm(
+            /* lac = */ 1,
+            /* cid = */ 2,
+            /* arfcn = */ 3,
+            /* bsic = */ 4,
+            /* mccStr = */ "123",
+            /* mncStr = */ "01",
+            /* alphal = */ "",
+            /* alphas = */ SHORT,
+            /* additionalPlmns = */ emptyList(),
+        )
+
+        val networkTitle = cellIdentity.getNetworkTitle()
+
+        assertThat(networkTitle).isEqualTo(SHORT)
+    }
+
+    @Test
+    fun getNetworkTitle_operatorNumeric() {
+        val cellIdentity = CellIdentityGsm(
+            /* lac = */ 1,
+            /* cid = */ 2,
+            /* arfcn = */ 3,
+            /* bsic = */ 4,
+            /* mccStr = */ "123",
+            /* mncStr = */ "01",
+            /* alphal = */ "",
+            /* alphas = */ "",
+            /* additionalPlmns = */ emptyList(),
+        )
+
+        val networkTitle = cellIdentity.getNetworkTitle()
+
+        assertThat(networkTitle).isEqualTo("12301")
+    }
+
+    @Test
+    fun getNetworkTitle_null() {
+        val cellIdentity = CellIdentityGsm(
+            /* lac = */ 1,
+            /* cid = */ 2,
+            /* arfcn = */ 3,
+            /* bsic = */ 4,
+            /* mccStr = */ null,
+            /* mncStr = */ null,
+            /* alphal = */ null,
+            /* alphas = */ null,
+            /* additionalPlmns = */ emptyList(),
+        )
+
+        val networkTitle = cellIdentity.getNetworkTitle()
+
+        assertThat(networkTitle).isNull()
+    }
+
+    @Test
+    fun convertOperatorInfoToCellInfo() {
+        val operatorInfo = OperatorInfo(LONG, SHORT, "12301")
+
+        val cellInfo = CellInfoUtil.convertOperatorInfoToCellInfo(operatorInfo)
+
+        assertThat(cellInfo.cellIdentity.mccString).isEqualTo("123")
+        assertThat(cellInfo.cellIdentity.mncString).isEqualTo("01")
+        assertThat(cellInfo.cellIdentity.operatorAlphaLong).isEqualTo(LONG)
+        assertThat(cellInfo.cellIdentity.operatorAlphaShort).isEqualTo(SHORT)
+    }
+
+    @Test
+    fun cellInfoListToString() {
+        val cellInfoList =
+            listOf(
+                CellInfoCdma().apply {
+                    cellIdentity = CELL_IDENTITY_CDMA
+                },
+                CellInfoGsm().apply {
+                    isRegistered = true
+                    cellIdentity = CELL_IDENTITY_GSM
+                },
+            )
+
+        val string = CellInfoUtil.cellInfoListToString(cellInfoList)
+
+        assertThat(string).isEqualTo(
+            "{CellType = CellInfoCdma, isRegistered = false, " +
+                "mcc = null, mnc = null, alphaL = Long, alphaS = Short}, " +
+                "{CellType = CellInfoGsm, isRegistered = true, " +
+                "mcc = 123, mnc = 01, alphaL = Long, alphaS = Short}"
+        )
+    }
+
+    @Test
+    fun getOperatorNumeric_cdma() {
+        val operatorNumeric = CELL_IDENTITY_CDMA.getOperatorNumeric()
+
+        assertThat(operatorNumeric).isNull()
+    }
+
+    @Test
+    fun getOperatorNumeric_gsm() {
+        val operatorNumeric = CELL_IDENTITY_GSM.getOperatorNumeric()
+
+        assertThat(operatorNumeric).isEqualTo("12301")
+    }
+
+    private companion object {
+        const val LONG = "Long"
+        const val SHORT = "Short"
+
+        val CELL_IDENTITY_GSM = CellIdentityGsm(
+            /* lac = */ 1,
+            /* cid = */ 2,
+            /* arfcn = */ 3,
+            /* bsic = */ 4,
+            /* mccStr = */ "123",
+            /* mncStr = */ "01",
+            /* alphal = */ LONG,
+            /* alphas = */ SHORT,
+            /* additionalPlmns = */ emptyList(),
+        )
+
+        val CELL_IDENTITY_CDMA = CellIdentityCdma(
+            /* nid = */ 1,
+            /* sid = */ 2,
+            /* bid = */ 3,
+            /* lon = */ 4,
+            /* lat = */ 5,
+            /* alphal = */ LONG,
+            /* alphas = */ SHORT,
+        )
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.java
deleted file mode 100644
index 5827516..0000000
--- a/tests/unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 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.network.telephony;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.PersistableBundle;
-import android.provider.Settings;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.ims.ImsMmTelManager;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.network.ims.MockWifiCallingQueryImsState;
-import com.android.settings.network.ims.WifiCallingQueryImsState;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-public class WifiCallingPreferenceControllerTest {
-    private static final int SUB_ID = 2;
-    @Mock
-    private SubscriptionManager mSubscriptionManager;
-    @Mock
-    private CarrierConfigManager mCarrierConfigManager;
-    @Mock
-    private TelephonyManager mTelephonyManager;
-    @Mock
-    private ImsMmTelManager mImsMmTelManager;
-
-    private PreferenceScreen mScreen;
-    private PreferenceManager mPreferenceManager;
-
-    private MockWifiCallingQueryImsState mQueryImsState;
-
-    private TestWifiCallingPreferenceController mController;
-    private Preference mPreference;
-    private Context mContext;
-    private PersistableBundle mCarrierConfig;
-
-    @Before
-    @UiThreadTest
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mContext = spy(ApplicationProvider.getApplicationContext());
-        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
-
-        mQueryImsState = new MockWifiCallingQueryImsState(mContext, SUB_ID);
-        mQueryImsState.setIsEnabledByUser(true);
-        mQueryImsState.setIsProvisionedOnDevice(true);
-
-        mController = new TestWifiCallingPreferenceController(mContext, "wifi_calling");
-        mController.mCarrierConfigManager = mCarrierConfigManager;
-        mController.init(SUB_ID);
-        mController.mCallState = TelephonyManager.CALL_STATE_IDLE;
-        mCarrierConfig = new PersistableBundle();
-        when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(mCarrierConfig);
-
-        mPreferenceManager = new PreferenceManager(mContext);
-        mScreen = mPreferenceManager.createPreferenceScreen(mContext);
-        mPreference = new Preference(mContext);
-        mPreference.setKey(mController.getPreferenceKey());
-        mScreen.addPreference(mPreference);
-    }
-
-    @Test
-    @UiThreadTest
-    public void updateState_noSimCallManager_setCorrectSummary() {
-        mController.mSimCallManager = null;
-        mQueryImsState.setIsEnabledByUser(true);
-        when(mImsMmTelManager.getVoWiFiRoamingModeSetting()).thenReturn(
-                ImsMmTelManager.WIFI_MODE_WIFI_ONLY);
-        when(mImsMmTelManager.getVoWiFiModeSetting()).thenReturn(
-                ImsMmTelManager.WIFI_MODE_WIFI_ONLY);
-
-        mController.updateState(mPreference);
-
-        assertThat(mPreference.getSummary()).isEqualTo(
-                mContext.getString(com.android.internal.R.string.wfc_mode_wifi_only_summary));
-    }
-
-    @Test
-    @UiThreadTest
-    public void updateState_notCallIdle_disable() {
-        mController.mCallState = TelephonyManager.CALL_STATE_RINGING;
-
-        mController.updateState(mPreference);
-
-        assertThat(mPreference.isEnabled()).isFalse();
-    }
-
-    @Test
-    @UiThreadTest
-    public void updateState_invalidPhoneAccountHandle_shouldNotCrash() {
-        mController.mSimCallManager = new PhoneAccountHandle(null /* invalid */, "");
-
-        //Should not crash
-        mController.updateState(mPreference);
-    }
-
-    @Test
-    @UiThreadTest
-    public void updateState_wfcNonRoamingByConfig() {
-        assertNull(mController.mSimCallManager);
-        mCarrierConfig.putBoolean(
-                CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL, true);
-        mController.init(SUB_ID);
-
-        when(mImsMmTelManager.getVoWiFiRoamingModeSetting()).thenReturn(
-                ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED);
-        when(mImsMmTelManager.getVoWiFiModeSetting()).thenReturn(
-                ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED);
-        mQueryImsState.setIsEnabledByUser(true);
-        when(mTelephonyManager.isNetworkRoaming()).thenReturn(true);
-
-        mController.updateState(mPreference);
-        assertThat(mPreference.getSummary())
-                .isEqualTo(mContext.getString(R.string.wfc_mode_cellular_preferred_summary));
-    }
-
-    @Test
-    @UiThreadTest
-    public void updateState_wfcRoamingByConfig() {
-        assertNull(mController.mSimCallManager);
-        // useWfcHomeModeForRoaming is false by default. In order to check wfc in roaming mode. We
-        // need the device roaming, and not using home mode in roaming network.
-        when(mImsMmTelManager.getVoWiFiRoamingModeSetting()).thenReturn(
-                ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED);
-        when(mImsMmTelManager.getVoWiFiModeSetting()).thenReturn(
-                ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED);
-        mQueryImsState.setIsEnabledByUser(true);
-        when(mTelephonyManager.isNetworkRoaming()).thenReturn(true);
-
-        mController.updateState(mPreference);
-        assertThat(mPreference.getSummary())
-                .isEqualTo(mContext.getString(R.string.wfc_mode_wifi_preferred_summary));
-    }
-
-    @Test
-    @UiThreadTest
-    public void displayPreference_notAvailable_setPreferenceInvisible() {
-        mController.init(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null);
-
-        mController.displayPreference(mScreen);
-
-        assertThat(mController.getPreferenceKey()).isEqualTo("wifi_calling");
-        assertThat(mScreen.findPreference(mController.getPreferenceKey()).isVisible()).isFalse();
-    }
-
-    @Test
-    @Ignore
-    public void displayPreference_available_setsSubscriptionIdOnIntent() {
-        final Intent intent = new Intent();
-        mPreference.setIntent(intent);
-        mController.displayPreference(mScreen);
-        assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(SUB_ID);
-    }
-
-    @Test
-    @UiThreadTest
-    public void getAvailabilityStatus_noWiFiCalling_shouldReturnUnsupported() {
-        mController.init(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null);
-
-        assertThat(mController.getAvailabilityStatus()).isEqualTo(
-                BasePreferenceController.UNSUPPORTED_ON_DEVICE);
-    }
-
-    private class TestWifiCallingPreferenceController extends WifiCallingPreferenceController {
-        TestWifiCallingPreferenceController(Context context, String preferenceKey) {
-            super(context, preferenceKey);
-        }
-
-        @Override
-        protected ImsMmTelManager getImsMmTelManager(int subId) {
-            return mImsMmTelManager;
-        }
-
-        @Override
-        protected TelephonyManager getTelephonyManager(Context context, int subId) {
-            return mTelephonyManager;
-        }
-
-        @Override
-        protected WifiCallingQueryImsState queryImsState(int subId) {
-            return mQueryImsState;
-        }
-    }
-}