Merge "Data only device(without FEATURE_TELEPHON_CALLING) : Add try/catch to handle UnsupportedOperationException" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6ad4c05..854d409 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -140,6 +140,7 @@
     <uses-permission android:name="android.permission.ACCESS_GPU_SERVICE" />
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
     <uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
+    <uses-permission android:name="android.permission.RESTART_PHONE_PROCESS" />
 
     <application
             android:name=".SettingsApplication"
diff --git a/res/layout/data_usage_summary_preference.xml b/res/layout/data_usage_summary_preference.xml
index 24399ac..4cbd958 100644
--- a/res/layout/data_usage_summary_preference.xml
+++ b/res/layout/data_usage_summary_preference.xml
@@ -18,8 +18,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingTop="22dp"
-    android:paddingBottom="32dp"
+    android:paddingTop="8dp"
+    android:paddingBottom="16dp"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:orientation="vertical"
@@ -99,6 +99,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingTop="12dp"
+        android:minHeight="54dp"
         android:orientation="vertical">
 
         <TextView
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f287439..66e7321 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1216,7 +1216,7 @@
     <!-- Summary for the Private Space page. [CHAR LIMIT=NONE] -->
     <string name="private_space_summary">Keep private apps locked and hidden</string>
     <!-- Description for the Private Space page. [CHAR LIMIT=NONE] -->
-    <string name="private_space_description">Hide apps in a private folder that only you can access</string>
+    <string name="private_space_description">Keep private apps in a separate space that you can hide or lock</string>
     <!-- Title for the Private Space one lock preference. [CHAR LIMIT=60] -->
     <string name="private_space_lock_title">Private Space lock</string>
     <!-- Description for the Private Space one lock preference page. [CHAR LIMIT=NONE] -->
@@ -12697,4 +12697,7 @@
 
     <!-- Content description for setting password complete-->
     <string name="accessibility_setup_password_complete">Password is now set up</string>
+
+    <!-- Authority of the content provider that support methods restartPhoneProcess and restartRild. Will be overlaid by OEM.-->
+    <string name="reset_telephony_stack_content_provider_authority" translatable="false"></string>
 </resources>
diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml
index cb4167b..364dd3d 100644
--- a/res/xml/connected_devices_advanced.xml
+++ b/res/xml/connected_devices_advanced.xml
@@ -33,7 +33,7 @@
         android:icon="@drawable/ic_bt_audio_sharing"
         settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingPreferenceController"/>
 
-    <Preference
+    <com.android.settingslib.RestrictedPreference
         android:fragment="com.android.settings.connecteddevice.NfcAndPaymentFragment"
         android:key="nfc_and_payment_settings"
         android:title="@string/nfc_quick_toggle_title"
@@ -61,7 +61,7 @@
         android:fragment="com.android.settings.print.PrintSettingsFragment"
         android:order="-3"/>
 
-    <SwitchPreferenceCompat
+    <com.android.settingslib.RestrictedSwitchPreference
         android:key="uwb_settings"
         android:title="@string/uwb_settings_title"
         android:order="100"
diff --git a/src/com/android/settings/ResetNetwork.java b/src/com/android/settings/ResetNetwork.java
index ebfa935..f05f65c 100644
--- a/src/com/android/settings/ResetNetwork.java
+++ b/src/com/android/settings/ResetNetwork.java
@@ -21,7 +21,6 @@
 import android.app.settings.SettingsEnums;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.provider.Settings;
@@ -128,6 +127,8 @@
                         | ResetNetworkRequest.RESET_VPN_MANAGER;
         if (Flags.resetMobileNetworkSettings()) {
             resetOptions |= ResetNetworkRequest.RESET_IMS_STACK;
+            resetOptions |= ResetNetworkRequest.RESET_PHONE_PROCESS;
+            resetOptions |= ResetNetworkRequest.RESET_RILD;
         }
         ResetNetworkRequest request = new ResetNetworkRequest(resetOptions);
         if (mSubscriptions != null && mSubscriptions.size() > 0) {
diff --git a/src/com/android/settings/ResetNetworkRequest.java b/src/com/android/settings/ResetNetworkRequest.java
index 71c12b1..4be8b32 100644
--- a/src/com/android/settings/ResetNetworkRequest.java
+++ b/src/com/android/settings/ResetNetworkRequest.java
@@ -51,6 +51,12 @@
     /* Reset option - reset IMS stack */
     public static final int RESET_IMS_STACK = 0x20;
 
+    /* Reset option - reset phone process */
+    public static final int RESET_PHONE_PROCESS = 0x40;
+
+    /* Reset option - reset RILD */
+    public static final int RESET_RILD = 0x80;
+
     /**
      *  Subscription ID indicates NOT resetting any of the components below:
      *  - TelephonyAndNetworkPolicy
@@ -264,6 +270,12 @@
         if ((mResetOptions & RESET_IMS_STACK) != 0) {
             builder.resetIms(mSubscriptionIdToResetIms);
         }
+        if ((mResetOptions & RESET_PHONE_PROCESS) != 0) {
+            builder.restartPhoneProcess();
+        }
+        if ((mResetOptions & RESET_RILD) != 0) {
+            builder.restartRild();
+        }
         return builder;
     }
 }
diff --git a/src/com/android/settings/datausage/DataPlanInfo.kt b/src/com/android/settings/datausage/DataPlanInfo.kt
new file mode 100644
index 0000000..51eb592
--- /dev/null
+++ b/src/com/android/settings/datausage/DataPlanInfo.kt
@@ -0,0 +1,50 @@
+/*
+ * 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
+
+data class DataPlanInfo(
+
+    /** The number of registered plans, [0, N] */
+    val dataPlanCount: Int,
+
+    /**
+     * The size of the first registered plan if one exists or the size of the warning if it is set.
+     *
+     * Set to -1 if no plan information is available.
+     */
+    val dataPlanSize: Long,
+
+    /**
+     * The "size" of the data usage bar, i.e. the amount of data its rhs end represents.
+     *
+     * Set to -1 if not display a data usage bar.
+     */
+    val dataBarSize: Long,
+
+    /** The number of bytes used since the start of the cycle. */
+    val dataPlanUse: Long,
+
+    /**
+     * The ending time of the billing cycle in ms since the epoch.
+     *
+     * Set to `null` if no cycle information is available.
+     */
+    val cycleEnd: Long?,
+
+    /** The time of the last update in milliseconds since the epoch, or -1 if unknown. */
+    val snapshotTime: Long,
+)
diff --git a/src/com/android/settings/datausage/DataPlanRepository.kt b/src/com/android/settings/datausage/DataPlanRepository.kt
new file mode 100644
index 0000000..9d34200
--- /dev/null
+++ b/src/com/android/settings/datausage/DataPlanRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.net.NetworkPolicy
+import android.telephony.SubscriptionPlan
+import com.android.settings.datausage.lib.INetworkCycleDataRepository
+import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.getCycles
+import com.android.settings.datausage.lib.NetworkStatsRepository
+
+interface DataPlanRepository {
+    fun getDataPlanInfo(policy: NetworkPolicy, plans: List<SubscriptionPlan>): DataPlanInfo
+}
+
+class DataPlanRepositoryImpl(
+    private val networkCycleDataRepository: INetworkCycleDataRepository,
+) : DataPlanRepository {
+    override fun getDataPlanInfo(
+        policy: NetworkPolicy,
+        plans: List<SubscriptionPlan>,
+    ): DataPlanInfo {
+        getPrimaryPlan(plans)?.let { primaryPlan ->
+            val dataPlanSize = when (primaryPlan.dataLimitBytes) {
+                SubscriptionPlan.BYTES_UNLIMITED -> SubscriptionPlan.BYTES_UNKNOWN
+                else -> primaryPlan.dataLimitBytes
+            }
+            return DataPlanInfo(
+                dataPlanCount = plans.size,
+                dataPlanSize = dataPlanSize,
+                dataBarSize = dataPlanSize,
+                dataPlanUse = primaryPlan.dataUsageBytes,
+                cycleEnd = primaryPlan.cycleRule.end?.toInstant()?.toEpochMilli(),
+                snapshotTime = primaryPlan.dataUsageTime,
+            )
+        }
+
+        val cycle = policy.getCycles().firstOrNull()
+        val dataUsage = networkCycleDataRepository.queryUsage(
+            cycle ?: NetworkStatsRepository.AllTimeRange
+        ).usage
+        return DataPlanInfo(
+            dataPlanCount = 0,
+            dataPlanSize = SubscriptionPlan.BYTES_UNKNOWN,
+            dataBarSize = maxOf(dataUsage, policy.limitBytes, policy.warningBytes),
+            dataPlanUse = dataUsage,
+            cycleEnd = cycle?.upper,
+            snapshotTime = SubscriptionPlan.TIME_UNKNOWN,
+        )
+    }
+
+    companion object {
+        private const val PETA = 1_000_000_000_000_000L
+
+        private fun getPrimaryPlan(plans: List<SubscriptionPlan>): SubscriptionPlan? =
+            plans.firstOrNull()?.takeIf { plan ->
+                plan.dataLimitBytes > 0 && validSize(plan.dataUsageBytes) && plan.cycleRule != null
+            }
+
+        private fun validSize(value: Long): Boolean = value in 0L until PETA
+    }
+}
diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreference.java b/src/com/android/settings/datausage/DataUsageSummaryPreference.java
index f2fcddd..93d930c 100644
--- a/src/com/android/settings/datausage/DataUsageSummaryPreference.java
+++ b/src/com/android/settings/datausage/DataUsageSummaryPreference.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.graphics.Typeface;
 import android.icu.text.MessageFormat;
-import android.net.NetworkTemplate;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -32,13 +31,14 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
 import com.android.settingslib.Utils;
-import com.android.settingslib.net.DataUsageController;
 import com.android.settingslib.utils.StringUtil;
 
 import java.util.HashMap;
@@ -62,10 +62,9 @@
     private CharSequence mEndLabel;
 
     private int mNumPlans;
-    /** The specified un-initialized value for cycle time */
-    private static final long CYCLE_TIME_UNINITIAL_VALUE = 0;
     /** The ending time of the billing cycle in milliseconds since epoch. */
-    private long mCycleEndTimeMs;
+    @Nullable
+    private Long mCycleEndTimeMs;
     /** The time of the last update in standard milliseconds since the epoch */
     private long mSnapshotTimeMs;
     /** Name of carrier, or null if not available */
@@ -74,7 +73,6 @@
 
     /** Progress to display on ProgressBar */
     private float mProgress;
-    private boolean mHasMobileData;
 
     /**
      * The size of the first registered plan if one exists or the size of the warning if it is set.
@@ -102,7 +100,10 @@
         notifyChanged();
     }
 
-    public void setUsageInfo(long cycleEnd, long snapshotTime, CharSequence carrierName,
+    /**
+     * Sets the usage info.
+     */
+    public void setUsageInfo(@Nullable Long cycleEnd, long snapshotTime, CharSequence carrierName,
             int numPlans) {
         mCycleEndTimeMs = cycleEnd;
         mSnapshotTimeMs = snapshotTime;
@@ -124,15 +125,17 @@
         notifyChanged();
     }
 
-    void setUsageNumbers(long used, long dataPlanSize, boolean hasMobileData) {
+    /**
+     * Sets the usage numbers.
+     */
+    public void setUsageNumbers(long used, long dataPlanSize) {
         mDataplanUse = used;
         mDataplanSize = dataPlanSize;
-        mHasMobileData = hasMobileData;
         notifyChanged();
     }
 
     @Override
-    public void onBindViewHolder(PreferenceViewHolder holder) {
+    public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
 
         ProgressBar bar = getProgressBar(holder);
@@ -178,7 +181,7 @@
 
         final MeasurableLinearLayout layout = getLayout(holder);
 
-        if (mHasMobileData && mNumPlans >= 0 && mDataplanSize > 0L) {
+        if (mDataplanSize > 0L) {
             TextView usageRemainingField = getDataRemaining(holder);
             long dataRemaining = mDataplanSize - mDataplanUse;
             if (dataRemaining >= 0) {
@@ -204,7 +207,7 @@
         TextView cycleTime = getCycleTime(holder);
 
         // Takes zero as a special case which value is never set.
-        if (mCycleEndTimeMs == CYCLE_TIME_UNINITIAL_VALUE) {
+        if (mCycleEndTimeMs == null) {
             cycleTime.setVisibility(View.GONE);
             return;
         }
@@ -228,7 +231,7 @@
 
 
     private void updateCarrierInfo(TextView carrierInfo) {
-        if (mNumPlans > 0 && mSnapshotTimeMs >= 0L) {
+        if (mSnapshotTimeMs >= 0L) {
             carrierInfo.setVisibility(View.VISIBLE);
             long updateAgeMillis = calculateTruncatedUpdateAge();
 
@@ -294,13 +297,6 @@
     }
 
     @VisibleForTesting
-    protected long getHistoricalUsageLevel() {
-        final DataUsageController controller = new DataUsageController(getContext());
-        return controller.getHistoricalUsageLevel(
-                new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build());
-    }
-
-    @VisibleForTesting
     protected TextView getUsageTitle(PreferenceViewHolder holder) {
         return (TextView) holder.findViewById(R.id.usage_title);
     }
diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java
deleted file mode 100644
index 35f5931..0000000
--- a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.java
+++ /dev/null
@@ -1,273 +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.datausage;
-
-import android.app.Activity;
-import android.content.Context;
-import android.net.NetworkTemplate;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionPlan;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.RecurrenceRule;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-
-import com.android.internal.util.CollectionUtils;
-import com.android.settings.R;
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settings.datausage.lib.DataUsageLib;
-import com.android.settings.network.ProxySubscriptionManager;
-import com.android.settings.network.telephony.TelephonyBasePreferenceController;
-import com.android.settingslib.net.DataUsageController;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.List;
-import java.util.concurrent.Future;
-
-/**
- * This is the controller for a data usage header that retrieves carrier data from the new
- * subscriptions framework API if available. The controller reads subscription information from the
- * framework and falls back to legacy usage data if none are available.
- */
-public class DataUsageSummaryPreferenceController extends TelephonyBasePreferenceController
-        implements PreferenceControllerMixin {
-
-    private static final String TAG = "DataUsageController";
-    private static final String KEY = "status_header";
-    private static final long PETA = 1000000000000000L;
-
-    protected DataUsageController mDataUsageController;
-    protected DataUsageInfoController mDataInfoController;
-    private NetworkTemplate mDefaultTemplate;
-    private boolean mHasMobileData;
-
-    /** Name of the carrier, or null if not available */
-    private CharSequence mCarrierName;
-
-    /** The number of registered plans, [0,N] */
-    private int mDataplanCount;
-
-    /** The time of the last update in milliseconds since the epoch, or -1 if unknown */
-    private long mSnapshotTime;
-
-    /**
-     * The size of the first registered plan if one exists or the size of the warning if it is set.
-     * -1 if no information is available.
-     */
-    private long mDataplanSize;
-    /** The "size" of the data usage bar, i.e. the amount of data its rhs end represents */
-    private long mDataBarSize;
-    /** The number of bytes used since the start of the cycle. */
-    private long mDataplanUse;
-    /** The ending time of the billing cycle in ms since the epoch */
-    private long mCycleEnd;
-
-    private Future<Long> mHistoricalUsageLevel;
-
-    public DataUsageSummaryPreferenceController(Activity activity, int subscriptionId) {
-        super(activity, KEY);
-
-        init(subscriptionId);
-    }
-
-    /**
-     * Initialize based on subscription ID provided
-     * @param subscriptionId is the target subscriptionId
-     */
-    public void init(int subscriptionId) {
-        mSubId = subscriptionId;
-        mHasMobileData = DataUsageUtils.hasMobileData(mContext);
-        mDataUsageController = null;
-    }
-
-    protected void updateConfiguration(Context context,
-            int subscriptionId, SubscriptionInfo subInfo) {
-        mDataUsageController = createDataUsageController(context);
-        mDataUsageController.setSubscriptionId(subscriptionId);
-        mDataInfoController = new DataUsageInfoController();
-
-        if (subInfo != null) {
-            mDefaultTemplate = DataUsageLib.getMobileTemplate(context, subscriptionId);
-        }
-    }
-
-    @VisibleForTesting
-    DataUsageController createDataUsageController(Context context) {
-        return new DataUsageController(context);
-    }
-
-    @VisibleForTesting
-    DataUsageSummaryPreferenceController(
-            DataUsageController dataUsageController,
-            DataUsageInfoController dataInfoController,
-            NetworkTemplate defaultTemplate,
-            Activity activity,
-            int subscriptionId) {
-        super(activity, KEY);
-        mDataUsageController = dataUsageController;
-        mDataInfoController = dataInfoController;
-        mDefaultTemplate = defaultTemplate;
-        mHasMobileData = true;
-        mSubId = subscriptionId;
-    }
-
-    @VisibleForTesting
-    List<SubscriptionPlan> getSubscriptionPlans(int subscriptionId) {
-        return ProxySubscriptionManager.getInstance(mContext).get()
-                .getSubscriptionPlans(subscriptionId);
-    }
-
-    protected SubscriptionInfo getSubscriptionInfo(int subscriptionId) {
-        if (!mHasMobileData) {
-            return null;
-        }
-        return ProxySubscriptionManager.getInstance(mContext)
-                .getAccessibleSubscriptionInfo(subscriptionId);
-    }
-
-    @Override
-    public int getAvailabilityStatus(int subId) {
-        return getSubscriptionInfo(subId) != null ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        DataUsageSummaryPreference summaryPreference = (DataUsageSummaryPreference) preference;
-
-        final SubscriptionInfo subInfo = getSubscriptionInfo(mSubId);
-        if (subInfo == null) {
-            return;
-        }
-        if (mDataUsageController == null) {
-            updateConfiguration(mContext, mSubId, subInfo);
-        }
-
-        mHistoricalUsageLevel = ThreadUtils.postOnBackgroundThread(() ->
-                mDataUsageController.getHistoricalUsageLevel(mDefaultTemplate));
-
-        final DataUsageController.DataUsageInfo info =
-                mDataUsageController.getDataUsageInfo(mDefaultTemplate);
-
-        long usageLevel = info.usageLevel;
-
-        refreshDataplanInfo(info, subInfo);
-
-        if (info.warningLevel > 0 && info.limitLevel > 0) {
-            summaryPreference.setLimitInfo(TextUtils.expandTemplate(
-                    mContext.getText(R.string.cell_data_warning_and_limit),
-                    DataUsageUtils.formatDataUsage(mContext, info.warningLevel),
-                    DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
-        } else if (info.warningLevel > 0) {
-            summaryPreference.setLimitInfo(TextUtils.expandTemplate(
-                    mContext.getText(R.string.cell_data_warning),
-                    DataUsageUtils.formatDataUsage(mContext, info.warningLevel)));
-        } else if (info.limitLevel > 0) {
-            summaryPreference.setLimitInfo(TextUtils.expandTemplate(
-                    mContext.getText(R.string.cell_data_limit),
-                    DataUsageUtils.formatDataUsage(mContext, info.limitLevel)));
-        } else {
-            summaryPreference.setLimitInfo(null);
-        }
-
-        if ((mDataplanUse <= 0L) && (mSnapshotTime < 0)) {
-            Log.d(TAG, "Display data usage from history");
-            mDataplanUse = displayUsageLevel(usageLevel);
-            mSnapshotTime = -1L;
-        }
-
-        summaryPreference.setUsageNumbers(mDataplanUse, mDataplanSize, mHasMobileData);
-
-        if (mDataBarSize <= 0) {
-            summaryPreference.setChartEnabled(false);
-        } else {
-            summaryPreference.setChartEnabled(true);
-            summaryPreference.setLabels(DataUsageUtils.formatDataUsage(mContext, 0 /* sizeBytes */),
-                    DataUsageUtils.formatDataUsage(mContext, mDataBarSize));
-            summaryPreference.setProgress(mDataplanUse / (float) mDataBarSize);
-        }
-        summaryPreference.setUsageInfo(mCycleEnd, mSnapshotTime, mCarrierName, mDataplanCount);
-    }
-
-    private long displayUsageLevel(long usageLevel) {
-        if (usageLevel > 0) {
-            return usageLevel;
-        }
-        try {
-            usageLevel = mHistoricalUsageLevel.get();
-        } catch (Exception ex) {
-        }
-        return usageLevel;
-    }
-
-    // TODO(b/70950124) add test for this method once the robolectric shadow run script is
-    // completed (b/3526807)
-    private void refreshDataplanInfo(DataUsageController.DataUsageInfo info,
-            SubscriptionInfo subInfo) {
-        // reset data before overwriting
-        mCarrierName = null;
-        mDataplanCount = 0;
-        mDataplanSize = -1L;
-        mDataBarSize = mDataInfoController.getSummaryLimit(info);
-        mDataplanUse = info.usageLevel;
-        mCycleEnd = info.cycleEnd;
-        mSnapshotTime = -1L;
-
-        if (subInfo != null && mHasMobileData) {
-            mCarrierName = subInfo.getCarrierName();
-            final List<SubscriptionPlan> plans = getSubscriptionPlans(mSubId);
-            final SubscriptionPlan primaryPlan = getPrimaryPlan(plans);
-
-            if (primaryPlan != null) {
-                mDataplanCount = plans.size();
-                mDataplanSize = primaryPlan.getDataLimitBytes();
-                if (unlimited(mDataplanSize)) {
-                    mDataplanSize = -1L;
-                }
-                mDataBarSize = mDataplanSize;
-                mDataplanUse = primaryPlan.getDataUsageBytes();
-
-                RecurrenceRule rule = primaryPlan.getCycleRule();
-                if (rule != null && rule.start != null && rule.end != null) {
-                    mCycleEnd = rule.end.toEpochSecond() * 1000L;
-                }
-                mSnapshotTime = primaryPlan.getDataUsageTime();
-            }
-        }
-        Log.i(TAG, "Have " + mDataplanCount + " plans, dflt sub-id " + mSubId);
-    }
-
-    private static SubscriptionPlan getPrimaryPlan(List<SubscriptionPlan> plans) {
-        if (CollectionUtils.isEmpty(plans)) {
-            return null;
-        }
-        // First plan in the list is the primary plan
-        SubscriptionPlan plan = plans.get(0);
-        return plan.getDataLimitBytes() > 0
-                && validSize(plan.getDataUsageBytes())
-                && plan.getCycleRule() != null ? plan : null;
-    }
-
-    private static boolean validSize(long value) {
-        return value >= 0L && value < PETA;
-    }
-
-    public static boolean unlimited(long size) {
-        return size == SubscriptionPlan.BYTES_UNLIMITED;
-    }
-}
diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.kt b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.kt
new file mode 100644
index 0000000..8b31f67
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.net.NetworkPolicy
+import android.net.NetworkTemplate
+import android.text.TextUtils
+import android.util.Log
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.datausage.lib.DataUsageLib.getMobileTemplate
+import com.android.settings.datausage.lib.INetworkCycleDataRepository
+import com.android.settings.datausage.lib.NetworkCycleDataRepository
+import com.android.settings.network.ProxySubscriptionManager
+import com.android.settings.network.telephony.TelephonyBasePreferenceController
+import kotlin.math.max
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * This is the controller for a data usage header that retrieves carrier data from the new
+ * subscriptions framework API if available. The controller reads subscription information from the
+ * framework and falls back to legacy usage data if none are available.
+ */
+open class DataUsageSummaryPreferenceController @JvmOverloads constructor(
+    context: Context,
+    subId: Int,
+    private val proxySubscriptionManager: ProxySubscriptionManager =
+        ProxySubscriptionManager.getInstance(context),
+    private val networkCycleDataRepositoryFactory: (
+        template: NetworkTemplate,
+    ) -> INetworkCycleDataRepository = { NetworkCycleDataRepository(context, it) },
+    private val dataPlanRepositoryFactory: (
+        networkCycleDataRepository: INetworkCycleDataRepository,
+    ) -> DataPlanRepository = { DataPlanRepositoryImpl(it) }
+) : TelephonyBasePreferenceController(context, KEY) {
+
+    init {
+        mSubId = subId
+    }
+
+    private val subInfo by lazy {
+        if (DataUsageUtils.hasMobileData(mContext)) {
+            proxySubscriptionManager.getAccessibleSubscriptionInfo(mSubId)
+        } else null
+    }
+    private val networkCycleDataRepository by lazy {
+        networkCycleDataRepositoryFactory(getMobileTemplate(mContext, mSubId))
+    }
+    private val policy by lazy { networkCycleDataRepository.getPolicy() }
+    private lateinit var preference: DataUsageSummaryPreference
+
+    override fun getAvailabilityStatus(subId: Int) =
+        if (subInfo != null && policy != null) AVAILABLE else CONDITIONALLY_UNAVAILABLE
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        super.displayPreference(screen)
+        preference = screen.findPreference(preferenceKey)!!
+        policy?.let {
+            preference.setLimitInfo(it.getLimitInfo())
+            val dataBarSize = max(it.limitBytes, it.warningBytes)
+            if (dataBarSize > NetworkPolicy.WARNING_DISABLED) {
+                setDataBarSize(dataBarSize)
+            }
+        }
+    }
+
+    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                update()
+            }
+        }
+    }
+
+    private suspend fun update() {
+        val policy = policy ?: return
+        val dataPlanInfo = withContext(Dispatchers.Default) {
+            dataPlanRepositoryFactory(networkCycleDataRepository).getDataPlanInfo(
+                policy = policy,
+                plans = proxySubscriptionManager.get().getSubscriptionPlans(mSubId),
+            )
+        }
+        Log.d(TAG, "dataPlanInfo: $dataPlanInfo")
+        preference.setUsageNumbers(dataPlanInfo.dataPlanUse, dataPlanInfo.dataPlanSize)
+        if (dataPlanInfo.dataBarSize > 0) {
+            preference.setChartEnabled(true)
+            setDataBarSize(dataPlanInfo.dataBarSize)
+            preference.setProgress(dataPlanInfo.dataPlanUse / dataPlanInfo.dataBarSize.toFloat())
+        } else {
+            preference.setChartEnabled(false)
+        }
+
+        preference.setUsageInfo(
+            dataPlanInfo.cycleEnd,
+            dataPlanInfo.snapshotTime,
+            subInfo?.carrierName,
+            dataPlanInfo.dataPlanCount,
+        )
+    }
+
+    private fun setDataBarSize(dataBarSize: Long) {
+        preference.setLabels(
+            DataUsageUtils.formatDataUsage(mContext, /* byteValue = */ 0),
+            DataUsageUtils.formatDataUsage(mContext, dataBarSize)
+        )
+    }
+
+    private fun NetworkPolicy.getLimitInfo(): CharSequence? = when {
+        warningBytes > 0 && limitBytes > 0 -> {
+            TextUtils.expandTemplate(
+                mContext.getText(R.string.cell_data_warning_and_limit),
+                DataUsageUtils.formatDataUsage(mContext, warningBytes),
+                DataUsageUtils.formatDataUsage(mContext, limitBytes),
+            )
+        }
+
+        warningBytes > 0 -> {
+            TextUtils.expandTemplate(
+                mContext.getText(R.string.cell_data_warning),
+                DataUsageUtils.formatDataUsage(mContext, warningBytes),
+            )
+        }
+
+        limitBytes > 0 -> {
+            TextUtils.expandTemplate(
+                mContext.getText(R.string.cell_data_limit),
+                DataUsageUtils.formatDataUsage(mContext, limitBytes),
+            )
+        }
+
+        else -> null
+    }
+
+    companion object {
+        private const val TAG = "DataUsageSummaryPC"
+        private const val KEY = "status_header"
+    }
+}
diff --git a/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt b/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt
index 652919e0..7e3e183 100644
--- a/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt
+++ b/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt
@@ -21,6 +21,7 @@
 import android.text.format.DateUtils
 import android.util.Range
 import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.bucketRange
+import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.getCycles
 import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.reverseBucketRange
 import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
 import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.aggregate
@@ -37,12 +38,8 @@
     fun loadCycles(): List<NetworkUsageData> =
         getCycles().map { aggregateUsage(it) }.filter { it.usage > 0 }
 
-    private fun getCycles(): List<Range<Long>> {
-        val policy = networkCycleDataRepository.getPolicy() ?: return queryCyclesAsFourWeeks()
-        return policy.cycleIterator().asSequence().map {
-            Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
-        }.toList()
-    }
+    private fun getCycles(): List<Range<Long>> =
+        networkCycleDataRepository.getPolicy()?.getCycles() ?: queryCyclesAsFourWeeks()
 
     private fun queryCyclesAsFourWeeks(): List<Range<Long>> {
         val timeRange = buckets.aggregate()?.timeRange ?: return emptyList()
diff --git a/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt b/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
index cde64df..31052ef 100644
--- a/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
+++ b/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
@@ -27,6 +27,7 @@
 interface INetworkCycleDataRepository {
     fun getCycles(): List<Range<Long>>
     fun getPolicy(): NetworkPolicy?
+    fun queryUsage(range: Range<Long>): NetworkUsageData
 }
 
 class NetworkCycleDataRepository(
@@ -40,12 +41,8 @@
 
     fun loadFirstCycle(): NetworkUsageData? = getCycles().firstOrNull()?.let { queryUsage(it) }
 
-    override fun getCycles(): List<Range<Long>> {
-        val policy = getPolicy() ?: return queryCyclesAsFourWeeks()
-        return policy.cycleIterator().asSequence().map {
-            Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
-        }.toList()
-    }
+    override fun getCycles(): List<Range<Long>> =
+        getPolicy()?.getCycles() ?: queryCyclesAsFourWeeks()
 
     private fun queryCyclesAsFourWeeks(): List<Range<Long>> {
         val timeRange = networkStatsRepository.getTimeRange() ?: return emptyList()
@@ -63,13 +60,17 @@
         }
 
 
-    fun queryUsage(range: Range<Long>) = NetworkUsageData(
+    override fun queryUsage(range: Range<Long>) = NetworkUsageData(
         startTime = range.lower,
         endTime = range.upper,
         usage = networkStatsRepository.querySummaryForDevice(range.lower, range.upper),
     )
 
     companion object {
+        fun NetworkPolicy.getCycles() = cycleIterator().asSequence().map {
+            Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
+        }.toList()
+
         fun bucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
             (startTime..endTime step step).zipWithNext(::Range)
 
diff --git a/src/com/android/settings/development/ForcePeakRefreshRatePreferenceController.java b/src/com/android/settings/development/ForcePeakRefreshRatePreferenceController.java
index 80b295c..abeb949 100644
--- a/src/com/android/settings/development/ForcePeakRefreshRatePreferenceController.java
+++ b/src/com/android/settings/development/ForcePeakRefreshRatePreferenceController.java
@@ -28,6 +28,7 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.TwoStatePreference;
 
+import com.android.server.display.feature.flags.Flags;
 import com.android.settings.R;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
@@ -95,7 +96,9 @@
 
     @VisibleForTesting
     void forcePeakRefreshRate(boolean enable) {
-        final float peakRefreshRate = enable ? Float.POSITIVE_INFINITY : NO_CONFIG;
+        final float valueIfEnabled = Flags.backUpSmoothDisplayAndForcePeakRefreshRate()
+                ? Float.POSITIVE_INFINITY : mPeakRefreshRate;
+        final float peakRefreshRate = enable ? valueIfEnabled : NO_CONFIG;
         Settings.System.putFloat(mContext.getContentResolver(),
             Settings.System.MIN_REFRESH_RATE, peakRefreshRate);
     }
diff --git a/src/com/android/settings/display/PeakRefreshRatePreferenceController.java b/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
index dfe571a..17d763a 100644
--- a/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
+++ b/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
@@ -30,6 +30,7 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
+import com.android.server.display.feature.flags.Flags;
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -107,7 +108,9 @@
 
     @Override
     public boolean setChecked(boolean isChecked) {
-        final float peakRefreshRate = isChecked ? Float.POSITIVE_INFINITY : DEFAULT_REFRESH_RATE;
+        final float valueIfChecked = Flags.backUpSmoothDisplayAndForcePeakRefreshRate()
+                ? Float.POSITIVE_INFINITY : mPeakRefreshRate;
+        final float peakRefreshRate = isChecked ? valueIfChecked : DEFAULT_REFRESH_RATE;
         Log.d(TAG, "setChecked to : " + peakRefreshRate);
 
         return Settings.System.putFloat(
diff --git a/src/com/android/settings/network/ResetNetworkOperationBuilder.java b/src/com/android/settings/network/ResetNetworkOperationBuilder.java
index 61f57f9..ac07897 100644
--- a/src/com/android/settings/network/ResetNetworkOperationBuilder.java
+++ b/src/com/android/settings/network/ResetNetworkOperationBuilder.java
@@ -33,6 +33,8 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
 import com.android.settings.ResetNetworkRequest;
 import com.android.settings.network.apn.ApnSettings;
 
@@ -50,6 +52,13 @@
 
     private static final boolean DRY_RUN = false;
 
+    // TelephonyContentProvider method to restart phone process
+    @VisibleForTesting
+    static final String METHOD_RESTART_PHONE_PROCESS = "restartPhoneProcess";
+    // TelephonyContentProvider method to restart RILD
+    @VisibleForTesting
+    static final String METHOD_RESTART_RILD = "restartRild";
+
     private Context mContext;
     private List<Runnable> mResetSequence = new ArrayList<Runnable>();
 
@@ -229,17 +238,57 @@
                         // Reset IMS for all slots
                         for (int slotIndex = 0; slotIndex < tm.getActiveModemCount(); slotIndex++) {
                             tm.resetIms(slotIndex);
+                            Log.i(TAG, "IMS was reset for slot " + slotIndex);
                         }
                     } else {
                         // Reset IMS for the slot specified by the sucriptionId.
                         final int slotIndex = SubscriptionManager.getSlotIndex(subId);
                         tm.resetIms(slotIndex);
+                        Log.i(TAG, "IMS was reset for slot " + slotIndex);
                     }
                 });
         return this;
     }
 
     /**
+     * Append a step to restart phone process by the help of TelephonyContentProvider.
+     * It's a no-op if TelephonyContentProvider doesn't exist.
+     * @return this
+     */
+    public ResetNetworkOperationBuilder restartPhoneProcess() {
+        try {
+            mContext.getContentResolver().call(
+                    getResetTelephonyContentProviderAuthority(),
+                    METHOD_RESTART_PHONE_PROCESS,
+                    /* arg= */ null,
+                    /* extras= */ null);
+            Log.i(TAG, "Phone process was restarted.");
+        } catch (IllegalArgumentException iae) {
+            Log.w(TAG, "Fail to restart phone process: " + iae);
+        }
+        return this;
+    }
+
+    /**
+     * Append a step to restart RILD by the help of TelephonyContentProvider.
+     * It's a no-op if TelephonyContentProvider doesn't exist.
+     * @return this
+     */
+    public ResetNetworkOperationBuilder restartRild() {
+        try {
+            mContext.getContentResolver().call(
+                    getResetTelephonyContentProviderAuthority(),
+                    METHOD_RESTART_RILD,
+                    /* arg= */ null,
+                    /* extras= */ null);
+            Log.i(TAG, "RILD was restarted.");
+        } catch (IllegalArgumentException iae) {
+            Log.w(TAG, "Fail to restart RILD: " + iae);
+        }
+        return this;
+    }
+
+    /**
      * Construct a Runnable containing all operations appended.
      * @return Runnable
      */
@@ -262,4 +311,14 @@
         };
         mResetSequence.add(runnable);
     }
+
+    /**
+     * @return the authority of the telephony content provider that support methods
+     * resetPhoneProcess and resetRild.
+     */
+    @VisibleForTesting
+    String getResetTelephonyContentProviderAuthority() {
+        return mContext.getResources().getString(
+                R.string.reset_telephony_stack_content_provider_authority);
+    }
 }
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index 16b04aa..b4b40ef 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -172,7 +172,7 @@
         });
 
         return Arrays.asList(
-                new DataUsageSummaryPreferenceController(getActivity(), mSubId),
+                new DataUsageSummaryPreferenceController(context, mSubId),
                 new RoamingPreferenceController(context, KEY_ROAMING_PREF, getSettingsLifecycle(),
                         this, mSubId),
                 new CallsDefaultSubscriptionController(context, KEY_CALLS_PREF,
@@ -229,11 +229,6 @@
 
         }
 
-        final DataUsageSummaryPreferenceController dataUsageSummaryPreferenceController =
-                use(DataUsageSummaryPreferenceController.class);
-        if (dataUsageSummaryPreferenceController != null) {
-            dataUsageSummaryPreferenceController.init(mSubId);
-        }
         use(MobileNetworkSwitchController.class).init(mSubId);
         use(CarrierSettingsVersionPreferenceController.class).init(mSubId);
         use(BillingCyclePreferenceController.class).init(mSubId);
diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java
deleted file mode 100644
index a929873..0000000
--- a/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java
+++ /dev/null
@@ -1,352 +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.datausage;
-
-import static com.android.settings.core.BasePreferenceController.AVAILABLE;
-import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.NetworkTemplate;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionPlan;
-import android.telephony.TelephonyManager;
-import android.util.RecurrenceRule;
-
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
-import com.android.settings.widget.EntityHeaderController;
-import com.android.settingslib.net.DataUsageController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-import java.time.Instant;
-import java.time.ZoneId;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = ShadowEntityHeaderController.class)
-public class DataUsageSummaryPreferenceControllerTest {
-
-    private static final long UPDATE_BACKOFF_MS = TimeUnit.MINUTES.toMillis(13);
-    private static final long CYCLE_BACKOFF_MS = TimeUnit.DAYS.toMillis(6);
-    private static final long CYCLE_LENGTH_MS = TimeUnit.DAYS.toMillis(30);
-    private static final long USAGE1 =  373 * BillingCycleSettings.MIB_IN_BYTES;
-    private static final long LIMIT1 = BillingCycleSettings.GIB_IN_BYTES;
-    private static final String CARRIER_NAME = "z-mobile";
-    private static final String PERIOD = "Feb";
-
-    @Mock
-    private DataUsageController mDataUsageController;
-    @Mock
-    private DataUsageSummaryPreference mSummaryPreference;
-    @Mock
-    private NetworkTemplate mNetworkTemplate;
-    @Mock
-    private SubscriptionInfo mSubscriptionInfo;
-    @Mock
-    private SubscriptionPlan mSubscriptionPlan;
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private EntityHeaderController mHeaderController;
-    @Mock
-    private TelephonyManager mTelephonyManager;
-    @Mock
-    private PackageManager mPm;
-
-    private DataUsageInfoController mDataInfoController;
-
-    private FakeFeatureFactory mFactory;
-    private FragmentActivity mActivity;
-    private Context mContext;
-    private DataUsageSummaryPreferenceController mController;
-    private int mDefaultSubscriptionId;
-    private List<SubscriptionPlan> mSubscriptionPlans;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(RuntimeEnvironment.application);
-
-        doReturn("%1$s %2%s").when(mContext)
-                .getString(com.android.internal.R.string.fileSizeSuffix);
-
-        mDefaultSubscriptionId = 1234;
-        mSubscriptionPlans = new ArrayList<SubscriptionPlan>();
-
-        mFactory = FakeFeatureFactory.setupForTest();
-        when(mFactory.metricsFeatureProvider.getMetricsCategory(any(Object.class)))
-                .thenReturn(MetricsProto.MetricsEvent.SETTINGS_APP_NOTIF_CATEGORY);
-        ShadowEntityHeaderController.setUseMock(mHeaderController);
-
-        mDataInfoController = spy(new DataUsageInfoController());
-        doReturn(-1L).when(mDataInfoController).getSummaryLimit(any());
-
-        mActivity = spy(Robolectric.buildActivity(FragmentActivity.class).get());
-        doReturn(mTelephonyManager).when(mActivity).getSystemService(TelephonyManager.class);
-        doReturn(mTelephonyManager).when(mTelephonyManager)
-                .createForSubscriptionId(mDefaultSubscriptionId);
-        doReturn(mPm).when(mActivity).getPackageManager();
-        doReturn(TelephonyManager.SIM_STATE_READY).when(mTelephonyManager).getSimState();
-
-        mController = spy(new DataUsageSummaryPreferenceController(
-                mDataUsageController,
-                mDataInfoController,
-                mNetworkTemplate,
-                mActivity, mDefaultSubscriptionId));
-        doReturn(null).when(mController).getSubscriptionInfo(
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        doReturn(null).when(mController).getSubscriptionPlans(
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-
-        doReturn(CARRIER_NAME).when(mSubscriptionInfo).getCarrierName();
-        doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(mDefaultSubscriptionId);
-        doReturn(mSubscriptionPlans).when(mController).getSubscriptionPlans(mDefaultSubscriptionId);
-    }
-
-    @After
-    public void tearDown() {
-        ShadowEntityHeaderController.reset();
-    }
-
-    @Test
-    public void testSummaryUpdate_onePlan_basic() {
-        final long now = System.currentTimeMillis();
-        final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
-
-        doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
-        setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
-        createTestDataPlan(info.cycleStart, info.cycleEnd);
-
-        mController.updateState(mSummaryPreference);
-
-        ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
-        verify(mSummaryPreference).setLimitInfo(captor.capture());
-        CharSequence value = captor.getValue();
-        assertThat(value.toString()).isEqualTo("512 MB data warning / 1.00 GB data limit");
-
-        // TODO (b/170330084): return intent instead of null for mSummaryPreference
-        verify(mSummaryPreference).setUsageInfo((info.cycleEnd / 1000) * 1000,
-                now - UPDATE_BACKOFF_MS,
-                CARRIER_NAME, 1 /* numPlans */);
-        verify(mSummaryPreference).setChartEnabled(true);
-    }
-
-    @Test
-    public void testSummaryUpdate_noPlan_basic() {
-        final long now = System.currentTimeMillis();
-        final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
-
-        doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
-        setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
-
-        mController.updateState(mSummaryPreference);
-
-        ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
-        verify(mSummaryPreference).setLimitInfo(captor.capture());
-        CharSequence value = captor.getValue();
-        assertThat(value.toString()).isEqualTo("512 MB data warning / 1.00 GB data limit");
-
-        verify(mSummaryPreference).setUsageInfo(
-                info.cycleEnd,
-                -1L /* snapshotTime */,
-                CARRIER_NAME,
-                0 /* numPlans */);
-        verify(mSummaryPreference).setChartEnabled(true);
-    }
-
-    @Test
-    public void testSummaryUpdate_noCarrier_basic() {
-        final long now = System.currentTimeMillis();
-        final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
-
-        doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
-        doReturn(null).when(mSubscriptionInfo).getCarrierName();
-        setupTestDataUsage(LIMIT1, USAGE1, -1L /* snapshotTime */);
-
-        mController.updateState(mSummaryPreference);
-
-        ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
-        verify(mSummaryPreference).setLimitInfo(captor.capture());
-        CharSequence value = captor.getValue();
-        assertThat(value.toString()).isEqualTo("512 MB data warning / 1.00 GB data limit");
-
-        verify(mSummaryPreference).setUsageInfo(
-                info.cycleEnd,
-                -1L /* snapshotTime */,
-                null /* carrierName */,
-                0 /* numPlans */);
-        verify(mSummaryPreference).setChartEnabled(true);
-    }
-
-    @Test
-    public void testSummaryUpdate_noPlanData_basic() {
-        final long now = System.currentTimeMillis();
-
-        final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
-
-        doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
-        doReturn(null).when(mSubscriptionInfo).getCarrierName();
-        setupTestDataUsage(-1L /* dataPlanSize */, USAGE1, -1L /* snapshotTime */);
-
-        mController.updateState(mSummaryPreference);
-
-        ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
-        verify(mSummaryPreference).setLimitInfo(captor.capture());
-        CharSequence value = captor.getValue();
-        assertThat(value.toString()).isEqualTo("512 MB data warning / 1.00 GB data limit");
-        verify(mSummaryPreference).setUsageInfo(
-                info.cycleEnd,
-                -1L /* snapshotTime */,
-                null /* carrierName */,
-                0 /* numPlans */);
-        verify(mSummaryPreference).setChartEnabled(false);
-    }
-
-    @Test
-    public void testSummaryUpdate_noLimitNoWarning() {
-        final long now = System.currentTimeMillis();
-        final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
-        info.warningLevel = 0L;
-        info.limitLevel = 0L;
-
-        doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
-        setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
-
-        mController.updateState(mSummaryPreference);
-        verify(mSummaryPreference).setLimitInfo(null);
-    }
-
-    @Test
-    public void testSummaryUpdate_warningOnly() {
-        final long now = System.currentTimeMillis();
-        final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
-        info.warningLevel = BillingCycleSettings.MIB_IN_BYTES;
-        info.limitLevel = 0L;
-
-        doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
-        setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
-
-        mController.updateState(mSummaryPreference);
-
-        ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
-        verify(mSummaryPreference).setLimitInfo(captor.capture());
-        CharSequence value = captor.getValue();
-        assertThat(value.toString()).isEqualTo("1.00 MB data warning");
-    }
-
-    @Test
-    public void testSummaryUpdate_limitOnly() {
-        final long now = System.currentTimeMillis();
-        final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
-        info.warningLevel = 0L;
-        info.limitLevel = BillingCycleSettings.MIB_IN_BYTES;
-
-        doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
-        setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
-
-        mController.updateState(mSummaryPreference);
-
-        ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
-        verify(mSummaryPreference).setLimitInfo(captor.capture());
-        CharSequence value = captor.getValue();
-        assertThat(value.toString()).isEqualTo("1.00 MB data limit");
-    }
-
-    @Test
-    public void testSummaryUpdate_limitAndWarning() {
-        final long now = System.currentTimeMillis();
-        final DataUsageController.DataUsageInfo info = createTestDataUsageInfo(now);
-        info.warningLevel = BillingCycleSettings.MIB_IN_BYTES;
-        info.limitLevel = BillingCycleSettings.MIB_IN_BYTES;
-
-        doReturn(info).when(mDataUsageController).getDataUsageInfo(any());
-        setupTestDataUsage(LIMIT1, USAGE1, now - UPDATE_BACKOFF_MS);
-
-        mController.updateState(mSummaryPreference);
-
-        ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
-        verify(mSummaryPreference).setLimitInfo(captor.capture());
-        CharSequence value = captor.getValue();
-        assertThat(value.toString()).isEqualTo("1.00 MB data warning / 1.00 MB data limit");
-    }
-
-    @Test
-    public void testMobileData_preferenceAvailable() {
-        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
-    }
-
-    @Test
-    public void testMobileData_noSim_preferenceDisabled() {
-        final int subscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        mController.init(subscriptionId);
-        mController.mDataUsageController = mDataUsageController;
-        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
-    }
-
-    private DataUsageController.DataUsageInfo createTestDataUsageInfo(long now) {
-        DataUsageController.DataUsageInfo info = new DataUsageController.DataUsageInfo();
-        info.carrier = CARRIER_NAME;
-        info.period = PERIOD;
-        info.startDate = now;
-        info.limitLevel = LIMIT1;
-        info.warningLevel = LIMIT1 >> 1;
-        info.usageLevel = USAGE1;
-        info.cycleStart = now - CYCLE_BACKOFF_MS;
-        info.cycleEnd = info.cycleStart + CYCLE_LENGTH_MS;
-        return info;
-    }
-
-    private void setupTestDataUsage(long dataPlanSize, long dataUsageSize, long snapshotTime) {
-        doReturn(dataPlanSize).when(mSubscriptionPlan).getDataLimitBytes();
-        doReturn(dataUsageSize).when(mSubscriptionPlan).getDataUsageBytes();
-        doReturn(snapshotTime).when(mSubscriptionPlan).getDataUsageTime();
-
-        doReturn(dataPlanSize).when(mDataInfoController).getSummaryLimit(any());
-    }
-
-    private void createTestDataPlan(long startTime, long endTime) {
-        final RecurrenceRule recurrenceRule = new RecurrenceRule(
-                Instant.ofEpochMilli(startTime).atZone(ZoneId.systemDefault()),
-                Instant.ofEpochMilli(endTime).atZone(ZoneId.systemDefault()),
-                null);
-        doReturn(recurrenceRule).when(mSubscriptionPlan).getCycleRule();
-        mSubscriptionPlans.add(mSubscriptionPlan);
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/development/ForcePeakRefreshRatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/ForcePeakRefreshRatePreferenceControllerTest.java
index c5cc856..314120a 100644
--- a/tests/robotests/src/com/android/settings/development/ForcePeakRefreshRatePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/ForcePeakRefreshRatePreferenceControllerTest.java
@@ -25,12 +25,19 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 
+import com.android.server.display.feature.flags.Flags;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -50,6 +57,9 @@
     private Context mContext;
     private ForcePeakRefreshRatePreferenceController mController;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -61,7 +71,19 @@
     }
 
     @Test
-    public void onPreferenceChange_preferenceChecked_shouldEnableForcePeak() {
+    @RequiresFlagsDisabled(Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE)
+    public void onPreferenceChange_preferenceChecked_shouldEnableForcePeak_featureFlagOff() {
+        mController.mPeakRefreshRate = 88f;
+
+        mController.onPreferenceChange(mPreference, true);
+
+        assertThat(Settings.System.getFloat(mContext.getContentResolver(),
+                Settings.System.MIN_REFRESH_RATE, NO_CONFIG)).isEqualTo(88f);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE)
+    public void onPreferenceChange_preferenceChecked_shouldEnableForcePeak_featureFlagOn() {
         mController.mPeakRefreshRate = 88f;
 
         mController.onPreferenceChange(mPreference, true);
@@ -88,6 +110,7 @@
         mController.updateState(mPreference);
 
         verify(mPreference).setChecked(true);
+        assertThat(mController.isForcePeakRefreshRateEnabled()).isTrue();
     }
 
     @Test
@@ -98,6 +121,7 @@
         mController.updateState(mPreference);
 
         verify(mPreference).setChecked(false);
+        assertThat(mController.isForcePeakRefreshRateEnabled()).isFalse();
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/display/PeakRefreshRatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/PeakRefreshRatePreferenceControllerTest.java
index 8b2c445..cb0963b 100644
--- a/tests/robotests/src/com/android/settings/display/PeakRefreshRatePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/PeakRefreshRatePreferenceControllerTest.java
@@ -25,11 +25,18 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 
 import androidx.preference.SwitchPreference;
 
+import com.android.server.display.feature.flags.Flags;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -49,6 +56,9 @@
     private PeakRefreshRatePreferenceController.DeviceConfigDisplaySettings
             mDeviceConfigDisplaySettings;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -79,7 +89,19 @@
     }
 
     @Test
-    public void setChecked_enableSmoothDisplay_setRefreshRateToInfinity() {
+    @RequiresFlagsDisabled(Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE)
+    public void setChecked_enableSmoothDisplay_featureFlagOff() {
+        mController.mPeakRefreshRate = 88f;
+        mController.setChecked(true);
+
+        assertThat(Settings.System.getFloat(mContext.getContentResolver(),
+                Settings.System.PEAK_REFRESH_RATE, DEFAULT_REFRESH_RATE))
+                .isEqualTo(88f);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE)
+    public void setChecked_enableSmoothDisplay_featureFlagOn() {
         mController.mPeakRefreshRate = 88f;
         mController.setChecked(true);
 
@@ -100,14 +122,16 @@
 
     @Test
     public void isChecked_enableSmoothDisplay_returnTrue() {
-        enableSmoothDisplayPreference();
+        mController.mPeakRefreshRate = 88f;
+        mController.setChecked(true);
 
         assertThat(mController.isChecked()).isTrue();
     }
 
     @Test
     public void isChecked_disableSmoothDisplay_returnFalse() {
-        disableSmoothDisplayPreference();
+        mController.mPeakRefreshRate = 88f;
+        mController.setChecked(false);
 
         assertThat(mController.isChecked()).isFalse();
     }
@@ -128,22 +152,4 @@
 
         assertThat(mController.isChecked()).isFalse();
     }
-
-    private void enableSmoothDisplayPreference() {
-        mController.mPeakRefreshRate = 88f;
-
-        Settings.System.putFloat(
-                mContext.getContentResolver(),
-                Settings.System.PEAK_REFRESH_RATE,
-                mController.mPeakRefreshRate);
-    }
-
-    private void disableSmoothDisplayPreference() {
-        mController.mPeakRefreshRate = 88f;
-
-        Settings.System.putFloat(
-                mContext.getContentResolver(),
-                Settings.System.PEAK_REFRESH_RATE,
-                DEFAULT_REFRESH_RATE);
-    }
 }
diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataPlanRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataPlanRepositoryTest.kt
new file mode 100644
index 0000000..5ed1486
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/DataPlanRepositoryTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.net.NetworkPolicy
+import android.telephony.SubscriptionPlan
+import android.util.Range
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.datausage.lib.INetworkCycleDataRepository
+import com.android.settings.datausage.lib.NetworkUsageData
+import com.android.settings.testutils.zonedDateTime
+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
+
+@RunWith(AndroidJUnit4::class)
+class DataPlanRepositoryTest {
+
+    private object FakeNetworkCycleDataRepository : INetworkCycleDataRepository {
+        override fun getCycles(): List<Range<Long>> = emptyList()
+        override fun getPolicy() = null
+
+        override fun queryUsage(range: Range<Long>) = NetworkUsageData(
+            startTime = CYCLE_CYCLE_START_TIME,
+            endTime = CYCLE_CYCLE_END_TIME,
+            usage = CYCLE_BYTES,
+        )
+    }
+
+    private val repository = DataPlanRepositoryImpl(FakeNetworkCycleDataRepository)
+
+    private val policy = mock<NetworkPolicy> {
+        on { cycleIterator() } doReturn listOf(
+            Range(zonedDateTime(CYCLE_CYCLE_START_TIME), zonedDateTime(CYCLE_CYCLE_END_TIME)),
+        ).iterator()
+    }
+
+    @Test
+    fun getDataPlanInfo_hasSubscriptionPlan() {
+        val dataPlanInfo = repository.getDataPlanInfo(policy, listOf(SUBSCRIPTION_PLAN))
+
+        assertThat(dataPlanInfo).isEqualTo(
+            DataPlanInfo(
+                dataPlanCount = 1,
+                dataPlanSize = DATA_LIMIT_BYTES,
+                dataBarSize = DATA_LIMIT_BYTES,
+                dataPlanUse = DATA_USAGE_BYTES,
+                cycleEnd = PLAN_CYCLE_END_TIME,
+                snapshotTime = DATA_USAGE_TIME,
+            )
+        )
+    }
+
+    @Test
+    fun getDataPlanInfo_noSubscriptionPlan() {
+        val dataPlanInfo = repository.getDataPlanInfo(policy, emptyList())
+
+        assertThat(dataPlanInfo).isEqualTo(
+            DataPlanInfo(
+                dataPlanCount = 0,
+                dataPlanSize = SubscriptionPlan.BYTES_UNKNOWN,
+                dataBarSize = CYCLE_BYTES,
+                dataPlanUse = CYCLE_BYTES,
+                cycleEnd = CYCLE_CYCLE_END_TIME,
+                snapshotTime = SubscriptionPlan.TIME_UNKNOWN,
+            )
+        )
+    }
+
+    private companion object {
+        const val CYCLE_CYCLE_START_TIME = 1L
+        const val CYCLE_CYCLE_END_TIME = 2L
+        const val CYCLE_BYTES = 11L
+
+        const val PLAN_CYCLE_START_TIME = 100L
+        const val PLAN_CYCLE_END_TIME = 200L
+        const val DATA_LIMIT_BYTES = 300L
+        const val DATA_USAGE_BYTES = 400L
+        const val DATA_USAGE_TIME = 500L
+
+        val SUBSCRIPTION_PLAN: SubscriptionPlan = SubscriptionPlan.Builder.createNonrecurring(
+            zonedDateTime(PLAN_CYCLE_START_TIME),
+            zonedDateTime(PLAN_CYCLE_END_TIME),
+        ).apply {
+            setDataLimit(DATA_LIMIT_BYTES, SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
+            setDataUsage(DATA_USAGE_BYTES, DATA_USAGE_TIME)
+        }.build()
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.kt
new file mode 100644
index 0000000..8ebb9c5
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.kt
@@ -0,0 +1,274 @@
+/*
+ * 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.net.NetworkPolicy
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionPlan
+import android.telephony.TelephonyManager
+import android.util.Range
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.preference.PreferenceManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.core.BasePreferenceController.AVAILABLE
+import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE
+import com.android.settings.datausage.lib.INetworkCycleDataRepository
+import com.android.settings.datausage.lib.NetworkUsageData
+import com.android.settings.network.ProxySubscriptionManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class DataUsageSummaryPreferenceControllerTest {
+
+    private var policy: NetworkPolicy? = mock<NetworkPolicy>()
+
+    private val mockTelephonyManager = mock<TelephonyManager> {
+        on { isDataCapable } doReturn true
+    }
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+    }
+
+    private val mockSubscriptionManager = mock<SubscriptionManager> {
+        on { getSubscriptionPlans(any()) } doReturn emptyList()
+    }
+
+    private val mockProxySubscriptionManager = mock<ProxySubscriptionManager> {
+        on { get() } doReturn mockSubscriptionManager
+    }
+
+    private val fakeNetworkCycleDataRepository = object : INetworkCycleDataRepository {
+        override fun getCycles(): List<Range<Long>> = emptyList()
+        override fun getPolicy() = policy
+        override fun queryUsage(range: Range<Long>) = NetworkUsageData.AllZero
+    }
+
+    private var dataPlanInfo = EMPTY_DATA_PLAN_INFO
+
+    private val fakeDataPlanRepository = object : DataPlanRepository {
+        override fun getDataPlanInfo(policy: NetworkPolicy, plans: List<SubscriptionPlan>) =
+            dataPlanInfo
+    }
+
+    private val controller = DataUsageSummaryPreferenceController(
+        context = context,
+        subId = SUB_ID,
+        proxySubscriptionManager = mockProxySubscriptionManager,
+        networkCycleDataRepositoryFactory = { fakeNetworkCycleDataRepository },
+        dataPlanRepositoryFactory = { fakeDataPlanRepository },
+    )
+
+    private val preference = mock<DataUsageSummaryPreference> {
+        on { key } doReturn controller.preferenceKey
+    }
+    private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
+
+    @Before
+    fun setUp() {
+        preferenceScreen.addPreference(preference)
+    }
+
+    @Test
+    fun getAvailabilityStatus_noMobileData_conditionallyUnavailable() {
+        mockTelephonyManager.stub {
+            on { isDataCapable } doReturn false
+        }
+
+        val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
+
+        assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
+    }
+
+    @Test
+    fun getAvailabilityStatus_hasSubInfoAndPolicy_available() {
+        mockProxySubscriptionManager.stub {
+            on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn SubscriptionInfo.Builder().build()
+        }
+
+        val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
+
+        assertThat(availabilityStatus).isEqualTo(AVAILABLE)
+    }
+
+    @Test
+    fun getAvailabilityStatus_noSubInfo_conditionallyUnavailable() {
+        mockProxySubscriptionManager.stub {
+            on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn null
+        }
+
+        val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
+
+        assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
+    }
+
+    @Test
+    fun getAvailabilityStatus_noPolicy_conditionallyUnavailable() {
+        policy = null
+
+        val availabilityStatus = controller.getAvailabilityStatus(SUB_ID)
+
+        assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
+    }
+
+    @Test
+    fun displayPreference_policyHasNoLimitInfo() {
+        policy = mock<NetworkPolicy>().apply {
+            warningBytes = NetworkPolicy.WARNING_DISABLED
+            limitBytes = NetworkPolicy.LIMIT_DISABLED
+        }
+
+        controller.displayPreference(preferenceScreen)
+
+        verify(preference).setLimitInfo(null)
+        verify(preference, never()).setLabels(any(), any())
+    }
+
+    @Test
+    fun displayPreference_policyWarningOnly() {
+        policy = mock<NetworkPolicy>().apply {
+            warningBytes = 1L
+            limitBytes = NetworkPolicy.LIMIT_DISABLED
+        }
+
+        controller.displayPreference(preferenceScreen)
+
+        val limitInfo = argumentCaptor {
+            verify(preference).setLimitInfo(capture())
+        }.firstValue.toString()
+        assertThat(limitInfo).isEqualTo("1 B data warning")
+        verify(preference).setLabels("0 B", "1 B")
+    }
+
+    @Test
+    fun displayPreference_policyLimitOnly() {
+        policy = mock<NetworkPolicy>().apply {
+            warningBytes = NetworkPolicy.WARNING_DISABLED
+            limitBytes = 1L
+        }
+
+        controller.displayPreference(preferenceScreen)
+
+        val limitInfo = argumentCaptor {
+            verify(preference).setLimitInfo(capture())
+        }.firstValue.toString()
+        assertThat(limitInfo).isEqualTo("1 B data limit")
+        verify(preference).setLabels("0 B", "1 B")
+    }
+
+    @Test
+    fun displayPreference_policyHasWarningAndLimit() {
+        policy = mock<NetworkPolicy>().apply {
+            warningBytes = BillingCycleSettings.GIB_IN_BYTES / 2
+            limitBytes = BillingCycleSettings.GIB_IN_BYTES
+        }
+
+        controller.displayPreference(preferenceScreen)
+
+        val limitInfo = argumentCaptor {
+            verify(preference).setLimitInfo(capture())
+        }.firstValue.toString()
+        assertThat(limitInfo).isEqualTo("512 MB data warning / 1.00 GB data limit")
+        verify(preference).setLabels("0 B", "1.00 GB")
+    }
+
+    @Test
+    fun onViewCreated_emptyDataPlanInfo() = runBlocking {
+        dataPlanInfo = EMPTY_DATA_PLAN_INFO
+        controller.displayPreference(preferenceScreen)
+        clearInvocations(preference)
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        verify(preference).setUsageNumbers(
+            EMPTY_DATA_PLAN_INFO.dataPlanUse,
+            EMPTY_DATA_PLAN_INFO.dataPlanSize,
+        )
+        verify(preference).setChartEnabled(false)
+        verify(preference).setUsageInfo(
+            EMPTY_DATA_PLAN_INFO.cycleEnd,
+            EMPTY_DATA_PLAN_INFO.snapshotTime,
+            null,
+            EMPTY_DATA_PLAN_INFO.dataPlanCount,
+        )
+    }
+
+    @Test
+    fun onViewCreated_positiveDataPlanInfo() = runBlocking {
+        dataPlanInfo = POSITIVE_DATA_PLAN_INFO
+        controller.displayPreference(preferenceScreen)
+        clearInvocations(preference)
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        verify(preference).setUsageNumbers(
+            POSITIVE_DATA_PLAN_INFO.dataPlanUse,
+            POSITIVE_DATA_PLAN_INFO.dataPlanSize,
+        )
+        verify(preference).setChartEnabled(true)
+        verify(preference).setLabels("0 B", "9 B")
+        val progress = argumentCaptor {
+            verify(preference).setProgress(capture())
+        }.firstValue
+        assertThat(progress).isEqualTo(0.8888889f)
+        verify(preference).setUsageInfo(
+            POSITIVE_DATA_PLAN_INFO.cycleEnd,
+            POSITIVE_DATA_PLAN_INFO.snapshotTime,
+            null,
+            POSITIVE_DATA_PLAN_INFO.dataPlanCount,
+        )
+    }
+
+    private companion object {
+        const val SUB_ID = 1234
+        val EMPTY_DATA_PLAN_INFO = DataPlanInfo(
+            dataPlanCount = 0,
+            dataPlanSize = SubscriptionPlan.BYTES_UNKNOWN,
+            dataBarSize = SubscriptionPlan.BYTES_UNKNOWN,
+            dataPlanUse = 0,
+            cycleEnd = null,
+            snapshotTime = SubscriptionPlan.TIME_UNKNOWN,
+        )
+        val POSITIVE_DATA_PLAN_INFO = DataPlanInfo(
+            dataPlanCount = 0,
+            dataPlanSize = 10L,
+            dataBarSize = 9L,
+            dataPlanUse = 8L,
+            cycleEnd = 7L,
+            snapshotTime = 6L,
+        )
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleDataRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleDataRepositoryTest.kt
index f0a5309..77fe843 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleDataRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleDataRepositoryTest.kt
@@ -23,10 +23,8 @@
 import android.util.Range
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.testutils.zonedDateTime
 import com.google.common.truth.Truth.assertThat
-import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -96,9 +94,6 @@
         )
     }
 
-    private fun zonedDateTime(epochMilli: Long): ZonedDateTime? =
-        ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault())
-
     private companion object {
         const val CYCLE1_START_TIME = 1L
         const val CYCLE1_END_TIME = 2L
diff --git a/tests/spa_unit/src/com/android/settings/testutils/DateTimeTestUtil.kt b/tests/spa_unit/src/com/android/settings/testutils/DateTimeTestUtil.kt
new file mode 100644
index 0000000..2a5f137
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/testutils/DateTimeTestUtil.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.testutils
+
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+
+fun zonedDateTime(epochMilli: Long): ZonedDateTime? =
+    ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault())
diff --git a/tests/unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceTest.java b/tests/unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceTest.java
index fe7759d..d80c16a 100644
--- a/tests/unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceTest.java
+++ b/tests/unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceTest.java
@@ -17,6 +17,7 @@
 package com.android.settings.datausage;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -90,7 +91,7 @@
 
     @Test
     public void testSetUsageInfo_withNoDataPlans_carrierInfoNotShown() {
-        mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
+        mSummaryPreference.setUsageInfo(mCycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
 
         mSummaryPreference.onBindViewHolder(mHolder);
         assertThat(mSummaryPreference.getCarrierInfo(mHolder).getVisibility())
@@ -197,7 +198,7 @@
 
     @Test
     public void testSetUsageInfo_withNoDataPlans_usageTitleNotShown() {
-        mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
+        mSummaryPreference.setUsageInfo(mCycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
 
         mSummaryPreference.onBindViewHolder(mHolder);
         assertThat(mSummaryPreference.getUsageTitle(mHolder).getVisibility()).isEqualTo(View.GONE);
@@ -216,7 +217,7 @@
     public void testSetUsageInfo_cycleRemainingTimeIsLessOneDay() {
         // just under one day
         final long cycleEnd = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(23);
-        mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
+        mSummaryPreference.setUsageInfo(cycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
 
         mSummaryPreference.onBindViewHolder(mHolder);
         assertThat(mSummaryPreference.getCycleTime(mHolder).getVisibility())
@@ -229,7 +230,7 @@
     @Test
     public void testSetUsageInfo_cycleRemainingTimeNegativeDaysLeft_shouldDisplayNoneLeft() {
         final long cycleEnd = System.currentTimeMillis() - 1L;
-        mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
+        mSummaryPreference.setUsageInfo(cycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
 
         mSummaryPreference.onBindViewHolder(mHolder);
         assertThat(mSummaryPreference.getCycleTime(mHolder).getVisibility())
@@ -243,7 +244,7 @@
         final int daysLeft = 3;
         final long cycleEnd = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(daysLeft)
                 + TimeUnit.HOURS.toMillis(1);
-        mSummaryPreference.setUsageInfo(cycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
+        mSummaryPreference.setUsageInfo(cycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
 
         mSummaryPreference.onBindViewHolder(mHolder);
         assertThat(mSummaryPreference.getCycleTime(mHolder).getVisibility())
@@ -329,8 +330,7 @@
         mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 1 /* numPlans */);
         mSummaryPreference.setUsageNumbers(
                 BillingCycleSettings.MIB_IN_BYTES,
-                10 * BillingCycleSettings.MIB_IN_BYTES,
-                true /* hasMobileData */);
+                10 * BillingCycleSettings.MIB_IN_BYTES);
 
         mSummaryPreference.onBindViewHolder(mHolder);
         assertThat(mSummaryPreference.getDataUsed(mHolder).getText().toString())
@@ -349,8 +349,7 @@
         mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 1 /* numPlans */);
         mSummaryPreference.setUsageNumbers(
                 11 * BillingCycleSettings.MIB_IN_BYTES,
-                10 * BillingCycleSettings.MIB_IN_BYTES,
-                true /* hasMobileData */);
+                10 * BillingCycleSettings.MIB_IN_BYTES);
 
         mSummaryPreference.onBindViewHolder(mHolder);
         assertThat(mSummaryPreference.getDataUsed(mHolder).getText().toString())
@@ -364,9 +363,9 @@
 
     @Test
     public void testSetUsageInfo_withUsageInfo_dataUsageShown() {
-        mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 0 /* numPlans */);
+        mSummaryPreference.setUsageInfo(mCycleEnd, -1, FAKE_CARRIER, 0 /* numPlans */);
         mSummaryPreference.setUsageNumbers(
-                BillingCycleSettings.MIB_IN_BYTES, -1L, true /* hasMobileData */);
+                BillingCycleSettings.MIB_IN_BYTES, -1L);
 
         mSummaryPreference.onBindViewHolder(mHolder);
         assertThat(mSummaryPreference.getDataUsed(mHolder).getText().toString())
@@ -383,8 +382,7 @@
         mSummaryPreference.setUsageInfo(mCycleEnd, mUpdateTime, FAKE_CARRIER, 1 /* numPlans */);
         mSummaryPreference.setUsageNumbers(
                 BillingCycleSettings.MIB_IN_BYTES,
-                10 * BillingCycleSettings.MIB_IN_BYTES,
-                true /* hasMobileData */);
+                10 * BillingCycleSettings.MIB_IN_BYTES);
 
         int data_used_formatted_id = ResourcesUtils.getResourcesId(
                 mContext, "string", "data_used_formatted");
diff --git a/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java b/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java
index 41b6b27..6213f8e 100644
--- a/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java
+++ b/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java
@@ -16,13 +16,20 @@
 
 package com.android.settings.network;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.ContentProvider;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkPolicyManager;
@@ -59,6 +66,9 @@
     private TelephonyManager mTelephonyManager;
     @Mock
     private NetworkPolicyManager mNetworkPolicyManager;
+    @Mock
+    private ContentProvider mContentProvider;;
+
 
     private Context mContext;
     private ResetNetworkOperationBuilder mBuilder;
@@ -67,6 +77,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = spy(ApplicationProvider.getApplicationContext());
+        doReturn(ContentResolver.wrap(mContentProvider)).when(mContext).getContentResolver();
 
         mBuilder = spy(new ResetNetworkOperationBuilder(mContext));
     }
@@ -171,4 +182,42 @@
 
         verify(mTelephonyManager, times(2)).resetIms(anyInt());
     }
+
+    @Test
+    public void restartPhoneProcess_withoutTelephonyContentProvider_shouldNotCrash() {
+        doThrow(new IllegalArgumentException()).when(mContentProvider).call(
+                anyString(), anyString(), anyString(), any());
+
+        mBuilder.restartPhoneProcess();
+    }
+
+    @Test
+    public void restartRild_withoutTelephonyContentProvider_shouldNotCrash() {
+        doThrow(new IllegalArgumentException()).when(mContentProvider).call(
+                anyString(), anyString(), anyString(), any());
+
+        mBuilder.restartRild();
+    }
+
+    @Test
+    public void restartPhoneProcess_withTelephonyContentProvider_shouldCallRestartPhoneProcess() {
+        mBuilder.restartPhoneProcess();
+
+        verify(mContentProvider).call(
+                eq(mBuilder.getResetTelephonyContentProviderAuthority()),
+                eq(ResetNetworkOperationBuilder.METHOD_RESTART_PHONE_PROCESS),
+                isNull(),
+                isNull());
+    }
+
+    @Test
+    public void restartRild_withTelephonyContentProvider_shouldCallRestartRild() {
+        mBuilder.restartRild();
+
+        verify(mContentProvider).call(
+                eq(mBuilder.getResetTelephonyContentProviderAuthority()),
+                eq(ResetNetworkOperationBuilder.METHOD_RESTART_RILD),
+                isNull(),
+                isNull());
+    }
 }