Merge "Create AppDataUsageSummaryController" into main
diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml
index 034015c..88f60ef 100644
--- a/res/xml/app_data_usage.xml
+++ b/res/xml/app_data_usage.xml
@@ -24,31 +24,9 @@
         android:key="cycle"
         settings:controller="com.android.settings.datausage.AppDataUsageCycleController" />
 
-    <PreferenceCategory
-        android:key="app_data_usage_summary_category">
-
-        <Preference
-            android:key="total_usage"
-            android:title="@string/total_size_label"
-            android:selectable="false"
-            android:layout="@layout/horizontal_preference"
-            android:summary="@string/summary_placeholder" />
-
-        <Preference
-            android:key="foreground_usage"
-            android:title="@string/data_usage_label_foreground"
-            android:selectable="false"
-            android:layout="@layout/horizontal_preference"
-            android:summary="@string/summary_placeholder" />
-
-        <Preference
-            android:key="background_usage"
-            android:title="@string/data_usage_label_background"
-            android:selectable="false"
-            android:layout="@layout/horizontal_preference"
-            android:summary="@string/summary_placeholder" />
-
-    </PreferenceCategory>
+    <com.android.settings.spa.preference.ComposePreference
+        android:key="app_data_usage_summary"
+        settings:controller="com.android.settings.datausage.AppDataUsageSummaryController"/>
 
     <PreferenceCategory
         android:key="app_data_usage_settings_category"
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
index dbc0bc7..8480c5c 100644
--- a/src/com/android/settings/datausage/AppDataUsage.java
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -43,7 +43,6 @@
 import com.android.settings.applications.AppInfoBase;
 import com.android.settings.datausage.lib.AppDataUsageDetailsRepository;
 import com.android.settings.datausage.lib.NetworkTemplates;
-import com.android.settings.datausage.lib.NetworkUsageDetailsData;
 import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
 import com.android.settings.network.SubscriptionUtil;
 import com.android.settings.widget.EntityHeaderController;
@@ -70,17 +69,11 @@
     static final String ARG_NETWORK_CYCLES = "network_cycles";
     static final String ARG_SELECTED_CYCLE = "selected_cycle";
 
-    private static final String KEY_TOTAL_USAGE = "total_usage";
-    private static final String KEY_FOREGROUND_USAGE = "foreground_usage";
-    private static final String KEY_BACKGROUND_USAGE = "background_usage";
     private static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
     private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver";
 
     private PackageManager mPackageManager;
     private final ArraySet<String> mPackages = new ArraySet<>();
-    private Preference mTotalUsage;
-    private Preference mForegroundUsage;
-    private Preference mBackgroundUsage;
     private RestrictedSwitchPreference mRestrictBackground;
 
     private Drawable mIcon;
@@ -139,10 +132,6 @@
             }
         }
 
-        mTotalUsage = findPreference(KEY_TOTAL_USAGE);
-        mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE);
-        mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE);
-
         final List<Integer> uidList = getAppUidList(mAppItem.uids);
         initCycle(uidList);
 
@@ -255,15 +244,17 @@
 
     @VisibleForTesting
     void initCycle(List<Integer> uidList) {
-        var controller = use(AppDataUsageCycleController.class);
+        var cycleController = use(AppDataUsageCycleController.class);
+        var summaryController = use(AppDataUsageSummaryController.class);
         var repository = new AppDataUsageDetailsRepository(mContext, mTemplate, mCycles, uidList);
-        controller.init(repository, data -> {
-            bindData(data);
+        cycleController.init(repository, data -> {
+            mIsLoading = false;
+            summaryController.update(data);
             return Unit.INSTANCE;
         });
         if (mCycles != null) {
             Log.d(TAG, "setInitialCycles: " + mCycles + " " + mSelectedCycle);
-            controller.setInitialCycles(mCycles, mSelectedCycle);
+            cycleController.setInitialCycles(mCycles, mSelectedCycle);
         }
     }
 
@@ -314,16 +305,6 @@
         }
     }
 
-    @VisibleForTesting
-    void bindData(@NonNull NetworkUsageDetailsData data) {
-        mIsLoading = false;
-        mTotalUsage.setSummary(DataUsageUtils.formatDataUsage(mContext, data.getTotalUsage()));
-        mForegroundUsage.setSummary(
-                DataUsageUtils.formatDataUsage(mContext, data.getForegroundUsage()));
-        mBackgroundUsage.setSummary(
-                DataUsageUtils.formatDataUsage(mContext, data.getBackgroundUsage()));
-    }
-
     private boolean getAppRestrictBackground() {
         final int uid = mAppItem.key;
         final int uidPolicy = services.mPolicyManager.getUidPolicy(uid);
diff --git a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt
new file mode 100644
index 0000000..a764c1d
--- /dev/null
+++ b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt
@@ -0,0 +1,77 @@
+/*
+ * 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 androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settings.R
+import com.android.settings.datausage.lib.NetworkUsageDetailsData
+import com.android.settings.spa.preference.ComposePreferenceController
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.framework.compose.placeholder
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+class AppDataUsageSummaryController(context: Context, preferenceKey: String) :
+    ComposePreferenceController(context, preferenceKey) {
+
+    private val dataFlow = MutableStateFlow(NetworkUsageDetailsData.AllZero)
+
+    private val totalUsageFlow = dataFlow.map {
+        DataUsageUtils.formatDataUsage(mContext, it.totalUsage).toString()
+    }
+
+    private val foregroundUsageFlow = dataFlow.map {
+        DataUsageUtils.formatDataUsage(mContext, it.foregroundUsage).toString()
+    }
+
+    private val backgroundUsageFlow = dataFlow.map {
+        DataUsageUtils.formatDataUsage(mContext, it.backgroundUsage).toString()
+    }
+
+    override fun getAvailabilityStatus() = AVAILABLE
+
+    fun update(data: NetworkUsageDetailsData) {
+        dataFlow.value = data
+    }
+
+    @Composable
+    override fun Content() {
+        Column {
+            val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(placeholder())
+            val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(placeholder())
+            val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(placeholder())
+            Preference(object : PreferenceModel {
+                override val title = stringResource(R.string.total_size_label)
+                override val summary = { totalUsage }
+            })
+            Preference(object : PreferenceModel {
+                override val title = stringResource(R.string.data_usage_label_foreground)
+                override val summary = { foregroundUsage }
+            })
+            Preference(object : PreferenceModel {
+                override val title = stringResource(R.string.data_usage_label_background)
+                override val summary = { backgroundUsage }
+            })
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
index 1d841fa..4b8c9de 100644
--- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
@@ -42,16 +42,13 @@
 import android.os.Process;
 import android.telephony.SubscriptionManager;
 import android.util.ArraySet;
-import android.util.Range;
 
 import androidx.fragment.app.FragmentActivity;
-import androidx.preference.Preference;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.settings.applications.AppInfoBase;
-import com.android.settings.datausage.lib.NetworkUsageDetailsData;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.shadow.ShadowDataUsageUtils;
 import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
@@ -253,34 +250,6 @@
     }
 
     @Test
-    public void bindData_shouldUpdateUsageSummary() {
-        mFragment = spy(new TestFragment());
-        final Context context = RuntimeEnvironment.application;
-        ReflectionHelpers.setField(mFragment, "mContext", context);
-        final long backgroundBytes = 1234L;
-        final long foregroundBytes = 5678L;
-        final NetworkUsageDetailsData appUsage = new NetworkUsageDetailsData(
-                new Range<>(1L, 2L),
-                backgroundBytes + foregroundBytes,
-                foregroundBytes,
-                backgroundBytes
-        );
-        final Preference backgroundPref = mock(Preference.class);
-        ReflectionHelpers.setField(mFragment, "mBackgroundUsage", backgroundPref);
-        final Preference foregroundPref = mock(Preference.class);
-        ReflectionHelpers.setField(mFragment, "mForegroundUsage", foregroundPref);
-        final Preference totalPref = mock(Preference.class);
-        ReflectionHelpers.setField(mFragment, "mTotalUsage", totalPref);
-
-        mFragment.bindData(appUsage);
-
-        verify(totalPref).setSummary(
-                DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes));
-        verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes));
-        verify(foregroundPref).setSummary(DataUsageUtils.formatDataUsage(context, foregroundBytes));
-    }
-
-    @Test
     @Config(shadows = {ShadowDataUsageUtils.class, ShadowSubscriptionManager.class,
             ShadowFragment.class})
     public void onCreate_noNetworkTemplateAndInvalidDataSubscription_shouldUseWifiTemplate() {
diff --git a/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageSummaryControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageSummaryControllerTest.kt
new file mode 100644
index 0000000..5842956
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageSummaryControllerTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.util.Range
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.datausage.lib.NetworkUsageDetailsData
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppDataUsageSummaryControllerTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val controller = AppDataUsageSummaryController(context, TEST_KEY)
+
+    @Test
+    fun summary() {
+        val appUsage = NetworkUsageDetailsData(
+            range = Range(1L, 2L),
+            totalUsage = BACKGROUND_BYTES + FOREGROUND_BYTES,
+            foregroundUsage = FOREGROUND_BYTES,
+            backgroundUsage = BACKGROUND_BYTES,
+        )
+
+        controller.update(appUsage)
+        composeTestRule.setContent {
+            controller.Content()
+        }
+
+        composeTestRule.onNodeWithText("6.75 kB").assertIsDisplayed()
+        composeTestRule.onNodeWithText("5.54 kB").assertIsDisplayed()
+        composeTestRule.onNodeWithText("1.21 kB").assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TEST_KEY = "test_key"
+        const val BACKGROUND_BYTES = 1234L
+        const val FOREGROUND_BYTES = 5678L
+    }
+}