Merge "Update error dialog text for SIM switching" into main
diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml
index fc99ae2..29e7c25 100644
--- a/res/xml/mobile_network_settings.xml
+++ b/res/xml/mobile_network_settings.xml
@@ -98,9 +98,11 @@
             settings:searchable="false"
             settings:controller="com.android.settings.network.telephony.DataUsagePreferenceController"/>
 
+        <!-- Settings search is handled by BillingCycleSearchItem. -->
         <com.android.settings.datausage.BillingCyclePreference
             android:key="billing_preference"
             android:title="@string/billing_cycle"
+            settings:searchable="false"
             settings:controller="com.android.settings.datausage.BillingCyclePreferenceController"/>
 
         <SwitchPreferenceCompat
diff --git a/src/com/android/settings/accessibility/HearingAidCompatibilityPreferenceController.java b/src/com/android/settings/accessibility/HearingAidCompatibilityPreferenceController.java
index 727cdd5..821fddd 100644
--- a/src/com/android/settings/accessibility/HearingAidCompatibilityPreferenceController.java
+++ b/src/com/android/settings/accessibility/HearingAidCompatibilityPreferenceController.java
@@ -25,6 +25,7 @@
 import com.android.internal.telephony.flags.Flags;
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
 
 /** Preference controller for Hearing Aid Compatibility (HAC) settings */
 public class HearingAidCompatibilityPreferenceController extends TogglePreferenceController {
@@ -73,6 +74,8 @@
 
     @Override
     public boolean setChecked(boolean isChecked) {
+        FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().changed(
+                getMetricsCategory(), getPreferenceKey(), isChecked ? 1 : 0);
         setAudioParameterHacEnabled(isChecked);
         return Settings.System.putInt(mContext.getContentResolver(), Settings.System.HEARING_AID,
                 (isChecked ? HAC_ENABLED : HAC_DISABLED));
diff --git a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
index 818eb5e..45a19b7 100644
--- a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
+++ b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
@@ -27,6 +27,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -69,7 +70,8 @@
     public KeyboardVibrationTogglePreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
         mVibrator = context.getSystemService(Vibrator.class);
-        mContentObserver = new ContentObserver(new Handler(/* async= */ true)) {
+        Handler handler = Looper.myLooper() != null ? new Handler(/* async= */ true) : null;
+        mContentObserver = new ContentObserver(handler) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
                 if (uri.equals(MAIN_VIBRATION_SWITCH_URI)) {
diff --git a/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceController.java b/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceController.java
index db184bf..5b553e3 100644
--- a/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceController.java
+++ b/src/com/android/settings/accessibility/VibrationMainSwitchPreferenceController.java
@@ -23,6 +23,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -49,7 +50,8 @@
     public VibrationMainSwitchPreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
         mVibrator = context.getSystemService(Vibrator.class);
-        mSettingObserver = new ContentObserver(new Handler(/* async= */ true)) {
+        Handler handler = Looper.myLooper() != null ? new Handler(/* async= */ true) : null;
+        mSettingObserver = new ContentObserver(handler) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
                 updateState(mSwitchPreference);
diff --git a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
index ec1fab1..3887cec 100644
--- a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
+++ b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
@@ -27,6 +27,7 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -165,7 +166,7 @@
 
         /** Creates observer for given preference. */
         public SettingObserver(VibrationPreferenceConfig preferenceConfig) {
-            super(new Handler(/* async= */ true));
+            super(Looper.myLooper() != null ? new Handler(/* async= */ true) : null);
             mUri = Settings.System.getUriFor(preferenceConfig.getSettingKey());
 
             if (preferenceConfig.isRestrictedByRingerModeSilent()) {
diff --git a/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceController.java b/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceController.java
index 149bed3..69b1e15 100644
--- a/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceController.java
+++ b/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceController.java
@@ -21,6 +21,7 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Vibrator;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -74,7 +75,8 @@
         mRingVibrationPreferenceConfig = new RingVibrationPreferenceConfig(context);
         mRingSettingObserver = new VibrationPreferenceConfig.SettingObserver(
                 mRingVibrationPreferenceConfig);
-        mSettingObserver = new ContentObserver(new Handler(/* async= */ true)) {
+        Handler handler = Looper.myLooper() != null ? new Handler(/* async= */ true) : null;
+        mSettingObserver = new ContentObserver(handler) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
                 updateState(mPreference);
diff --git a/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceController.java b/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceController.java
index 622d5d8..b35575e 100644
--- a/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceController.java
+++ b/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceController.java
@@ -26,6 +26,7 @@
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.core.SubSettingLauncher;
 import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
 
 /** Preference controller for all bluetooth device preference. */
 public class ViewAllBluetoothDevicesPreferenceController extends BasePreferenceController {
@@ -52,6 +53,8 @@
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
         if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+            FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().clicked(
+                    getMetricsCategory(), getPreferenceKey());
             launchConnectedDevicePage();
             return true;
         }
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java
index f7ccc61..25a99ed 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingAidsPresetsController.java
@@ -38,6 +38,7 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.HapClientProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -111,6 +112,7 @@
                 final int index = listPreference.findIndexOfValue(value);
                 final String presetName = listPreference.getEntries()[index].toString();
                 final int presetIndex = Integer.parseInt(value);
+                logPresetChangedIfNeeded();
                 listPreference.setSummary(presetName);
                 if (DEBUG) {
                     Log.d(TAG, "onPreferenceChange"
@@ -373,4 +375,15 @@
             mHapClientProfile.selectPreset(memberDevice.getDevice(), presetIndex);
         }
     }
+
+    private void logPresetChangedIfNeeded() {
+        if (mPreference == null || mPreference.getEntries() == null) {
+            return;
+        }
+        if (mFragment instanceof BluetoothDeviceDetailsFragment) {
+            int category = ((BluetoothDeviceDetailsFragment) mFragment).getMetricsCategory();
+            FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().changed(category,
+                    getPreferenceKey(), mPreference.getEntries().length);
+        }
+    }
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index 5d9b124..4cb540c 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -65,15 +65,14 @@
 public class BluetoothDetailsProfilesController extends BluetoothDetailsController
         implements Preference.OnPreferenceClickListener,
         LocalBluetoothProfileManager.ServiceListener {
+    public static final String HIGH_QUALITY_AUDIO_PREF_TAG = "A2dpProfileHighQualityAudio";
+
     private static final String TAG = "BtDetailsProfilesCtrl";
 
     private static final String KEY_PROFILES_GROUP = "bluetooth_profiles";
     private static final String KEY_BOTTOM_PREFERENCE = "bottom_preference";
     private static final int ORDINAL = 99;
 
-    @VisibleForTesting
-    static final String HIGH_QUALITY_AUDIO_PREF_TAG = "A2dpProfileHighQualityAudio";
-
     private static final String ENABLE_DUAL_MODE_AUDIO =
             "persist.bluetooth.enable_dual_mode_audio";
     private static final String LE_AUDIO_CONNECTION_BY_DEFAULT_PROPERTY =
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index c0d67cc..6501084 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -25,6 +25,7 @@
 import android.bluetooth.BluetoothLeBroadcastAssistant;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -52,7 +53,10 @@
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -75,7 +79,8 @@
 public class AudioSharingSwitchBarController extends BasePreferenceController
         implements DefaultLifecycleObserver,
                 OnCheckedChangeListener,
-                LocalBluetoothProfileManager.ServiceListener {
+                LocalBluetoothProfileManager.ServiceListener,
+                BluetoothCallback {
     private static final String TAG = "AudioSharingSwitchCtlr";
     private static final String PREF_KEY = "audio_sharing_main_switch";
 
@@ -99,6 +104,7 @@
     private final SettingsMainSwitchBar mSwitchBar;
     private final BluetoothAdapter mBluetoothAdapter;
     @Nullable private final LocalBluetoothManager mBtManager;
+    @Nullable private final BluetoothEventManager mEventManager;
     @Nullable private final LocalBluetoothProfileManager mProfileManager;
     @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
     @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant;
@@ -302,6 +308,7 @@
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
         mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         mBtManager = Utils.getLocalBtManager(context);
+        mEventManager = mBtManager == null ? null : mBtManager.getEventManager();
         mProfileManager = mBtManager == null ? null : mBtManager.getProfileManager();
         mBroadcast = mProfileManager == null ? null : mProfileManager.getLeAudioBroadcastProfile();
         mAssistant =
@@ -427,6 +434,13 @@
         // Do nothing.
     }
 
+    @Override
+    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+        if (activeDevice != null && bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+            updateSwitch();
+        }
+    }
+
     /**
      * Initialize the controller.
      *
@@ -447,7 +461,7 @@
             Log.d(TAG, "Skip registerCallbacks(). Feature is not available.");
             return;
         }
-        if (mBroadcast == null || mAssistant == null) {
+        if (mBroadcast == null || mAssistant == null || mEventManager == null) {
             Log.d(TAG, "Skip registerCallbacks(). Profile not support on this device.");
             return;
         }
@@ -456,6 +470,7 @@
             mSwitchBar.addOnSwitchChangeListener(this);
             mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
             mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+            mEventManager.registerCallback(this);
             mCallbacksRegistered.set(true);
         }
     }
@@ -465,7 +480,7 @@
             Log.d(TAG, "Skip unregisterCallbacks(). Feature is not available.");
             return;
         }
-        if (mBroadcast == null || mAssistant == null) {
+        if (mBroadcast == null || mAssistant == null || mEventManager == null) {
             Log.d(TAG, "Skip unregisterCallbacks(). Profile not support on this device.");
             return;
         }
@@ -474,6 +489,7 @@
             mSwitchBar.removeOnSwitchChangeListener(this);
             mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
             mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
+            mEventManager.unregisterCallback(this);
             mCallbacksRegistered.set(false);
         }
     }
@@ -518,10 +534,15 @@
                 ThreadUtils.postOnBackgroundThread(
                         () -> {
                             boolean isBroadcasting = BluetoothUtils.isBroadcasting(mBtManager);
+                            boolean hasActiveDevice =
+                                    AudioSharingUtils.hasActiveConnectedLeadDevice(mBtManager);
                             boolean isStateReady =
                                     isBluetoothOn()
                                             && AudioSharingUtils.isAudioSharingProfileReady(
-                                                    mProfileManager);
+                                                    mProfileManager)
+                                            // Disable toggle till device gets active after
+                                            // broadcast ends.
+                                            && (isBroadcasting || hasActiveDevice);
                             AudioSharingUtils.postOnMainThread(
                                     mContext,
                                     () -> {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index 0c2dc36..43256bc 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -115,7 +115,7 @@
      * sharing. The active device is placed in the first place if it exists. The devices can be
      * filtered by whether it is already in the audio sharing session.
      *
-     * @param localBtManager The BT manager to provide BT functions. *
+     * @param localBtManager The BT manager to provide BT functions.
      * @param groupedConnectedDevices devices connected to broadcast assistant grouped by CSIP group
      *     id.
      * @param filterByInSharing Whether to filter the device by if is already in the sharing
@@ -190,7 +190,7 @@
      * sharing. The active device is placed in the first place if it exists. The devices can be
      * filtered by whether it is already in the audio sharing session.
      *
-     * @param localBtManager The BT manager to provide BT functions. *
+     * @param localBtManager The BT manager to provide BT functions.
      * @param groupedConnectedDevices devices connected to broadcast assistant grouped by CSIP group
      *     id.
      * @param filterByInSharing Whether to filter the device by if is already in the sharing
@@ -210,6 +210,22 @@
                 .collect(toList());
     }
 
+    /** Return if there exists active connected lead device. */
+    public static boolean hasActiveConnectedLeadDevice(
+            @Nullable LocalBluetoothManager localBtManager) {
+        CachedBluetoothDeviceManager deviceManager =
+                localBtManager == null ? null : localBtManager.getCachedDeviceManager();
+        Map<Integer, List<BluetoothDevice>> groupedConnectedDevices =
+                fetchConnectedDevicesByGroupId(localBtManager);
+        for (List<BluetoothDevice> devices : groupedConnectedDevices.values()) {
+            CachedBluetoothDevice leadDevice = getLeadDevice(deviceManager, devices);
+            if (isActiveLeAudioDevice(leadDevice)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */
     public static AudioSharingDeviceItem buildAudioSharingDeviceItem(
             CachedBluetoothDevice cachedDevice) {
@@ -225,8 +241,8 @@
      * @param cachedDevice The cached bluetooth device to check.
      * @return Whether the device is an active le audio device.
      */
-    public static boolean isActiveLeAudioDevice(CachedBluetoothDevice cachedDevice) {
-        return BluetoothUtils.isActiveLeAudioDevice(cachedDevice);
+    public static boolean isActiveLeAudioDevice(@Nullable CachedBluetoothDevice cachedDevice) {
+        return cachedDevice != null && BluetoothUtils.isActiveLeAudioDevice(cachedDevice);
     }
 
     /** Toast message on main thread. */
diff --git a/src/com/android/settings/datausage/BillingCyclePreferenceController.java b/src/com/android/settings/datausage/BillingCyclePreferenceController.java
deleted file mode 100644
index 8b55585..0000000
--- a/src/com/android/settings/datausage/BillingCyclePreferenceController.java
+++ /dev/null
@@ -1,52 +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.datausage;
-
-import android.content.Context;
-import android.net.NetworkTemplate;
-
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.datausage.lib.DataUsageLib;
-
-public class BillingCyclePreferenceController extends BasePreferenceController {
-    private int mSubscriptionId;
-
-    public BillingCyclePreferenceController(Context context, String preferenceKey) {
-        super(context, preferenceKey);
-    }
-
-    public void init(int subscriptionId) {
-        mSubscriptionId = subscriptionId;
-    }
-
-    @Override
-    public void displayPreference(PreferenceScreen screen) {
-        super.displayPreference(screen);
-        BillingCyclePreference preference = screen.findPreference(getPreferenceKey());
-
-        NetworkTemplate template = DataUsageLib.getMobileTemplate(mContext, mSubscriptionId);
-
-        preference.setTemplate(template, mSubscriptionId);
-    }
-
-    @Override
-    public int getAvailabilityStatus() {
-        return DataUsageUtils.hasMobileData(mContext) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
-    }
-}
diff --git a/src/com/android/settings/datausage/BillingCyclePreferenceController.kt b/src/com/android/settings/datausage/BillingCyclePreferenceController.kt
new file mode 100644
index 0000000..f699743
--- /dev/null
+++ b/src/com/android/settings/datausage/BillingCyclePreferenceController.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.datausage
+
+import android.content.Context
+import android.telephony.SubscriptionManager
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.datausage.lib.DataUsageLib.getMobileTemplate
+import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem
+import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
+
+class BillingCyclePreferenceController(context: Context, preferenceKey: String) :
+    BasePreferenceController(context, preferenceKey) {
+    private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+
+    fun init(subId: Int) {
+        this.subId = subId
+    }
+
+    override fun getAvailabilityStatus() =
+        if (DataUsageUtils.hasMobileData(mContext)) AVAILABLE else CONDITIONALLY_UNAVAILABLE
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        super.displayPreference(screen)
+        val preference = screen.findPreference<BillingCyclePreference>(preferenceKey)
+        val template = getMobileTemplate(mContext, subId)
+        preference?.setTemplate(template, subId)
+    }
+
+    companion object {
+        class BillingCycleSearchItem(private val context: Context) :
+            MobileNetworkSettingsSearchItem {
+            override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? {
+                if (!DataUsageUtils.hasMobileData(context)) return null
+                return MobileNetworkSettingsSearchResult(
+                    key = "billing_preference",
+                    title = context.getString(R.string.billing_cycle),
+                )
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/development/DevelopmentMemtagFooterPreferenceController.java b/src/com/android/settings/development/DevelopmentMemtagFooterPreferenceController.java
index a5612ca..f2c9f59 100644
--- a/src/com/android/settings/development/DevelopmentMemtagFooterPreferenceController.java
+++ b/src/com/android/settings/development/DevelopmentMemtagFooterPreferenceController.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.settings.security;
+package com.android.settings.development;
 
 import android.content.Context;
 import android.text.TextUtils;
diff --git a/src/com/android/settings/deviceinfo/StorageItemPreference.java b/src/com/android/settings/deviceinfo/StorageItemPreference.java
index 91102a0..62c0a6a 100644
--- a/src/com/android/settings/deviceinfo/StorageItemPreference.java
+++ b/src/com/android/settings/deviceinfo/StorageItemPreference.java
@@ -22,6 +22,7 @@
 import android.util.AttributeSet;
 import android.widget.ProgressBar;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
@@ -48,6 +49,7 @@
         setLayoutResource(R.layout.storage_item);
     }
 
+    @VisibleForTesting
     public void setStorageSize(long size, long total) {
         setStorageSize(size, total, false /* animate */);
     }
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt b/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt
index 4f31e0f..83e3a31 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt
@@ -20,6 +20,7 @@
 import android.provider.Settings
 import android.telephony.SubscriptionInfo
 import com.android.settings.R
+import com.android.settings.datausage.BillingCyclePreferenceController.Companion.BillingCycleSearchItem
 import com.android.settings.network.SubscriptionUtil
 import com.android.settings.network.telephony.CarrierSettingsVersionPreferenceController.Companion.CarrierSettingsVersionSearchItem
 import com.android.settings.network.telephony.DataUsagePreferenceController.Companion.DataUsageSearchItem
@@ -117,6 +118,7 @@
 
         fun createSearchItems(context: Context): List<MobileNetworkSettingsSearchItem> =
             listOf(
+                BillingCycleSearchItem(context),
                 CarrierSettingsVersionSearchItem(context),
                 DataUsageSearchItem(context),
                 MmsMessageSearchItem(context),
diff --git a/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java b/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java
index d69b317..e5c6570 100644
--- a/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java
+++ b/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java
@@ -44,8 +44,8 @@
 
     @Override
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
-        preference.setEnabled(zenMode.isEnabled());
-        boolean allowingAll = zenMode.getRule().getInterruptionFilter() == INTERRUPTION_FILTER_ALL;
+        preference.setEnabled(zenMode.isEnabled() && zenMode.canEditPolicy());
+        boolean allowingAll = zenMode.getInterruptionFilter() == INTERRUPTION_FILTER_ALL;
 
         ((TwoStatePreference) preference).setChecked(allowingAll);
         preference.setSummary(allowingAll
@@ -57,7 +57,7 @@
     public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
         final boolean allowAll = ((Boolean) newValue);
         return saveMode(zenMode -> {
-            zenMode.getRule().setInterruptionFilter(allowAll
+            zenMode.setInterruptionFilter(allowAll
                     ? INTERRUPTION_FILTER_ALL
                     : INTERRUPTION_FILTER_PRIORITY);
             return zenMode;
diff --git a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
index c5beb36..fcd5fd5 100644
--- a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
+++ b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
@@ -228,7 +228,7 @@
             return false;
         }
 
-        modeToUpdate.getRule().setEnabled(true);
+        modeToUpdate.setEnabled(true);
         mBackend.updateMode(modeToUpdate);
         return true;
     }
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
index 7b17f0c..c133f51 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -92,7 +92,7 @@
 
     @Override
     public boolean isAvailable(ZenMode zenMode) {
-        return zenMode.getRule().getInterruptionFilter() != INTERRUPTION_FILTER_ALL;
+        return zenMode.getInterruptionFilter() != INTERRUPTION_FILTER_ALL;
     }
 
     @Override
@@ -102,7 +102,7 @@
         preference.setIntent(
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModeAppsFragment.class,
                         zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
-        preference.setEnabled(zenMode.isEnabled());
+        preference.setEnabled(zenMode.isEnabled() && zenMode.canEditPolicy());
 
         mZenMode = zenMode;
         mPreference = (CircularIconsPreference) preference;
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
index b0d3952..afd9b76 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
@@ -74,7 +74,7 @@
                     updatedEffects.setShouldUseNightMode(allow);
                     break;
             }
-            zenMode.getRule().setDeviceEffects(updatedEffects.build());
+            zenMode.setDeviceEffects(updatedEffects.build());
             return zenMode;
         });
     }
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
index 57dce89..14c8cb6 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
@@ -45,7 +45,7 @@
         preference.setIntent(
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModeDisplayFragment.class,
                         zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
-        preference.setEnabled(zenMode.isEnabled());
+        preference.setEnabled(zenMode.isEnabled() && zenMode.canEditPolicy());
     }
 
     @Override
diff --git a/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java
index 326bc97..18e3fc1 100644
--- a/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java
@@ -50,7 +50,8 @@
         if (mSchedule.exitAtAlarm != exitAtAlarm) {
             mSchedule.exitAtAlarm = exitAtAlarm;
             return saveMode(mode -> {
-                mode.getRule().setConditionId(ZenModeConfig.toScheduleConditionId(mSchedule));
+                mode.setCustomModeConditionId(mContext,
+                        ZenModeConfig.toScheduleConditionId(mSchedule));
                 return mode;
             });
         }
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index 8eef708..08075b4 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -171,7 +171,7 @@
             } else if (menuItem.getItemId() == DELETE_MODE) {
                 new AlertDialog.Builder(mContext)
                         .setTitle(mContext.getString(R.string.zen_mode_delete_mode_confirmation,
-                                mZenMode.getRule().getName()))
+                                mZenMode.getName()))
                         .setPositiveButton(R.string.zen_mode_schedule_delete,
                                 (dialog, which) -> {
                                     // start finishing before calling removeMode() so that we
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
index cd1e8c7..f08a05d 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
@@ -41,7 +41,7 @@
 
     @Override
     public boolean isAvailable(ZenMode zenMode) {
-        return zenMode.getRule().getInterruptionFilter() != INTERRUPTION_FILTER_ALL;
+        return zenMode.getInterruptionFilter() != INTERRUPTION_FILTER_ALL;
     }
 
     @Override
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index 9613d98..9adc768 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -61,7 +61,7 @@
 
     @Override
     public boolean isAvailable(ZenMode zenMode) {
-        return zenMode.getRule().getInterruptionFilter() != INTERRUPTION_FILTER_ALL;
+        return zenMode.getInterruptionFilter() != INTERRUPTION_FILTER_ALL;
     }
 
     @Override
@@ -70,7 +70,7 @@
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModeOtherFragment.class,
                         zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
 
-        preference.setEnabled(zenMode.isEnabled());
+        preference.setEnabled(zenMode.isEnabled() && zenMode.canEditPolicy());
         preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
         ((CircularIconsPreference) preference).setIcons(getSoundIcons(zenMode.getPolicy()));
     }
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index bf55471..c635ad8 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -84,7 +84,7 @@
 
     @Override
     public boolean isAvailable(ZenMode zenMode) {
-        return zenMode.getRule().getInterruptionFilter() != INTERRUPTION_FILTER_ALL;
+        return zenMode.getInterruptionFilter() != INTERRUPTION_FILTER_ALL;
     }
 
     @Override
@@ -94,7 +94,7 @@
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModePeopleFragment.class,
                         zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
 
-        preference.setEnabled(zenMode.isEnabled());
+        preference.setEnabled(zenMode.isEnabled() && zenMode.canEditPolicy());
         preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode.getPolicy()));
         ((CircularIconsPreference) preference).setIcons(getPeopleIcons(zenMode.getPolicy()),
                 PEOPLE_ITEM_EQUIVALENCE);
diff --git a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
index c5300da..483b8f0 100644
--- a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
+++ b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
@@ -209,48 +209,46 @@
         boolean isFirst = true;
         List<String> enabledEffects = new ArrayList<>();
         if (!zenMode.getPolicy().shouldShowAllVisualEffects()
-                && zenMode.getRule().getInterruptionFilter() != INTERRUPTION_FILTER_ALL) {
+                && zenMode.getInterruptionFilter() != INTERRUPTION_FILTER_ALL) {
             enabledEffects.add(getBlockedEffectsSummary(zenMode));
             isFirst = false;
         }
-        ZenDeviceEffects currEffects = zenMode.getRule().getDeviceEffects();
-        if (currEffects != null) {
-            if (currEffects.shouldDisplayGrayscale()) {
-                if (isFirst) {
-                    enabledEffects.add(mContext.getString(R.string.mode_grayscale_title));
-                } else {
-                    enabledEffects.add(mContext.getString(
-                            R.string.mode_grayscale_title_secondary_list));
-                }
-                isFirst = false;
+        ZenDeviceEffects currEffects = zenMode.getDeviceEffects();
+        if (currEffects.shouldDisplayGrayscale()) {
+            if (isFirst) {
+                enabledEffects.add(mContext.getString(R.string.mode_grayscale_title));
+            } else {
+                enabledEffects.add(mContext.getString(
+                        R.string.mode_grayscale_title_secondary_list));
             }
-            if (currEffects.shouldSuppressAmbientDisplay()) {
-                if (isFirst) {
-                    enabledEffects.add(mContext.getString(R.string.mode_aod_title));
-                } else {
-                    enabledEffects.add(mContext.getString(
-                            R.string.mode_aod_title_secondary_list));
-                }
-                isFirst = false;
+            isFirst = false;
+        }
+        if (currEffects.shouldSuppressAmbientDisplay()) {
+            if (isFirst) {
+                enabledEffects.add(mContext.getString(R.string.mode_aod_title));
+            } else {
+                enabledEffects.add(mContext.getString(
+                        R.string.mode_aod_title_secondary_list));
             }
-            if (currEffects.shouldDimWallpaper()) {
-                if (isFirst) {
-                    enabledEffects.add(mContext.getString(R.string.mode_wallpaper_title));
-                } else {
-                    enabledEffects.add(mContext.getString(
-                            R.string.mode_wallpaper_title_secondary_list));
-                }
-                isFirst = false;
+            isFirst = false;
+        }
+        if (currEffects.shouldDimWallpaper()) {
+            if (isFirst) {
+                enabledEffects.add(mContext.getString(R.string.mode_wallpaper_title));
+            } else {
+                enabledEffects.add(mContext.getString(
+                        R.string.mode_wallpaper_title_secondary_list));
             }
-            if (currEffects.shouldUseNightMode()) {
-                if (isFirst) {
-                    enabledEffects.add(mContext.getString(R.string.mode_dark_theme_title));
-                } else {
-                    enabledEffects.add(mContext.getString(
-                            R.string.mode_dark_theme_title_secondary_list));
-                }
-                isFirst = false;
+            isFirst = false;
+        }
+        if (currEffects.shouldUseNightMode()) {
+            if (isFirst) {
+                enabledEffects.add(mContext.getString(R.string.mode_dark_theme_title));
+            } else {
+                enabledEffects.add(mContext.getString(
+                        R.string.mode_dark_theme_title_secondary_list));
             }
+            isFirst = false;
         }
 
         int numCategories = enabledEffects.size();
diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
index f2302c0..1933635 100644
--- a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
@@ -87,7 +87,7 @@
 
         mModeName = zenMode.getName();
         PrimarySwitchPreference triggerPref = (PrimarySwitchPreference) preference;
-        triggerPref.setChecked(zenMode.getRule().isEnabled());
+        triggerPref.setChecked(zenMode.isEnabled());
         triggerPref.setOnPreferenceChangeListener(mSwitchChangeListener);
         if (zenMode.isSystemOwned()) {
             setUpForSystemOwnedTrigger(triggerPref, zenMode);
@@ -213,7 +213,7 @@
 
     private void setModeEnabled(boolean enabled) {
         saveMode((zenMode) -> {
-            if (enabled != zenMode.getRule().isEnabled()) {
+            if (enabled != zenMode.isEnabled()) {
                 zenMode.getRule().setEnabled(enabled);
             }
             return zenMode;
diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
index 5e36469..75d4ee3 100644
--- a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
@@ -118,7 +118,7 @@
         for (ZenMode mode : mBackend.getModes()) {
             SearchIndexableRaw data = new SearchIndexableRaw(mContext);
             data.key = mode.getId();
-            data.title = mode.getRule().getName();
+            data.title = mode.getName();
             data.screenTitle = res.getString(R.string.zen_modes_list_title);
             rawData.add(data);
         }
diff --git a/src/com/android/settings/utils/FileSizeFormatter.java b/src/com/android/settings/utils/FileSizeFormatter.java
deleted file mode 100644
index 5950d5d..0000000
--- a/src/com/android/settings/utils/FileSizeFormatter.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.utils;
-
-import android.content.Context;
-import android.icu.text.DecimalFormat;
-import android.icu.text.MeasureFormat;
-import android.icu.text.NumberFormat;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
-import android.text.BidiFormatter;
-import android.text.TextUtils;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.math.BigDecimal;
-import java.util.Locale;
-
-/**
- * Utility class to aid in formatting file sizes always with the same unit. This is modified from
- * android.text.format.Formatter to fit this purpose.
- */
-public final class FileSizeFormatter {
-    public static final long KILOBYTE_IN_BYTES = 1000;
-    public static final long MEGABYTE_IN_BYTES = KILOBYTE_IN_BYTES * 1000;
-    public static final long GIGABYTE_IN_BYTES = MEGABYTE_IN_BYTES * 1000;
-
-    private static class RoundedBytesResult {
-        public final float value;
-        public final MeasureUnit units;
-        public final int fractionDigits;
-        public final long roundedBytes;
-
-        private RoundedBytesResult(
-                float value, MeasureUnit units, int fractionDigits, long roundedBytes) {
-            this.value = value;
-            this.units = units;
-            this.fractionDigits = fractionDigits;
-            this.roundedBytes = roundedBytes;
-        }
-    }
-
-    private static Locale localeFromContext(@NonNull Context context) {
-        return context.getResources().getConfiguration().locale;
-    }
-
-    private static String bidiWrap(@NonNull Context context, String source) {
-        final Locale locale = localeFromContext(context);
-        if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) {
-            return BidiFormatter.getInstance(true /* RTL*/).unicodeWrap(source);
-        } else {
-            return source;
-        }
-    }
-
-    private static NumberFormat getNumberFormatter(Locale locale, int fractionDigits) {
-        final NumberFormat numberFormatter = NumberFormat.getInstance(locale);
-        numberFormatter.setMinimumFractionDigits(fractionDigits);
-        numberFormatter.setMaximumFractionDigits(fractionDigits);
-        numberFormatter.setGroupingUsed(false);
-        if (numberFormatter instanceof DecimalFormat) {
-            // We do this only for DecimalFormat, since in the general NumberFormat case, calling
-            // setRoundingMode may throw an exception.
-            numberFormatter.setRoundingMode(BigDecimal.ROUND_HALF_UP);
-        }
-        return numberFormatter;
-    }
-
-    private static String formatMeasureShort(Locale locale, NumberFormat numberFormatter,
-            float value, MeasureUnit units) {
-        final MeasureFormat measureFormatter = MeasureFormat.getInstance(
-                locale, MeasureFormat.FormatWidth.SHORT, numberFormatter);
-        return measureFormatter.format(new Measure(value, units));
-    }
-
-    private static String formatRoundedBytesResult(
-            @NonNull Context context, @NonNull RoundedBytesResult input) {
-        final Locale locale = localeFromContext(context);
-        final NumberFormat numberFormatter = getNumberFormatter(locale, input.fractionDigits);
-        return formatMeasureShort(locale, numberFormatter, input.value, input.units);
-    }
-
-    /**
-     * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc.
-     *
-     * <p>As of O, the prefixes are used in their standard meanings in the SI system, so kB = 1000
-     * bytes, MB = 1,000,000 bytes, etc.
-     *
-     * <p class="note">In {@link android.os.Build.VERSION_CODES#N} and earlier, powers of 1024 are
-     * used instead, with KB = 1024 bytes, MB = 1,048,576 bytes, etc.
-     *
-     * <p>If the context has a right-to-left locale, the returned string is wrapped in bidi
-     * formatting characters to make sure it's displayed correctly if inserted inside a
-     * right-to-left string. (This is useful in cases where the unit strings, like "MB", are
-     * left-to-right, but the locale is right-to-left.)
-     *
-     * @param context Context to use to load the localized units
-     * @param sizeBytes size value to be formatted, in bytes
-     * @param unit The unit used for formatting.
-     * @param mult Amount of bytes in the unit.
-     * @return formatted string with the number
-     */
-    public static String formatFileSize(
-            @Nullable Context context, long sizeBytes, MeasureUnit unit, long mult) {
-        if (context == null) {
-            return "";
-        }
-        final RoundedBytesResult res = formatBytes(sizeBytes, unit, mult);
-        return bidiWrap(context, formatRoundedBytesResult(context, res));
-    }
-
-    /**
-     * A simplified version of the SettingsLib file size formatter. The primary difference is that
-     * this version always assumes it is doing a "short file size" and allows for a suffix to be
-     * provided.
-     *
-     * @param res Resources to fetch strings with.
-     * @param sizeBytes File size in bytes to format.
-     * @param suffix String id for the unit suffix.
-     * @param mult Amount of bytes in the unit.
-     */
-    private static RoundedBytesResult formatBytes(
-            long sizeBytes, MeasureUnit unit, long mult) {
-        final boolean isNegative = (sizeBytes < 0);
-        float result = isNegative ? -sizeBytes : sizeBytes;
-        result = result / mult;
-        // Note we calculate the rounded long by ourselves, but still let String.format()
-        // compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to
-        // floating point errors.
-        final int roundFactor;
-        final int roundDigits;
-        if (mult == 1) {
-            roundFactor = 1;
-            roundDigits = 0;
-        } else if (result < 1) {
-            roundFactor = 100;
-            roundDigits = 2;
-        } else if (result < 10) {
-            roundFactor = 10;
-            roundDigits = 1;
-        } else { // 10 <= result < 100
-            roundFactor = 1;
-            roundDigits = 0;
-        }
-
-        if (isNegative) {
-            result = -result;
-        }
-
-        // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so
-        // it's okay (for now)...
-        final long roundedBytes = (((long) Math.round(result * roundFactor)) * mult / roundFactor);
-
-        return new RoundedBytesResult(result, unit, roundDigits, roundedBytes);
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
index 5073119..d1533c9 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -75,6 +75,8 @@
 import com.android.settings.testutils.shadow.ShadowThreadUtils;
 import com.android.settings.widget.SettingsMainSwitchBar;
 import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
@@ -117,6 +119,8 @@
 public class AudioSharingSwitchBarControllerTest {
     private static final String TEST_DEVICE_NAME1 = "test1";
     private static final String TEST_DEVICE_NAME2 = "test2";
+    private static final String TEST_DEVICE_ANONYMIZED_ADDR1 = "XX:XX:01";
+    private static final String TEST_DEVICE_ANONYMIZED_ADDR2 = "XX:XX:02";
     private static final int TEST_DEVICE_GROUP_ID1 = 1;
     private static final int TEST_DEVICE_GROUP_ID2 = 2;
     private static final Correspondence<Fragment, String> TAG_EQUALS =
@@ -133,6 +137,7 @@
     @Spy Context mContext = ApplicationProvider.getApplicationContext();
     @Mock private LocalBluetoothManager mLocalBtManager;
     @Mock private CachedBluetoothDeviceManager mDeviceManager;
+    @Mock private BluetoothEventManager mEventManager;
     @Mock private LocalBluetoothProfileManager mBtProfileManager;
     @Mock private LocalBluetoothLeBroadcast mBroadcast;
     @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@@ -168,11 +173,14 @@
         mFeatureFactory = FakeFeatureFactory.setupForTest();
         when(localBluetoothManager.getProfileManager()).thenReturn(mBtProfileManager);
         when(localBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
+        when(localBluetoothManager.getEventManager()).thenReturn(mEventManager);
+        when(mDevice1.getAnonymizedAddress()).thenReturn(TEST_DEVICE_ANONYMIZED_ADDR1);
         when(mDeviceManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
         when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
         when(mCachedDevice1.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID1);
         when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
         when(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false);
+        when(mDevice2.getAnonymizedAddress()).thenReturn(TEST_DEVICE_ANONYMIZED_ADDR2);
         when(mDeviceManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
         when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
         when(mCachedDevice2.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID2);
@@ -308,6 +316,7 @@
         verify(mAssistant, never())
                 .registerServiceCallBack(
                         any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
+        verify(mEventManager, never()).registerCallback(any(BluetoothCallback.class));
         verify(mBtProfileManager).addServiceListener(mController);
         assertThat(mSwitchBar.isChecked()).isFalse();
         assertThat(mSwitchBar.isEnabled()).isFalse();
@@ -328,6 +337,7 @@
         verify(mAssistant)
                 .registerServiceCallBack(
                         any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
+        verify(mEventManager).registerCallback(any(BluetoothCallback.class));
         verify(mBtProfileManager, never()).addServiceListener(mController);
         assertThat(mSwitchBar.isChecked()).isTrue();
         assertThat(mSwitchBar.isEnabled()).isTrue();
@@ -342,6 +352,7 @@
                 .unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
         verify(mAssistant, never())
                 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
+        verify(mEventManager, never()).unregisterCallback(any(BluetoothCallback.class));
         verify(mBtProfileManager, never()).removeServiceListener(mController);
     }
 
@@ -358,6 +369,7 @@
                 .unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
         verify(mAssistant, never())
                 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
+        verify(mEventManager, never()).unregisterCallback(any(BluetoothCallback.class));
     }
 
     @Test
@@ -374,6 +386,7 @@
         verify(mBroadcast).unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
         verify(mAssistant)
                 .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
+        verify(mEventManager).unregisterCallback(any(BluetoothCallback.class));
     }
 
     @Test
@@ -599,9 +612,11 @@
         mOnAudioSharingStateChanged = false;
         mSwitchBar.setChecked(false);
         when(mBroadcast.isEnabled(any())).thenReturn(false);
+        when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2));
         mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 1);
         shadowOf(Looper.getMainLooper()).idle();
         assertThat(mSwitchBar.isChecked()).isFalse();
+        assertThat(mSwitchBar.isEnabled()).isTrue();
         assertThat(mOnAudioSharingStateChanged).isFalse();
         verify(mFeatureFactory.metricsFeatureProvider)
                 .action(
@@ -613,6 +628,7 @@
         mController.mBroadcastCallback.onBroadcastStarted(/* reason= */ 1, /* broadcastId= */ 1);
         shadowOf(Looper.getMainLooper()).idle();
         assertThat(mSwitchBar.isChecked()).isTrue();
+        assertThat(mSwitchBar.isEnabled()).isTrue();
         assertThat(mOnAudioSharingStateChanged).isTrue();
 
         mOnAudioSharingStateChanged = false;
@@ -627,9 +643,11 @@
                         SettingsEnums.AUDIO_SHARING_SETTINGS);
 
         when(mBroadcast.isEnabled(any())).thenReturn(false);
+        when(mCachedDevice2.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false);
         mController.mBroadcastCallback.onBroadcastStopped(/* reason= */ 1, /* broadcastId= */ 1);
         shadowOf(Looper.getMainLooper()).idle();
         assertThat(mSwitchBar.isChecked()).isFalse();
+        assertThat(mSwitchBar.isEnabled()).isFalse();
         assertThat(mOnAudioSharingStateChanged).isTrue();
     }
 
@@ -683,6 +701,34 @@
     }
 
     @Test
+    public void onActiveDeviceChanged_leaProfile_updateSwitch() {
+        mSwitchBar.setChecked(true);
+        mSwitchBar.setEnabled(false);
+        when(mBroadcast.isEnabled(null)).thenReturn(false);
+        when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
+        mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.LE_AUDIO);
+        shadowOf(Looper.getMainLooper()).idle();
+        assertThat(mSwitchBar.isChecked()).isFalse();
+        verify(mSwitchBar).setEnabled(true);
+    }
+
+    @Test
+    public void onActiveDeviceChanged_nullActiveDevice_doNothing() {
+        mController.onActiveDeviceChanged(/* activeDevice= */ null, BluetoothProfile.LE_AUDIO);
+        shadowOf(Looper.getMainLooper()).idle();
+        verify(mSwitchBar, never()).setEnabled(anyBoolean());
+        verify(mSwitchBar, never()).setChecked(anyBoolean());
+    }
+
+    @Test
+    public void onActiveDeviceChanged_notLeaProfile_doNothing() {
+        mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.HEADSET);
+        shadowOf(Looper.getMainLooper()).idle();
+        verify(mSwitchBar, never()).setEnabled(anyBoolean());
+        verify(mSwitchBar, never()).setChecked(anyBoolean());
+    }
+
+    @Test
     public void testAccessibilityDelegate() {
         View view = new View(mContext);
         AccessibilityEvent event =
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java b/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java
index 984d945..eba8e11 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.settings.deviceinfo;
 
-import static com.android.settings.utils.FileSizeFormatter.MEGABYTE_IN_BYTES;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
@@ -36,6 +34,7 @@
 @RunWith(RobolectricTestRunner.class)
 public class StorageItemPreferenceTest {
 
+    private static final long MEGABYTE_IN_BYTES = 1000_000;
     private Context mContext;
     private StorageItemPreference mPreference;
 
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/NonCurrentUserControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/NonCurrentUserControllerTest.java
index 61d3bed..34cc4fd 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/NonCurrentUserControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/NonCurrentUserControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.settings.deviceinfo.storage;
 
-import static com.android.settings.utils.FileSizeFormatter.MEGABYTE_IN_BYTES;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyString;
@@ -61,6 +59,7 @@
 
     private static final String TEST_NAME = "Fred";
     private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_secondary_users";
+    private static final long MEGABYTE_IN_BYTES = 1000_000;
     @Mock
     private UserManager mUserManager;
     @Mock
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
index 306d80c..a9e9eac 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
@@ -16,9 +16,6 @@
 package com.android.settings.deviceinfo.storage;
 
 import static com.android.settings.applications.manageapplications.ManageApplications.EXTRA_WORK_ID;
-import static com.android.settings.utils.FileSizeFormatter.GIGABYTE_IN_BYTES;
-import static com.android.settings.utils.FileSizeFormatter.KILOBYTE_IN_BYTES;
-import static com.android.settings.utils.FileSizeFormatter.MEGABYTE_IN_BYTES;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -74,6 +71,10 @@
 })
 public class StorageItemPreferenceControllerTest {
 
+    private static final long KILOBYTE_IN_BYTES = 1000;
+    private static final long MEGABYTE_IN_BYTES = KILOBYTE_IN_BYTES * 1000;
+    private static final long GIGABYTE_IN_BYTES = MEGABYTE_IN_BYTES * 1000;
+
     private Context mContext;
     private VolumeInfo mVolume;
     @Mock
diff --git a/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java
index 777d213..8653d95 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.settings.notification.modes;
 
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.ZenPolicy.STATE_DISALLOW;
 
@@ -68,6 +69,26 @@
     }
 
     @Test
+    public void updateState_dnd_enabled() {
+        TwoStatePreference preference = mock(TwoStatePreference.class);
+        ZenMode dnd = TestModeBuilder.MANUAL_DND_ACTIVE;
+
+        mController.updateState(preference, dnd);
+
+        verify(preference).setEnabled(true);
+    }
+
+    @Test
+    public void updateState_specialDnd_disabled() {
+        TwoStatePreference preference = mock(TwoStatePreference.class);
+        ZenMode specialDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, true);
+
+        mController.updateState(preference, specialDnd);
+
+        verify(preference).setEnabled(false);
+    }
+
+    @Test
     public void testUpdateState_disabled() {
         TwoStatePreference preference = mock(TwoStatePreference.class);
         ZenMode zenMode = new TestModeBuilder()
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
index 4148fa3..fa83f30 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
@@ -150,6 +151,20 @@
     }
 
     @Test
+    public void updateState_dnd_enabled() {
+        ZenMode dnd = TestModeBuilder.MANUAL_DND_ACTIVE;
+        mController.updateState(mPreference, dnd);
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_specialDnd_disabled() {
+        ZenMode specialDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, true);
+        mController.updateState(mPreference, specialDnd);
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
     public void testUpdateState_disabled() {
         ZenMode zenMode = new TestModeBuilder()
                 .setEnabled(false)
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java
index 7cf0109..05486e0 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -64,6 +66,26 @@
     }
 
     @Test
+    public void updateState_dnd_enabled() {
+        Preference preference = mock(Preference.class);
+        ZenMode dnd = TestModeBuilder.MANUAL_DND_ACTIVE;
+
+        mController.updateState(preference, dnd);
+
+        verify(preference).setEnabled(true);
+    }
+
+    @Test
+    public void updateState_specialDnd_disabled() {
+        Preference preference = mock(Preference.class);
+        ZenMode specialDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, true);
+
+        mController.updateState(preference, specialDnd);
+
+        verify(preference).setEnabled(false);
+    }
+
+    @Test
     public void testUpdateState_disabled() {
         Preference preference = mock(Preference.class);
         ZenMode zenMode = new TestModeBuilder()
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java
index 3efa5f0..c949fb8 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.notification.modes;
 
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -77,6 +79,7 @@
         scheduleInfo.exitAtAlarm = false;
 
         ZenMode mode = new TestModeBuilder()
+                .setPackage(PACKAGE_ANDROID)
                 .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
                 .build();
 
@@ -105,6 +108,7 @@
         scheduleInfo.exitAtAlarm = true;
 
         ZenMode mode = new TestModeBuilder()
+                .setPackage(PACKAGE_ANDROID)
                 .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
                 .build();
         mPrefController.updateZenMode(preference, mode);
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
index 3db70fa..38790b2 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.mock;
@@ -62,6 +64,26 @@
     }
 
     @Test
+    public void updateState_dnd_enabled() {
+        CircularIconsPreference preference = mock(CircularIconsPreference.class);
+        ZenMode dnd = TestModeBuilder.MANUAL_DND_ACTIVE;
+
+        mController.updateState(preference, dnd);
+
+        verify(preference).setEnabled(true);
+    }
+
+    @Test
+    public void updateState_specialDnd_disabled() {
+        CircularIconsPreference preference = mock(CircularIconsPreference.class);
+        ZenMode specialDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, true);
+
+        mController.updateState(preference, specialDnd);
+
+        verify(preference).setEnabled(false);
+    }
+
+    @Test
     public void updateState_disabled() {
         CircularIconsPreference pref = mock(CircularIconsPreference.class);
         ZenMode zenMode = new TestModeBuilder()
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
index 8555d71..85fd004 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
@@ -117,6 +118,20 @@
     }
 
     @Test
+    public void updateState_dnd_enabled() {
+        ZenMode dnd = TestModeBuilder.MANUAL_DND_ACTIVE;
+        mController.updateState(mPreference, dnd);
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_specialDnd_disabled() {
+        ZenMode specialDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, true);
+        mController.updateState(mPreference, specialDnd);
+        assertThat(mPreference.isEnabled()).isFalse();
+    }
+
+    @Test
     public void updateState_disabled() {
         ZenMode zenMode = new TestModeBuilder()
                 .setEnabled(false)