Merge "Calling requestQuietMode with peindingIntent to match PS settings with PS state" into main
diff --git a/res/layout/accessibility_shortcut_option_checkable.xml b/res/layout/accessibility_shortcut_option_checkable.xml
index a5c5595..1551352 100644
--- a/res/layout/accessibility_shortcut_option_checkable.xml
+++ b/res/layout/accessibility_shortcut_option_checkable.xml
@@ -32,23 +32,22 @@
         android:id="@android:id/checkbox"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
         android:background="@null"
         android:clickable="false"
         android:focusable="false"
         android:gravity="center_vertical"
         android:orientation="vertical"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toBottomOf="@android:id/title"
-        app:layout_constraintEnd_toStartOf="@android:id/title"
-        app:layout_constraintHeight="true"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintWidth="true" />
+        app:layout_constraintTop_toTopOf="parent" />
 
     <TextView
         android:id="@android:id/title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
+        android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
         android:textAppearance="?android:attr/textAppearanceLarge"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toEndOf="@android:id/checkbox"
@@ -61,6 +60,7 @@
         android:layout_height="wrap_content"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textColor="?android:attr/textColorSecondary"
+        app:layout_constrainedHeight="true"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="@android:id/title"
         app:layout_constraintTop_toBottomOf="@android:id/title"
@@ -75,6 +75,7 @@
         android:adjustViewBounds="true"
         android:background="@drawable/protection_background"
         android:maxHeight="@dimen/accessibility_imageview_size"
+        app:layout_constrainedHeight="true"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_bias="0"
         app:layout_constraintStart_toStartOf="@android:id/title"
diff --git a/res/values/config.xml b/res/values/config.xml
index d084ff9..d0cd96b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -38,6 +38,11 @@
     <!-- Whether to show Camera laser sensor switch in Developer Options -->
     <bool name="config_show_camera_laser_sensor">false</bool>
 
+    <!-- Intent action to open Avatar Picker app -->
+    <string name="config_avatar_picker_action" translatable="false">
+        com.android.avatarpicker.FULL_SCREEN_ACTIVITY
+    </string>
+
     <!-- Package name and fully-qualified class name for the wallpaper picker activity. -->
     <string name="config_wallpaper_picker_package" translatable="false">com.android.settings</string>
     <string name="config_wallpaper_picker_class" translatable="false">com.android.settings.Settings$WallpaperSettingsActivity</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7465a60..0b12142 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1288,7 +1288,7 @@
     <string name="private_space_error_screen_title">Couldn\u2019t set up private space</string>
     <!-- Label for button to retry creating private space again on creation error. [CHAR LIMIT=30] -->
     <string name="private_space_tryagain_label">Try Again</string>
-    <!-- Title for private space lock setup screen. [CHAR LIMIT=50] -->
+    <!-- Title for private space lock setup screen. [CHAR LIMIT=90] -->
     <string name="private_space_lockscreen_title">Use screen lock to unlock private space?</string>
     <!-- Summary for the private space lock setup screen. [CHAR LIMIT=NONE] -->
     <string name="private_space_lockscreen_summary">You can unlock your private space the same way you unlock your device, or choose a different lock</string>
diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml
index 48835fc..4af02dd 100644
--- a/res/xml/private_space_settings.xml
+++ b/res/xml/private_space_settings.xml
@@ -63,4 +63,10 @@
 
     </PreferenceCategory>
 
+    <com.android.settings.accessibility.AccessibilityFooterPreference
+        android:key="private_space_footer"
+        android:title="@string/private_space_apps_permission_text"
+        android:selectable="false"
+        settings:searchable="false"/>
+
 </PreferenceScreen>
diff --git a/src/com/android/settings/IccLockSettings.java b/src/com/android/settings/IccLockSettings.java
index 4628221..422610a 100644
--- a/src/com/android/settings/IccLockSettings.java
+++ b/src/com/android/settings/IccLockSettings.java
@@ -52,6 +52,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
 import androidx.preference.Preference;
 import androidx.preference.TwoStatePreference;
 
@@ -716,13 +717,18 @@
         return slotId;
     }
 
+    @Nullable
     private SubscriptionInfo getVisibleSubscriptionInfoForSimSlotIndex(int slotId) {
         final List<SubscriptionInfo> subInfoList =
                 mProxySubscriptionMgr.getActiveSubscriptionsInfo();
         if (subInfoList == null) {
             return null;
         }
-        final CarrierConfigManager carrierConfigManager = getContext().getSystemService(
+        Context context = getContext();
+        if (context == null) {
+            return null;
+        }
+        final CarrierConfigManager carrierConfigManager = context.getSystemService(
                 CarrierConfigManager.class);
         for (SubscriptionInfo subInfo : subInfoList) {
             if ((isSubscriptionVisible(carrierConfigManager, subInfo)
diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
index 3b79b47..50134ba 100644
--- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
+++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
@@ -34,6 +34,7 @@
 import androidx.window.embedding.SplitPlaceholderRule;
 import androidx.window.embedding.SplitRule;
 
+import com.android.settings.R;
 import com.android.settings.Settings;
 import com.android.settings.SettingsActivity;
 import com.android.settings.SubSettings;
@@ -261,8 +262,13 @@
         addActivityFilter(activityFilters, FaceEnrollIntroduction.class);
         addActivityFilter(activityFilters, RemoteAuthActivity.class);
         addActivityFilter(activityFilters, RemoteAuthActivityInternal.class);
-        addActivityFilter(activityFilters, AvatarPickerActivity.class);
         addActivityFilter(activityFilters, ChooseLockPattern.class);
+        if (android.multiuser.Flags.avatarSync()) {
+            String action = mContext.getString(R.string.config_avatar_picker_action);
+            addActivityFilter(activityFilters, new Intent(action));
+        } else {
+            addActivityFilter(activityFilters, AvatarPickerActivity.class);
+        }
         ActivityRule activityRule = new ActivityRule.Builder(activityFilters).setAlwaysExpand(true)
                 .build();
         mRuleController.addRule(activityRule);
diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
index 80d3947..081d99a 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.applications;
 
+import static android.webkit.Flags.updateServiceV2;
+
 import android.Manifest;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -41,6 +43,7 @@
 
 import com.android.internal.telephony.SmsApplication;
 import com.android.settings.R;
+import com.android.settings.webview.WebViewUpdateServiceWrapper;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -54,6 +57,7 @@
     private final IPackageManager mPms;
     private final DevicePolicyManager mDpm;
     private final UserManager mUm;
+    private final WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
     /** Flags to use when querying PackageManager for Euicc component implementations. */
     private static final int EUICC_QUERY_FLAGS =
             PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
@@ -61,11 +65,16 @@
 
     public ApplicationFeatureProviderImpl(Context context, PackageManager pm,
             IPackageManager pms, DevicePolicyManager dpm) {
+        this(context, pm, pms, dpm, new WebViewUpdateServiceWrapper());
+    }
+    public ApplicationFeatureProviderImpl(Context context, PackageManager pm,
+            IPackageManager pms, DevicePolicyManager dpm, WebViewUpdateServiceWrapper wvusWrapper) {
         mContext = context.getApplicationContext();
         mPm = pm;
         mPms = pms;
         mDpm = dpm;
         mUm = UserManager.get(mContext);
+        mWebViewUpdateServiceWrapper = wvusWrapper;
     }
 
     @Override
@@ -159,6 +168,14 @@
             keepEnabledPackages.add(euicc.packageName);
         }
 
+        // Keep WebView default package enabled.
+        if (updateServiceV2()) {
+            String packageName = mWebViewUpdateServiceWrapper.getDefaultWebViewPackageName();
+            if (packageName != null) {
+                keepEnabledPackages.add(packageName);
+            }
+        }
+
         keepEnabledPackages.addAll(getEnabledPackageAllowlist());
 
         final LocationManager locationManager =
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index 6e90885..1b80838 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -53,7 +53,6 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.ProgressBar;
@@ -191,7 +190,7 @@
     private boolean mHaveShownSfpsRightEdgeLottie;
     private boolean mShouldShowLottie;
 
-    private ObjectAnimator mHelpAnimation;
+    private Animator mHelpAnimation;
 
     private OrientationEventListener mOrientationEventListener;
     private int mPreviousRotation = 0;
@@ -341,16 +340,10 @@
     }
 
     private void setHelpAnimation() {
-        final float translationX = 40;
-        final int duration = 550;
         final RelativeLayout progressLottieLayout = findViewById(R.id.progress_lottie);
-        mHelpAnimation = ObjectAnimator.ofFloat(progressLottieLayout,
-                "translationX" /* propertyName */,
-                0, translationX, -1 * translationX, translationX, 0f);
-        mHelpAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
-        mHelpAnimation.setDuration(duration);
-        mHelpAnimation.setAutoCancel(false);
+        mHelpAnimation = mSfpsEnrollmentFeature.getHelpAnimator(progressLottieLayout);
     }
+
     @Override
     protected BiometricEnrollSidecar getSidecar() {
         final FingerprintEnrollSidecar sidecar = new FingerprintEnrollSidecar(this,
@@ -703,6 +696,9 @@
         view.setComposition(composition);
         view.setVisibility(View.VISIBLE);
         view.playAnimation();
+        if (mCanAssumeSfps) {
+            mSfpsEnrollmentFeature.handleOnEnrollmentLottieComposition(view);
+        }
     }
 
     @EnrollStage
@@ -1226,5 +1222,10 @@
         public float getEnrollStageThreshold(@NonNull Context context, int index) {
             throw new IllegalStateException(exceptionStr);
         }
+
+        @Override
+        public Animator getHelpAnimator(@NonNull View target) {
+            throw new IllegalStateException(exceptionStr);
+        }
     }
 }
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
index a1a18e5..f99d394 100644
--- a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
@@ -16,12 +16,16 @@
 
 package com.android.settings.biometrics.fingerprint.feature;
 
+import android.animation.Animator;
 import android.content.Context;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
 
+import com.airbnb.lottie.LottieAnimationView;
+
 import java.util.function.Function;
 import java.util.function.Supplier;
 
@@ -76,4 +80,17 @@
      * @return threshold
      */
     float getEnrollStageThreshold(@NonNull Context context, int index);
+
+    /**
+     * Gets the help animator used when get help message.
+     * @param target the target view to animate
+     * @return animator
+     */
+    Animator getHelpAnimator(@NonNull View target);
+
+    /**
+     * Handles extra stuffs on lottie composition.
+     * @param lottieView the view related to the lottie
+     */
+    default void handleOnEnrollmentLottieComposition(LottieAnimationView lottieView) {}
 }
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
index 5a97537..abc31c9 100644
--- a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
@@ -23,8 +23,12 @@
 import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_RIGHT_EDGE;
 import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.STAGE_UNKNOWN;
 
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.hardware.fingerprint.FingerprintManager;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -88,4 +92,17 @@
         }
         return mFingerprintManager.getEnrollStageThreshold(index);
     }
+
+    @Override
+    public Animator getHelpAnimator(@NonNull View target) {
+        final float translationX = 40;
+        final int duration = 550;
+        final ObjectAnimator help = ObjectAnimator.ofFloat(target,
+                "translationX" /* propertyName */,
+                0, translationX, -1 * translationX, translationX, 0f);
+        help.setInterpolator(new AccelerateInterpolator());
+        help.setDuration(duration);
+        help.setAutoCancel(false);
+        return help;
+    }
 }
diff --git a/src/com/android/settings/datausage/ChartDataUsagePreferenceController.kt b/src/com/android/settings/datausage/ChartDataUsagePreferenceController.kt
index 780978f..34dcb4d 100644
--- a/src/com/android/settings/datausage/ChartDataUsagePreferenceController.kt
+++ b/src/com/android/settings/datausage/ChartDataUsagePreferenceController.kt
@@ -18,39 +18,22 @@
 
 import android.content.Context
 import android.net.NetworkTemplate
-import androidx.annotation.OpenForTesting
-import androidx.annotation.VisibleForTesting
-import androidx.lifecycle.LifecycleCoroutineScope
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
 import androidx.preference.PreferenceScreen
 import com.android.settings.core.BasePreferenceController
 import com.android.settings.datausage.lib.INetworkCycleDataRepository
 import com.android.settings.datausage.lib.NetworkCycleChartData
 import com.android.settings.datausage.lib.NetworkCycleDataRepository
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
 
-@OpenForTesting
-open class ChartDataUsagePreferenceController(context: Context, preferenceKey: String) :
+class ChartDataUsagePreferenceController(context: Context, preferenceKey: String) :
     BasePreferenceController(context, preferenceKey) {
 
     private lateinit var repository: INetworkCycleDataRepository
     private lateinit var preference: ChartDataUsagePreference
-    private lateinit var lifecycleScope: LifecycleCoroutineScope
-    private var lastStartTime: Long? = null
-    private var lastEndTime: Long? = null
 
-    open fun init(template: NetworkTemplate) {
+    fun init(template: NetworkTemplate) {
         this.repository = NetworkCycleDataRepository(mContext, template)
     }
 
-    @VisibleForTesting
-    fun init(repository: INetworkCycleDataRepository) {
-        this.repository = repository
-    }
-
     override fun getAvailabilityStatus() = AVAILABLE
 
     override fun displayPreference(screen: PreferenceScreen) {
@@ -58,33 +41,20 @@
         preference = screen.findPreference(preferenceKey)!!
     }
 
-    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
-        lifecycleScope = viewLifecycleOwner.lifecycleScope
-    }
-
     /**
      * Sets whether billing cycle modifiable.
      *
      * Don't bind warning / limit sweeps if not modifiable.
      */
-    open fun setBillingCycleModifiable(isModifiable: Boolean) {
+    fun setBillingCycleModifiable(isModifiable: Boolean) {
         preference.setNetworkPolicy(
             if (isModifiable) repository.getPolicy() else null
         )
     }
 
-    fun update(startTime: Long, endTime: Long) {
-        if (lastStartTime == startTime && lastEndTime == endTime) return
-        lastStartTime = startTime
-        lastEndTime = endTime
-
-        preference.setTime(startTime, endTime)
-        preference.setNetworkCycleData(NetworkCycleChartData.AllZero)
-        lifecycleScope.launch {
-            val chartData = withContext(Dispatchers.Default) {
-                repository.queryChartData(startTime, endTime)
-            }
-            preference.setNetworkCycleData(chartData)
-        }
+    /** Updates chart to show selected cycle. */
+    fun update(chartData: NetworkCycleChartData) {
+        preference.setTime(chartData.total.startTime, chartData.total.endTime)
+        preference.setNetworkCycleData(chartData)
     }
 }
diff --git a/src/com/android/settings/datausage/DataUsageList.kt b/src/com/android/settings/datausage/DataUsageList.kt
index 30e8db3..6a187d8 100644
--- a/src/com/android/settings/datausage/DataUsageList.kt
+++ b/src/com/android/settings/datausage/DataUsageList.kt
@@ -27,6 +27,7 @@
 import android.view.View
 import androidx.annotation.OpenForTesting
 import androidx.annotation.VisibleForTesting
+import androidx.fragment.app.viewModels
 import androidx.preference.Preference
 import com.android.settings.R
 import com.android.settings.datausage.lib.BillingCycleRepository
@@ -59,6 +60,8 @@
     private lateinit var chartDataUsagePreferenceController: ChartDataUsagePreferenceController
     private lateinit var billingCycleRepository: BillingCycleRepository
 
+    private val viewModel: DataUsageListViewModel by viewModels()
+
     @VisibleForTesting
     var dataUsageListHeaderController: DataUsageListHeaderController? = null
 
@@ -104,14 +107,21 @@
             .collectLatestWithLifecycle(viewLifecycleOwner) { updatePolicy() }
 
         val template = template ?: return
+        viewModel.templateFlow.value = template
         dataUsageListHeaderController = DataUsageListHeaderController(
             setPinnedHeaderView(R.layout.apps_filter_spinner),
             template,
             metricsCategory,
             viewLifecycleOwner,
-            ::onCyclesLoad,
+            viewModel.cyclesFlow,
             ::updateSelectedCycle,
         )
+        viewModel.cyclesFlow.collectLatestWithLifecycle(viewLifecycleOwner) { cycles ->
+            dataUsageListAppsController.updateCycles(cycles)
+        }
+        viewModel.chartDataFlow.collectLatestWithLifecycle(viewLifecycleOwner) { chartData ->
+            chartDataUsagePreferenceController.update(chartData)
+        }
     }
 
     override fun getPreferenceScreenResId() = R.xml.data_usage_list
@@ -158,10 +168,6 @@
                 .getActiveSubscriptionInfo(subId) != null)
     }
 
-    private fun onCyclesLoad(networkUsageData: List<NetworkUsageData>) {
-        dataUsageListAppsController.updateCycles(networkUsageData)
-    }
-
     /**
      * Updates the chart and detail data when initial loaded or selected cycle changed.
      */
@@ -169,19 +175,11 @@
         Log.d(TAG, "showing cycle $usageData")
 
         usageAmount.title = usageData.getDataUsedString(requireContext())
+        viewModel.selectedCycleFlow.value = usageData
 
-        updateChart(usageData)
         updateApps(usageData)
     }
 
-    /** Updates chart to show selected cycle. */
-    private fun updateChart(usageData: NetworkUsageData) {
-        chartDataUsagePreferenceController.update(
-            startTime = usageData.startTime,
-            endTime = usageData.endTime,
-        )
-    }
-
     /** Updates applications data usage. */
     private fun updateApps(usageData: NetworkUsageData) {
         dataUsageListAppsController.update(
diff --git a/src/com/android/settings/datausage/DataUsageListHeaderController.kt b/src/com/android/settings/datausage/DataUsageListHeaderController.kt
index ad76ede..ade891a 100644
--- a/src/com/android/settings/datausage/DataUsageListHeaderController.kt
+++ b/src/com/android/settings/datausage/DataUsageListHeaderController.kt
@@ -24,19 +24,13 @@
 import android.widget.AdapterView
 import android.widget.Spinner
 import androidx.annotation.OpenForTesting
-import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
 import com.android.settings.R
 import com.android.settings.core.SubSettingLauncher
 import com.android.settings.datausage.CycleAdapter.SpinnerInterface
-import com.android.settings.datausage.lib.INetworkCycleDataRepository
-import com.android.settings.datausage.lib.NetworkCycleDataRepository
 import com.android.settings.datausage.lib.NetworkUsageData
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import kotlinx.coroutines.flow.Flow
 
 @OpenForTesting
 open class DataUsageListHeaderController(
@@ -44,10 +38,8 @@
     template: NetworkTemplate,
     sourceMetricsCategory: Int,
     viewLifecycleOwner: LifecycleOwner,
-    private val onCyclesLoad: (usageDataList: List<NetworkUsageData>) -> Unit,
+    cyclesFlow: Flow<List<NetworkUsageData>>,
     private val updateSelectedCycle: (usageData: NetworkUsageData) -> Unit,
-    private val repository: INetworkCycleDataRepository =
-        NetworkCycleDataRepository(header.context, template),
 ) {
     private val context = header.context
 
@@ -104,13 +96,9 @@
             }
         }
 
-        viewLifecycleOwner.lifecycleScope.launch {
-            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-                cycles = withContext(Dispatchers.Default) {
-                    repository.loadCycles()
-                }
-                updateCycleData()
-            }
+        cyclesFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
+            cycles = it
+            updateCycleData()
         }
     }
 
@@ -121,7 +109,6 @@
     private fun updateCycleData() {
         cycleAdapter.updateCycleList(cycles.map { Range(it.startTime, it.endTime) })
         cycleSpinner.visibility = View.VISIBLE
-        onCyclesLoad(cycles)
     }
 
     private fun setSelectedCycle(position: Int) {
diff --git a/src/com/android/settings/datausage/DataUsageListViewModel.kt b/src/com/android/settings/datausage/DataUsageListViewModel.kt
new file mode 100644
index 0000000..7d7a1d6
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsageListViewModel.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.datausage
+
+import android.app.Application
+import android.net.NetworkTemplate
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.settings.datausage.lib.NetworkCycleBucketRepository
+import com.android.settings.datausage.lib.NetworkStatsRepository
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.filterTime
+import com.android.settings.datausage.lib.NetworkUsageData
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.plus
+
+data class SelectedBuckets(
+    val selectedCycle: NetworkUsageData,
+    val buckets: List<Bucket>,
+)
+
+class DataUsageListViewModel(application: Application) : AndroidViewModel(application) {
+    private val scope = viewModelScope + Dispatchers.Default
+
+    val templateFlow = MutableStateFlow<NetworkTemplate?>(null)
+
+    private val bucketsFlow = templateFlow.filterNotNull().map { template ->
+        NetworkStatsRepository(getApplication(), template).queryDetailsForDevice()
+    }.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+    val cyclesFlow = combine(templateFlow.filterNotNull(), bucketsFlow) { template, buckets ->
+        NetworkCycleBucketRepository(application, template, buckets).loadCycles()
+    }.flowOn(Dispatchers.Default)
+
+    val selectedCycleFlow = MutableStateFlow<NetworkUsageData?>(null)
+
+    private val selectedBucketsFlow =
+        combine(selectedCycleFlow.filterNotNull(), bucketsFlow) { selectedCycle, buckets ->
+            SelectedBuckets(
+                selectedCycle = selectedCycle,
+                buckets = buckets.filterTime(selectedCycle.startTime, selectedCycle.endTime),
+            )
+        }.flowOn(Dispatchers.Default)
+
+    val chartDataFlow =
+        combine(templateFlow.filterNotNull(), selectedBucketsFlow) { template, selectedBuckets ->
+            NetworkCycleBucketRepository(application, template, selectedBuckets.buckets)
+                .queryChartData(selectedBuckets.selectedCycle)
+        }.flowOn(Dispatchers.Default)
+}
diff --git a/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt b/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt
new file mode 100644
index 0000000..652919e0
--- /dev/null
+++ b/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.datausage.lib
+
+import android.content.Context
+import android.net.NetworkTemplate
+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.reverseBucketRange
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.aggregate
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.filterTime
+
+class NetworkCycleBucketRepository(
+    context: Context,
+    networkTemplate: NetworkTemplate,
+    private val buckets: List<Bucket>,
+    private val networkCycleDataRepository: NetworkCycleDataRepository =
+        NetworkCycleDataRepository(context, networkTemplate)
+) {
+
+    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 queryCyclesAsFourWeeks(): List<Range<Long>> {
+        val timeRange = buckets.aggregate()?.timeRange ?: return emptyList()
+        return reverseBucketRange(
+            startTime = timeRange.lower,
+            endTime = timeRange.upper,
+            step = DateUtils.WEEK_IN_MILLIS * 4,
+        )
+    }
+
+    fun queryChartData(usageData: NetworkUsageData) = NetworkCycleChartData(
+        total = usageData,
+        dailyUsage = bucketRange(
+            startTime = usageData.startTime,
+            endTime = usageData.endTime,
+            step = NetworkCycleChartData.BUCKET_DURATION.inWholeMilliseconds,
+        ).map { aggregateUsage(it) },
+    )
+
+    private fun aggregateUsage(range: Range<Long>) = NetworkUsageData(
+        startTime = range.lower,
+        endTime = range.upper,
+        usage = buckets.filterTime(range.lower, range.upper).aggregate()?.usage ?: 0,
+    )
+}
diff --git a/src/com/android/settings/datausage/lib/NetworkCycleChartData.kt b/src/com/android/settings/datausage/lib/NetworkCycleChartData.kt
index 4e73190..fd3c504 100644
--- a/src/com/android/settings/datausage/lib/NetworkCycleChartData.kt
+++ b/src/com/android/settings/datausage/lib/NetworkCycleChartData.kt
@@ -26,11 +26,6 @@
     val dailyUsage: List<NetworkUsageData>,
 ) {
     companion object {
-        val AllZero = NetworkCycleChartData(
-            total = NetworkUsageData.AllZero,
-            dailyUsage = emptyList(),
-        )
-
         val BUCKET_DURATION = 1.days
     }
 }
diff --git a/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt b/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
index 4256130..cde64df 100644
--- a/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
+++ b/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
@@ -23,13 +23,10 @@
 import android.text.format.DateUtils
 import android.util.Range
 import com.android.settingslib.NetworkPolicyEditor
-import com.android.settingslib.spa.framework.util.asyncMap
 
 interface INetworkCycleDataRepository {
-    suspend fun loadCycles(): List<NetworkUsageData>
     fun getCycles(): List<Range<Long>>
     fun getPolicy(): NetworkPolicy?
-    suspend fun queryChartData(startTime: Long, endTime: Long): NetworkCycleChartData?
 }
 
 class NetworkCycleDataRepository(
@@ -41,9 +38,6 @@
 
     private val policyManager = context.getSystemService(NetworkPolicyManager::class.java)!!
 
-    override suspend fun loadCycles(): List<NetworkUsageData> =
-        getCycles().queryUsage().filter { it.usage > 0 }
-
     fun loadFirstCycle(): NetworkUsageData? = getCycles().firstOrNull()?.let { queryUsage(it) }
 
     override fun getCycles(): List<Range<Long>> {
@@ -68,23 +62,6 @@
             getPolicy(networkTemplate)
         }
 
-    override suspend fun queryChartData(startTime: Long, endTime: Long): NetworkCycleChartData? {
-        val usage = networkStatsRepository.querySummaryForDevice(startTime, endTime)
-        if (usage > 0L) {
-            return NetworkCycleChartData(
-                total = NetworkUsageData(startTime, endTime, usage),
-                dailyUsage = bucketRange(
-                    startTime = startTime,
-                    endTime = endTime,
-                    step = NetworkCycleChartData.BUCKET_DURATION.inWholeMilliseconds,
-                ).queryUsage(),
-            )
-        }
-        return null
-    }
-
-    private suspend fun List<Range<Long>>.queryUsage(): List<NetworkUsageData> =
-        asyncMap { queryUsage(it) }
 
     fun queryUsage(range: Range<Long>) = NetworkUsageData(
         startTime = range.lower,
@@ -92,10 +69,12 @@
         usage = networkStatsRepository.querySummaryForDevice(range.lower, range.upper),
     )
 
-    private fun bucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
-        (startTime..endTime step step).zipWithNext(::Range)
+    companion object {
+        fun bucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
+            (startTime..endTime step step).zipWithNext(::Range)
 
-    private fun reverseBucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
-        (endTime downTo (startTime - step + 1) step step)
-            .zipWithNext { bucketEnd, bucketStart -> Range(bucketStart, bucketEnd) }
+        fun reverseBucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
+            (endTime downTo (startTime - step + 1) step step)
+                .zipWithNext { bucketEnd, bucketStart -> Range(bucketStart, bucketEnd) }
+    }
 }
diff --git a/src/com/android/settings/datausage/lib/NetworkStatsRepository.kt b/src/com/android/settings/datausage/lib/NetworkStatsRepository.kt
index f2e18f2..56b1931 100644
--- a/src/com/android/settings/datausage/lib/NetworkStatsRepository.kt
+++ b/src/com/android/settings/datausage/lib/NetworkStatsRepository.kt
@@ -33,20 +33,22 @@
     ): NetworkUsageData? = try {
         networkStatsManager.queryDetailsForUidTagState(
             template, range.lower, range.upper, uid, NetworkStats.Bucket.TAG_NONE, state,
-        ).aggregate()
+        ).convertToBuckets().aggregate()
     } catch (e: Exception) {
         Log.e(TAG, "Exception queryDetailsForUidTagState", e)
         null
     }
 
-    fun getTimeRange(): Range<Long>? = try {
+    fun queryDetailsForDevice(): List<Bucket> = try {
         networkStatsManager.queryDetailsForDevice(template, Long.MIN_VALUE, Long.MAX_VALUE)
-            .aggregate()?.timeRange
+            .convertToBuckets()
     } catch (e: Exception) {
         Log.e(TAG, "Exception queryDetailsForDevice", e)
-        null
+        emptyList()
     }
 
+    fun getTimeRange(): Range<Long>? = queryDetailsForDevice().aggregate()?.timeRange
+
     fun querySummaryForDevice(startTime: Long, endTime: Long): Long = try {
         networkStatsManager.querySummaryForDevice(template, startTime, endTime).bytes
     } catch (e: Exception) {
@@ -70,33 +72,38 @@
             val uid: Int,
             val bytes: Long,
             val state: Int = NetworkStats.Bucket.STATE_ALL,
+            val startTimeStamp: Long,
+            val endTimeStamp: Long,
         )
 
+        fun List<Bucket>.aggregate(): NetworkUsageData? = when {
+            isEmpty() -> null
+            else -> NetworkUsageData(
+                startTime = minOf { it.startTimeStamp },
+                endTime = maxOf { it.endTimeStamp },
+                usage = sumOf { it.bytes },
+            )
+        }
+
+        fun List<Bucket>.filterTime(startTime: Long, endTime: Long): List<Bucket> = filter {
+            it.startTimeStamp >= startTime && it.endTimeStamp <= endTime
+        }
+
         private fun NetworkStats.convertToBuckets(): List<Bucket> = use {
             val buckets = mutableListOf<Bucket>()
             val bucket = NetworkStats.Bucket()
             while (getNextBucket(bucket)) {
-                buckets += Bucket(uid = bucket.uid, bytes = bucket.bytes, state = bucket.state)
+                buckets += Bucket(
+                    uid = bucket.uid,
+                    bytes = bucket.bytes,
+                    state = bucket.state,
+                    startTimeStamp = bucket.startTimeStamp,
+                    endTimeStamp = bucket.endTimeStamp,
+                )
             }
             buckets
         }
 
-        private fun NetworkStats.aggregate(): NetworkUsageData? = use {
-            var startTime = Long.MAX_VALUE
-            var endTime = Long.MIN_VALUE
-            var usage = 0L
-            val bucket = NetworkStats.Bucket()
-            while (getNextBucket(bucket)) {
-                startTime = startTime.coerceAtMost(bucket.startTimeStamp)
-                endTime = endTime.coerceAtLeast(bucket.endTimeStamp)
-                usage += bucket.bytes
-            }
-            when {
-                startTime > endTime -> null
-                else -> NetworkUsageData(startTime, endTime, usage)
-            }
-        }
-
         private val NetworkStats.Bucket.bytes: Long
             get() = rxBytes + txBytes
     }
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index 403263c..c0fa313 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -63,7 +63,7 @@
 
     @Composable
     private fun getActionButtons(app: ApplicationInfo): List<ActionButton> = listOfNotNull(
-        if (featureFlags.archiving()) {
+        if (isArchivingEnabled(featureFlags)) {
             if (app.isArchived) {
                 appRestoreButton.getActionButton(app)
             } else {
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index 85e59de..9291892 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -125,7 +125,7 @@
         title = stringResource(R.string.application_info_label),
         actions = {
             packageInfoState.value?.applicationInfo?.let { app ->
-                if (featureFlags.archiving()) TopBarAppLaunchButton(packageInfoPresenter, app)
+                if (isArchivingEnabled(featureFlags)) TopBarAppLaunchButton(packageInfoPresenter, app)
                 AppInfoSettingsMoreOptions(packageInfoPresenter, app)
             }
         }
@@ -174,3 +174,6 @@
         appInfoProvider.FooterAppVersion()
     }
 }
+
+fun isArchivingEnabled(featureFlags: FeatureFlags) =
+        featureFlags.archiving() || "true" == System.getProperty("pm.archiving.enabled")
diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
index 8c802d1..230ccb9 100644
--- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
@@ -166,7 +166,7 @@
             flags = PackageManager.MATCH_ANY_USER.toLong() or
                 PackageManager.MATCH_DISABLED_COMPONENTS.toLong() or
                 PackageManager.GET_PERMISSIONS.toLong() or
-                if (featureFlags.archiving()) PackageManager.MATCH_ARCHIVED_PACKAGES else 0,
+                if (isArchivingEnabled(featureFlags)) PackageManager.MATCH_ARCHIVED_PACKAGES else 0,
             userId = userId,
         )
 }
diff --git a/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java b/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
index ff4ae41..05855e4 100644
--- a/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
+++ b/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
@@ -22,11 +22,14 @@
 import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.util.Log;
+import android.webkit.IWebViewUpdateService;
 import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewProviderInfo;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -104,5 +107,24 @@
         toast.show();
     }
 
+    /**
+     * Fetch the package name of the default WebView provider.
+     */
+    @Nullable
+    public String getDefaultWebViewPackageName() {
+        try {
+            IWebViewUpdateService service = WebViewFactory.getUpdateService();
+            if (service != null) {
+                WebViewProviderInfo provider = service.getDefaultWebViewPackage();
+                if (provider != null) {
+                    return provider.packageName;
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException when trying to fetch default WebView package Name", e);
+        }
+        return null;
+    }
+
     static final int PACKAGE_FLAGS = PackageManager.MATCH_ANY_USER;
 }
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index 95a78c7..d313878 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -57,6 +57,7 @@
         "Settings_robolectric_meta_service_file",
         "SettingsLib-robo-testutils",
         "Settings-robo-testutils",
+        "android.webkit.flags-aconfig-java",
         "androidx.test.core",
         "androidx.test.espresso.core",
         "androidx.test.ext.junit",
diff --git a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
index 6bed1bc..bac0de9 100644
--- a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
@@ -37,13 +37,20 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
+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.webkit.Flags;
 
 import com.android.settings.testutils.ApplicationTestUtils;
+import com.android.settings.webview.WebViewUpdateServiceWrapper;
 import com.android.settingslib.testutils.shadow.ShadowDefaultDialerManager;
 import com.android.settingslib.testutils.shadow.ShadowSmsApplication;
 
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -67,6 +74,9 @@
 @LooperMode(LooperMode.Mode.LEGACY)
 public final class ApplicationFeatureProviderImplTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private final int MAIN_USER_ID = 0;
     private final int MANAGED_PROFILE_ID = 10;
 
@@ -91,6 +101,8 @@
     private DevicePolicyManager mDevicePolicyManager;
     @Mock
     private LocationManager mLocationManager;
+    @Mock
+    private WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
 
     private ApplicationFeatureProvider mProvider;
 
@@ -106,7 +118,7 @@
         when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
 
         mProvider = new ApplicationFeatureProviderImpl(mContext, mPackageManager,
-                mPackageManagerService, mDevicePolicyManager);
+                mPackageManagerService, mDevicePolicyManager, mWebViewUpdateServiceWrapper);
     }
 
     private void verifyCalculateNumberOfPolicyInstalledApps(boolean async) {
@@ -342,6 +354,26 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_UPDATE_SERVICE_V2)
+    public void getKeepEnabledPackages_shouldContainWebViewPackage() {
+        final String testWebViewPackageName = "com.android.webview";
+        when(mWebViewUpdateServiceWrapper.getDefaultWebViewPackageName())
+                .thenReturn(testWebViewPackageName);
+        final Set<String> allowlist = mProvider.getKeepEnabledPackages();
+        assertThat(allowlist).contains(testWebViewPackageName);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_UPDATE_SERVICE_V2)
+    public void getKeepEnabledPackages_shouldNotContainWebViewPackageIfFlagDisabled() {
+        final String testWebViewPackageName = "com.android.webview";
+        when(mWebViewUpdateServiceWrapper.getDefaultWebViewPackageName())
+                .thenReturn(testWebViewPackageName);
+        final Set<String> allowlist = mProvider.getKeepEnabledPackages();
+        assertThat(allowlist).doesNotContain(testWebViewPackageName);
+    }
+
+    @Test
     @Config(shadows = {ShadowSmsApplication.class, ShadowDefaultDialerManager.class})
     public void getKeepEnabledPackages_shouldContainPackageInstaller() {
         final String testDialer = "com.android.test.defaultdialer";
diff --git a/tests/spa_unit/src/com/android/settings/datausage/ChartDataUsagePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/ChartDataUsagePreferenceControllerTest.kt
index ae09ef9..f061fe1 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/ChartDataUsagePreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/ChartDataUsagePreferenceControllerTest.kt
@@ -17,16 +17,12 @@
 package com.android.settings.datausage
 
 import android.content.Context
-import android.util.Range
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.preference.PreferenceScreen
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settings.datausage.lib.INetworkCycleDataRepository
 import com.android.settings.datausage.lib.NetworkCycleChartData
 import com.android.settings.datausage.lib.NetworkUsageData
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.runBlocking
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,17 +34,6 @@
 class ChartDataUsagePreferenceControllerTest {
     private val context: Context = ApplicationProvider.getApplicationContext()
 
-    private val repository = object : INetworkCycleDataRepository {
-        override suspend fun loadCycles() = emptyList<NetworkUsageData>()
-        override fun getCycles() = emptyList<Range<Long>>()
-        override fun getPolicy() = null
-
-        override suspend fun queryChartData(startTime: Long, endTime: Long) = when {
-            startTime == START_TIME && endTime == END_TIME -> CycleChartDate
-            else -> null
-        }
-    }
-
     private val preference = mock<ChartDataUsagePreference>()
     private val preferenceScreen = mock<PreferenceScreen> {
         onGeneric { findPreference(KEY) } doReturn preference
@@ -58,15 +43,13 @@
 
     @Before
     fun setUp() {
-        controller.init(repository)
         controller.displayPreference(preferenceScreen)
         controller.onViewCreated(TestLifecycleOwner())
     }
 
     @Test
-    fun update() = runBlocking {
-        controller.update(START_TIME, END_TIME)
-        delay(100L)
+    fun update() {
+        controller.update(CycleChartDate)
 
         verify(preference).setTime(START_TIME, END_TIME)
         verify(preference).setNetworkCycleData(CycleChartDate)
diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt
index 6d5be6b..8c4f315 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.net.NetworkTemplate
-import android.util.Range
 import android.view.LayoutInflater
 import android.view.View
 import android.widget.Spinner
@@ -27,10 +26,9 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.R
-import com.android.settings.datausage.lib.INetworkCycleDataRepository
-import com.android.settings.datausage.lib.NetworkUsageData
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,13 +45,6 @@
         doNothing().whenever(mock).startActivity(any())
     }
 
-    private val repository = object : INetworkCycleDataRepository {
-        override suspend fun loadCycles() = emptyList<NetworkUsageData>()
-        override fun getCycles() = emptyList<Range<Long>>()
-        override fun getPolicy() = null
-        override suspend fun queryChartData(startTime: Long, endTime: Long) = null
-    }
-
     private val header =
         LayoutInflater.from(context).inflate(R.layout.apps_filter_spinner, null, false)
 
@@ -68,9 +59,8 @@
         template = mock<NetworkTemplate>(),
         sourceMetricsCategory = 0,
         viewLifecycleOwner = testLifecycleOwner,
-        onCyclesLoad = {},
+        cyclesFlow = flowOf(emptyList()),
         updateSelectedCycle = {},
-        repository = repository,
     )
 
     @Test
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageDetailsRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageDetailsRepositoryTest.kt
index 85431a4..fda3dc9 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageDetailsRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageDetailsRepositoryTest.kt
@@ -49,13 +49,17 @@
             on { queryBuckets(CYCLE1_START_TIME, CYCLE1_END_TIME) } doReturn listOf(
                 Bucket(
                     uid = UID,
-                    state = NetworkStats.Bucket.STATE_DEFAULT,
                     bytes = BACKGROUND_USAGE,
+                    state = NetworkStats.Bucket.STATE_DEFAULT,
+                    startTimeStamp = 0L,
+                    endTimeStamp = 0L,
                 ),
                 Bucket(
                     uid = UID,
-                    state = NetworkStats.Bucket.STATE_FOREGROUND,
                     bytes = FOREGROUND_USAGE,
+                    state = NetworkStats.Bucket.STATE_FOREGROUND,
+                    startTimeStamp = 0L,
+                    endTimeStamp = 0L,
                 ),
             )
         }
@@ -86,13 +90,17 @@
             on { queryBuckets(CYCLE1_END_TIME, CYCLE2_END_TIME) } doReturn listOf(
                 Bucket(
                     uid = UID,
-                    state = NetworkStats.Bucket.STATE_DEFAULT,
                     bytes = BACKGROUND_USAGE,
+                    state = NetworkStats.Bucket.STATE_DEFAULT,
+                    startTimeStamp = 0L,
+                    endTimeStamp = 0L,
                 ),
                 Bucket(
                     uid = UID,
-                    state = NetworkStats.Bucket.STATE_FOREGROUND,
                     bytes = FOREGROUND_USAGE,
+                    state = NetworkStats.Bucket.STATE_FOREGROUND,
+                    startTimeStamp = 0L,
+                    endTimeStamp = 0L,
                 ),
             )
         }
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageRepositoryTest.kt
index f2bf524..3f517a9 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageRepositoryTest.kt
@@ -77,8 +77,8 @@
             getPackageName = { null },
         )
         val buckets = listOf(
-            Bucket(uid = APP_ID_1, bytes = 1),
-            Bucket(uid = APP_ID_2, bytes = 2),
+            Bucket(uid = APP_ID_1, bytes = 1, startTimeStamp = 0, endTimeStamp = 0),
+            Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
         )
 
         val appPercentList = repository.getAppPercent(null, buckets)
@@ -107,8 +107,8 @@
             getPackageName = { if (it.key == APP_ID_1) HIDING_PACKAGE_NAME else null },
         )
         val buckets = listOf(
-            Bucket(uid = APP_ID_1, bytes = 1),
-            Bucket(uid = APP_ID_2, bytes = 2),
+            Bucket(uid = APP_ID_1, bytes = 1, startTimeStamp = 0, endTimeStamp = 0),
+            Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
         )
 
         val appPercentList = repository.getAppPercent(HIDING_CARRIER_ID, buckets)
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleBucketRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleBucketRepositoryTest.kt
new file mode 100644
index 0000000..f83b85f
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleBucketRepositoryTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.datausage.lib
+
+import android.content.Context
+import android.net.NetworkPolicy
+import android.net.NetworkTemplate
+import android.text.format.DateUtils
+import android.util.Range
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
+import com.google.common.truth.Truth.assertThat
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class NetworkCycleBucketRepositoryTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val template = mock<NetworkTemplate>()
+
+    private val mockNetworkCycleDataRepository = mock<NetworkCycleDataRepository>()
+
+    @Test
+    fun loadCycles_byPolicy() {
+        val policy = mock<NetworkPolicy> {
+            on { cycleIterator() } doReturn listOf(
+                Range(zonedDateTime(CYCLE1_START_TIME), zonedDateTime(CYCLE1_END_TIME)),
+            ).iterator()
+        }
+        mockNetworkCycleDataRepository.stub {
+            on { getPolicy() } doReturn policy
+        }
+        val repository = NetworkCycleBucketRepository(
+            context = context,
+            networkTemplate = template,
+            buckets = listOf(
+                Bucket(
+                    uid = 0,
+                    bytes = CYCLE1_BYTES,
+                    startTimeStamp = CYCLE1_START_TIME,
+                    endTimeStamp = CYCLE1_END_TIME,
+                )
+            ),
+            networkCycleDataRepository = mockNetworkCycleDataRepository,
+        )
+
+        val cycles = repository.loadCycles()
+
+        assertThat(cycles).containsExactly(
+            NetworkUsageData(
+                startTime = CYCLE1_START_TIME,
+                endTime = CYCLE1_END_TIME,
+                usage = CYCLE1_BYTES,
+            ),
+        )
+    }
+
+    @Test
+    fun loadCycles_asFourWeeks() {
+        mockNetworkCycleDataRepository.stub {
+            on { getPolicy() } doReturn null
+        }
+        val repository = NetworkCycleBucketRepository(
+            context = context,
+            networkTemplate = template,
+            buckets = listOf(
+                Bucket(
+                    uid = 0,
+                    bytes = CYCLE2_BYTES,
+                    startTimeStamp = CYCLE2_START_TIME,
+                    endTimeStamp = CYCLE2_END_TIME,
+                )
+            ),
+            networkCycleDataRepository = mockNetworkCycleDataRepository,
+        )
+
+        val cycles = repository.loadCycles()
+
+        assertThat(cycles).containsExactly(
+            NetworkUsageData(
+                startTime = CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4,
+                endTime = CYCLE2_END_TIME,
+                usage = CYCLE2_BYTES,
+            ),
+        )
+    }
+
+    @Test
+    fun queryChartData() {
+        val cycle = NetworkUsageData(
+            startTime = CYCLE3_START_TIME,
+            endTime = CYCLE4_END_TIME,
+            usage = CYCLE3_BYTES + CYCLE4_BYTES,
+        )
+        val repository = NetworkCycleBucketRepository(
+            context = context,
+            networkTemplate = template,
+            buckets = listOf(
+                Bucket(
+                    uid = 0,
+                    bytes = CYCLE3_BYTES,
+                    startTimeStamp = CYCLE3_START_TIME,
+                    endTimeStamp = CYCLE3_END_TIME,
+                ),
+                Bucket(
+                    uid = 0,
+                    bytes = CYCLE4_BYTES,
+                    startTimeStamp = CYCLE4_START_TIME,
+                    endTimeStamp = CYCLE4_END_TIME,
+                ),
+            ),
+            networkCycleDataRepository = mockNetworkCycleDataRepository,
+        )
+
+        val summary = repository.queryChartData(cycle)
+
+        assertThat(summary).isEqualTo(
+            NetworkCycleChartData(
+                total = cycle,
+                dailyUsage = listOf(
+                    NetworkUsageData(
+                        startTime = CYCLE3_START_TIME,
+                        endTime = CYCLE3_END_TIME,
+                        usage = CYCLE3_BYTES,
+                    ),
+                    NetworkUsageData(
+                        startTime = CYCLE4_START_TIME,
+                        endTime = CYCLE4_END_TIME,
+                        usage = CYCLE4_BYTES,
+                    ),
+                ),
+            )
+        )
+    }
+
+    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
+        const val CYCLE1_BYTES = 11L
+
+        const val CYCLE2_START_TIME = 1695555555000L
+        const val CYCLE2_END_TIME = 1695566666000L
+        const val CYCLE2_BYTES = 22L
+
+        const val CYCLE3_START_TIME = 1695555555000L
+        const val CYCLE3_END_TIME = CYCLE3_START_TIME + DateUtils.DAY_IN_MILLIS
+        const val CYCLE3_BYTES = 33L
+
+        const val CYCLE4_START_TIME = CYCLE3_END_TIME
+        const val CYCLE4_END_TIME = CYCLE4_START_TIME + DateUtils.DAY_IN_MILLIS
+        const val CYCLE4_BYTES = 44L
+    }
+}
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 5678503..f0a5309 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
@@ -63,7 +63,7 @@
         spy(NetworkCycleDataRepository(context, template, mockNetworkStatsRepository))
 
     @Test
-    fun loadCycles_byPolicy() = runTest {
+    fun loadFirstCycle_byPolicy() = runTest {
         val policy = mock<NetworkPolicy> {
             on { cycleIterator() } doReturn listOf(
                 Range(zonedDateTime(CYCLE1_START_TIME), zonedDateTime(CYCLE1_END_TIME))
@@ -71,23 +71,23 @@
         }
         doReturn(policy).whenever(repository).getPolicy()
 
-        val cycles = repository.loadCycles()
+        val firstCycle = repository.loadFirstCycle()
 
-        assertThat(cycles).containsExactly(
+        assertThat(firstCycle).isEqualTo(
             NetworkUsageData(startTime = 1, endTime = 2, usage = CYCLE1_BYTES),
         )
     }
 
     @Test
-    fun loadCycles_asFourWeeks() = runTest {
+    fun loadFirstCycle_asFourWeeks() = runTest {
         doReturn(null).whenever(repository).getPolicy()
         mockNetworkStatsRepository.stub {
             on { getTimeRange() } doReturn Range(CYCLE2_START_TIME, CYCLE2_END_TIME)
         }
 
-        val cycles = repository.loadCycles()
+        val firstCycle = repository.loadFirstCycle()
 
-        assertThat(cycles).containsExactly(
+        assertThat(firstCycle).isEqualTo(
             NetworkUsageData(
                 startTime = CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4,
                 endTime = CYCLE2_END_TIME,
@@ -96,33 +96,6 @@
         )
     }
 
-    @Test
-    fun querySummary() = runTest {
-        val summary = repository.queryChartData(CYCLE3_START_TIME, CYCLE4_END_TIME)
-
-        assertThat(summary).isEqualTo(
-            NetworkCycleChartData(
-                total = NetworkUsageData(
-                    startTime = CYCLE3_START_TIME,
-                    endTime = CYCLE4_END_TIME,
-                    usage = CYCLE3_BYTES + CYCLE4_BYTES,
-                ),
-                dailyUsage = listOf(
-                    NetworkUsageData(
-                        startTime = CYCLE3_START_TIME,
-                        endTime = CYCLE3_END_TIME,
-                        usage = CYCLE3_BYTES,
-                    ),
-                    NetworkUsageData(
-                        startTime = CYCLE4_START_TIME,
-                        endTime = CYCLE4_END_TIME,
-                        usage = CYCLE4_BYTES,
-                    ),
-                ),
-            )
-        )
-    }
-
     private fun zonedDateTime(epochMilli: Long): ZonedDateTime? =
         ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault())