Merge "Telephony.Carriers.BEARER is deprecated." into main
diff --git a/Android.bp b/Android.bp
index 8c50ed5..20f8634 100644
--- a/Android.bp
+++ b/Android.bp
@@ -103,7 +103,6 @@
         "securebox",
         "settings-logtags",
         "statslog-settings",
-        "zxing-core-1.7",
         "android.hardware.dumpstate-V1.0-java",
         "android.hardware.dumpstate-V1.1-java",
         "android.hardware.dumpstate-V1-java",
diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml
index aacc1f6..d5d646c 100644
--- a/res/xml/app_data_usage.xml
+++ b/res/xml/app_data_usage.xml
@@ -76,6 +76,7 @@
 
     <PreferenceCategory
         android:key="app_list"
-        android:title="@string/data_usage_other_apps" />
+        android:title="@string/data_usage_other_apps"
+        settings:controller="com.android.settings.datausage.AppDataUsageListController" />
 
 </PreferenceScreen>
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
index 33307a3..e0dbcbf 100644
--- a/src/com/android/settings/datausage/AppDataUsage.java
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -16,6 +16,8 @@
 
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 
+import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUid;
+
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
@@ -32,6 +34,7 @@
 import android.util.IconDrawableFactory;
 import android.util.Log;
 import android.util.Range;
+import android.util.SparseBooleanArray;
 import android.view.View;
 import android.widget.AdapterView;
 
@@ -42,7 +45,6 @@
 import androidx.loader.content.Loader;
 import androidx.preference.Preference;
 import androidx.preference.Preference.OnPreferenceChangeListener;
-import androidx.preference.PreferenceCategory;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -78,12 +80,10 @@
     private static final String KEY_BACKGROUND_USAGE = "background_usage";
     private static final String KEY_APP_SETTINGS = "app_settings";
     private static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
-    private static final String KEY_APP_LIST = "app_list";
     private static final String KEY_CYCLE = "cycle";
     private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver";
 
     private static final int LOADER_APP_USAGE_DATA = 2;
-    private static final int LOADER_APP_PREF = 3;
 
     private PackageManager mPackageManager;
     private final ArraySet<String> mPackages = new ArraySet<>();
@@ -92,7 +92,6 @@
     private Preference mBackgroundUsage;
     private Preference mAppSettings;
     private RestrictedSwitchPreference mRestrictBackground;
-    private PreferenceCategory mAppList;
 
     private Drawable mIcon;
     @VisibleForTesting
@@ -170,6 +169,7 @@
 
         final UidDetailProvider uidDetailProvider = getUidDetailProvider();
 
+        final var appDataUsageListController = use(AppDataUsageListController.class);
         if (mAppItem.key > 0) {
             if ((!isSimHardwareVisible(mContext)) || !UserHandle.isApp(mAppItem.key)) {
                 final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true);
@@ -212,14 +212,8 @@
                 removePreference(KEY_APP_SETTINGS);
                 mAppSettings = null;
             }
+            appDataUsageListController.init(mAppItem.uids);
 
-            if (mPackages.size() > 1) {
-                mAppList = findPreference(KEY_APP_LIST);
-                LoaderManager.getInstance(this).restartLoader(LOADER_APP_PREF, Bundle.EMPTY,
-                        mAppPrefCallbacks);
-            } else {
-                removePreference(KEY_APP_LIST);
-            }
         } else {
             final Context context = getActivity();
             final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true);
@@ -230,7 +224,7 @@
             removePreference(KEY_UNRESTRICTED_DATA);
             removePreference(KEY_APP_SETTINGS);
             removePreference(KEY_RESTRICT_BACKGROUND);
-            removePreference(KEY_APP_LIST);
+            appDataUsageListController.init(new SparseBooleanArray());
         }
 
         addEntityHeader();
@@ -360,11 +354,7 @@
     }
 
     private void addUid(int uid) {
-        if (Process.isSdkSandboxUid(uid)) {
-            // For a sandbox process, get the associated app UID
-            uid = Process.getAppUidForSdkSandboxUid(uid);
-        }
-        String[] packages = mPackageManager.getPackagesForUid(uid);
+        String[] packages = mPackageManager.getPackagesForUid(getAppUid(uid));
         if (packages != null) {
             Collections.addAll(mPackages, packages);
         }
@@ -501,29 +491,6 @@
                 }
             };
 
-    private final LoaderManager.LoaderCallbacks<ArraySet<Preference>> mAppPrefCallbacks =
-            new LoaderManager.LoaderCallbacks<>() {
-                @Override
-                @NonNull
-                public Loader<ArraySet<Preference>> onCreateLoader(int i, Bundle bundle) {
-                    return new AppPrefLoader(getPrefContext(), mPackages, getPackageManager());
-                }
-
-                @Override
-                public void onLoadFinished(@NonNull Loader<ArraySet<Preference>> loader,
-                        ArraySet<Preference> preferences) {
-                    if (preferences != null && mAppList != null) {
-                        for (Preference preference : preferences) {
-                            mAppList.addPreference(preference);
-                        }
-                    }
-                }
-
-                @Override
-                public void onLoaderReset(@NonNull Loader<ArraySet<Preference>> loader) {
-                }
-            };
-
     @Override
     public void onDataSaverChanged(boolean isDataSaving) {
 
diff --git a/src/com/android/settings/datausage/AppDataUsageListController.kt b/src/com/android/settings/datausage/AppDataUsageListController.kt
new file mode 100644
index 0000000..ec944f4
--- /dev/null
+++ b/src/com/android/settings/datausage/AppDataUsageListController.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.content.Context
+import android.util.SparseBooleanArray
+import androidx.annotation.OpenForTesting
+import androidx.core.util.keyIterator
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceScreen
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.getAppUid
+import com.android.settings.datausage.lib.AppPreferenceRepository
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@OpenForTesting
+open class AppDataUsageListController @JvmOverloads constructor(
+    context: Context,
+    preferenceKey: String,
+    private val repository: AppPreferenceRepository = AppPreferenceRepository(context),
+) : BasePreferenceController(context, preferenceKey) {
+
+    private lateinit var uids: List<Int>
+    private lateinit var preference: PreferenceGroup
+
+    fun init(uids: SparseBooleanArray) {
+        this.uids = uids.keyIterator().asSequence().map { getAppUid(it) }.distinct().toList()
+    }
+
+    override fun getAvailabilityStatus() = AVAILABLE
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        super.displayPreference(screen)
+        preference = screen.findPreference(preferenceKey)!!
+    }
+
+    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                updateList()
+            }
+        }
+    }
+
+    private suspend fun updateList() {
+        if (uids.size <= 1) {
+            preference.isVisible = false
+            return
+        }
+        preference.isVisible = true
+        val appPreferences = withContext(Dispatchers.Default) {
+            repository.loadAppPreferences(uids)
+        }
+        preference.removeAll()
+        for (appPreference in appPreferences) {
+            preference.addPreference(appPreference)
+        }
+    }
+}
diff --git a/src/com/android/settings/datausage/AppPrefLoader.java b/src/com/android/settings/datausage/AppPrefLoader.java
deleted file mode 100644
index 1e0a554..0000000
--- a/src/com/android/settings/datausage/AppPrefLoader.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.datausage;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.util.ArraySet;
-
-import androidx.preference.Preference;
-
-import com.android.settingslib.utils.AsyncLoaderCompat;
-
-public class AppPrefLoader extends AsyncLoaderCompat<ArraySet<Preference>> {
-    private ArraySet<String> mPackages;
-    private PackageManager mPackageManager;
-    private Context mPrefContext;
-
-    public AppPrefLoader(Context prefContext, ArraySet<String> pkgs, PackageManager pm) {
-        super(prefContext);
-        mPackages = pkgs;
-        mPackageManager = pm;
-        mPrefContext = prefContext;
-    }
-
-    @Override
-    public ArraySet<Preference> loadInBackground() {
-        ArraySet<Preference> results = new ArraySet<>();
-        for (int i = 1, size = mPackages.size(); i < size; i++) {
-            try {
-                ApplicationInfo info = mPackageManager.getApplicationInfo(mPackages.valueAt(i), 0);
-                Preference preference = new Preference(mPrefContext);
-                preference.setIcon(info.loadIcon(mPackageManager));
-                preference.setTitle(info.loadLabel(mPackageManager));
-                preference.setSelectable(false);
-                results.add(preference);
-            } catch (PackageManager.NameNotFoundException e) {
-            }
-        }
-        return results;
-    }
-
-    @Override
-    protected void onDiscardResult(ArraySet<Preference> result) {
-    }
-}
diff --git a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
index 074a555..ccd3e60 100644
--- a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
+++ b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
@@ -126,12 +126,7 @@
                             items = items,
                         )
                     }
-                    // Map SDK sandbox back to its corresponding app
-                    collapseKey = if (Process.isSdkSandboxUid(uid)) {
-                        Process.getAppUidForSdkSandboxUid(uid)
-                    } else {
-                        uid
-                    }
+                    collapseKey = getAppUid(uid)
                     category = AppItem.CATEGORY_APP
                 } else {
                     // If it is a removed user add it to the removed users' key
@@ -200,6 +195,15 @@
             val bytes: Long,
         )
 
+        @JvmStatic
+        fun getAppUid(uid: Int): Int {
+            if (Process.isSdkSandboxUid(uid)) {
+                // For a sandbox process, get the associated app UID
+                return Process.getAppUidForSdkSandboxUid(uid)
+            }
+            return uid
+        }
+
         private fun convertToBuckets(stats: NetworkStats): List<Bucket> {
             val buckets = mutableListOf<Bucket>()
             stats.use {
diff --git a/src/com/android/settings/datausage/lib/AppPreferenceRepository.kt b/src/com/android/settings/datausage/lib/AppPreferenceRepository.kt
new file mode 100644
index 0000000..a71bc8f
--- /dev/null
+++ b/src/com/android/settings/datausage/lib/AppPreferenceRepository.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.datausage.lib
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import androidx.preference.Preference
+import com.android.settingslib.Utils
+
+class AppPreferenceRepository(private val context: Context) {
+    private val packageManager = context.packageManager
+
+    fun loadAppPreferences(uids: List<Int>): List<Preference> = uids.flatMap { uid ->
+        val userId = UserHandle.getUserId(uid)
+        getPackagesForUid(uid).mapNotNull { packageName ->
+            getPreference(packageName, userId)
+        }
+    }
+
+    private fun getPackagesForUid(uid: Int): Array<String> =
+        packageManager.getPackagesForUid(uid) ?: emptyArray()
+
+    private fun getPreference(packageName: String, userId: Int): Preference? = try {
+        val app = packageManager.getApplicationInfoAsUser(packageName, 0, userId)
+        Preference(context).apply {
+            icon = Utils.getBadgedIcon(context, app)
+            title = app.loadLabel(packageManager)
+            isSelectable = false
+        }
+    } catch (e: PackageManager.NameNotFoundException) {
+        null
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
index 6a1c0a6..e4b91c6 100644
--- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
@@ -60,6 +60,7 @@
 import com.android.settingslib.AppItem;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.net.NetworkCycleDataForUid;
 import com.android.settingslib.net.NetworkCycleDataForUidLoader;
 import com.android.settingslib.net.UidDetail;
@@ -109,10 +110,7 @@
     @Test
     @Config(shadows = ShadowFragment.class)
     public void onCreate_appUid_shouldGetAppLabelFromAppInfo() throws NameNotFoundException {
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
         final FragmentActivity activity = spy(Robolectric.setupActivity(FragmentActivity.class));
         doReturn(mPackageManager).when(activity).getPackageManager();
         doReturn(activity).when(mFragment).getActivity();
@@ -142,10 +140,7 @@
     @Test
     @Config(shadows = ShadowFragment.class)
     public void onCreate_notAppUid_shouldGetAppLabelFromUidDetailProvider() {
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
         ReflectionHelpers.setField(mFragment, "mDashboardFeatureProvider",
                 FakeFeatureFactory.setupForTest().dashboardFeatureProvider);
         doReturn(Robolectric.setupActivity(FragmentActivity.class)).when(mFragment).getActivity();
@@ -172,10 +167,7 @@
 
     @Test
     public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() {
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
 
         when(mFragment.getPreferenceManager())
                 .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS));
@@ -192,10 +184,7 @@
             throws PackageManager.NameNotFoundException {
         final int fakeUserId = 100;
 
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
         final ArraySet<String> packages = new ArraySet<>();
         packages.add("pkg");
         final AppItem appItem = new AppItem(123456789);
@@ -221,10 +210,7 @@
 
     @Test
     public void changePreference_backgroundData_shouldUpdateUI() {
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
         final AppItem appItem = new AppItem(123456789);
         final RestrictedSwitchPreference pref = mock(RestrictedSwitchPreference.class);
         final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class);
@@ -241,10 +227,7 @@
 
     @Test
     public void updatePrefs_restrictedByAdmin_shouldDisablePreference() {
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
         final int testUid = 123123;
         final AppItem appItem = new AppItem(testUid);
         final RestrictedSwitchPreference restrictBackgroundPref
@@ -272,10 +255,7 @@
 
     @Test
     public void bindData_noAppUsageData_shouldHideCycleSpinner() {
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
         final SpinnerPreference cycle = mock(SpinnerPreference.class);
         ReflectionHelpers.setField(mFragment, "mCycle", cycle);
         final Preference preference = mock(Preference.class);
@@ -291,10 +271,7 @@
 
     @Test
     public void bindData_hasAppUsageData_shouldShowCycleSpinnerAndUpdateUsageSummary() {
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
         final Context context = RuntimeEnvironment.application;
         ReflectionHelpers.setField(mFragment, "mContext", context);
         final long backgroundBytes = 1234L;
@@ -323,10 +300,7 @@
 
     @Test
     public void onCreateLoader_categoryApp_shouldQueryDataUsageUsingAppKey() {
-        mFragment = new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        };
+        mFragment = new TestFragment();
         final Context context = RuntimeEnvironment.application;
         final int testUid = 123123;
         final AppItem appItem = new AppItem(testUid);
@@ -349,10 +323,7 @@
 
     @Test
     public void onCreateLoader_categoryUser_shouldQueryDataUsageUsingAssociatedUids() {
-        mFragment = new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        };
+        mFragment = new TestFragment();
         final Context context = RuntimeEnvironment.application;
         final int testUserId = 11;
         final AppItem appItem = new AppItem(testUserId);
@@ -389,10 +360,7 @@
         appItem.category = AppItem.CATEGORY_APP;
         appItem.addUid(uid);
 
-        mFragment = new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        };
+        mFragment = new TestFragment();
         ReflectionHelpers.setField(mFragment, "mContext", RuntimeEnvironment.application);
         ReflectionHelpers.setField(mFragment, "mCycles", testCycles);
         ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
@@ -425,10 +393,7 @@
         builder.setStartTime(tenDaysAgo).setEndTime(now).setTotalUsage(1234L);
         data.add(builder.build());
 
-        mFragment = new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        };
+        mFragment = new TestFragment();
         ReflectionHelpers.setField(mFragment, "mContext", RuntimeEnvironment.application);
         ReflectionHelpers.setField(mFragment, "mCycleAdapter", mock(CycleAdapter.class));
         ReflectionHelpers.setField(mFragment, "mSelectedCycle", tenDaysAgo);
@@ -455,10 +420,7 @@
         ShadowDataUsageUtils.HAS_SIM = false;
         ShadowSubscriptionManager.setDefaultDataSubscriptionId(
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        mFragment = spy(new AppDataUsage() {
-            @Override
-            public boolean isSimHardwareVisible(Context context) { return true; }
-        });
+        mFragment = spy(new TestFragment());
         doReturn(Robolectric.setupActivity(FragmentActivity.class)).when(mFragment).getActivity();
         doReturn(RuntimeEnvironment.application).when(mFragment).getContext();
         final UidDetailProvider uidDetailProvider = mock(UidDetailProvider.class);
@@ -478,4 +440,16 @@
         assertTrue(mFragment.mTemplate.getSubscriberIds().isEmpty());
         assertTrue(mFragment.mTemplate.getWifiNetworkKeys().isEmpty());
     }
+
+    private static class TestFragment extends AppDataUsage {
+        @Override
+        protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {
+            return mock(clazz);
+        }
+
+        @Override
+        public boolean isSimHardwareVisible(Context context) {
+            return true;
+        }
+    }
 }
diff --git a/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageListControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageListControllerTest.kt
new file mode 100644
index 0000000..6727232
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageListControllerTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.content.Context
+import android.util.SparseBooleanArray
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.datausage.lib.AppPreferenceRepository
+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.doAnswer
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+class AppDataUsageListControllerTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val repository = mock<AppPreferenceRepository> {
+        on { loadAppPreferences(any()) } doAnswer {
+            val uids = it.arguments[0] as List<*>
+            uids.map { Preference(context) }
+        }
+    }
+
+    private val controller = AppDataUsageListController(
+        context = context,
+        preferenceKey = KEY,
+        repository = repository,
+    )
+
+    private val preference = PreferenceCategory(context).apply { key = KEY }
+
+    private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
+
+    @Before
+    fun setUp() {
+        preferenceScreen.addPreference(preference)
+    }
+
+    @Test
+    fun onViewCreated_singleUid_hidePreference(): Unit = runBlocking {
+        controller.init(SparseBooleanArray().apply {
+            put(UID_0, true)
+        })
+        controller.displayPreference(preferenceScreen)
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.isVisible).isFalse()
+    }
+
+    @Test
+    fun onViewCreated_twoUid_showPreference(): Unit = runBlocking {
+        controller.init(SparseBooleanArray().apply {
+            put(UID_0, true)
+            put(UID_1, true)
+        })
+        controller.displayPreference(preferenceScreen)
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.isVisible).isTrue()
+        assertThat(preference.preferenceCount).isEqualTo(2)
+    }
+
+    private companion object {
+        const val KEY = "test_key"
+        const val UID_0 = 10000
+        const val UID_1 = 10001
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/AppPreferenceRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/AppPreferenceRepositoryTest.kt
new file mode 100644
index 0000000..c7371ee
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/AppPreferenceRepositoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class AppPreferenceRepositoryTest {
+    private val packageManager = mock<PackageManager> {
+        on { getPackagesForUid(UID) } doReturn arrayOf(PACKAGE_NAME)
+    }
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { packageManager } doReturn packageManager
+    }
+
+    private val repository = AppPreferenceRepository(context)
+
+    @Test
+    fun loadAppPreferences_packageNotFound_returnEmpty() {
+        packageManager.stub {
+            on {
+                getApplicationInfoAsUser(PACKAGE_NAME, 0, UserHandle.getUserId(UID))
+            } doThrow PackageManager.NameNotFoundException()
+        }
+
+        val preferences = repository.loadAppPreferences(listOf(UID))
+
+        assertThat(preferences).isEmpty()
+    }
+
+    @Test
+    fun loadAppPreferences_packageFound_returnPreference() {
+        val app = mock<ApplicationInfo> {
+            on { loadUnbadgedIcon(any()) } doReturn UNBADGED_ICON
+            on { loadLabel(any()) } doReturn LABEL
+        }
+        packageManager.stub {
+            on {
+                getApplicationInfoAsUser(PACKAGE_NAME, 0, UserHandle.getUserId(UID))
+            } doReturn app
+        }
+
+        val preferences = repository.loadAppPreferences(listOf(UID))
+
+        assertThat(preferences).hasSize(1)
+        preferences[0].apply {
+            assertThat(title).isEqualTo(LABEL)
+            assertThat(icon).isNotNull()
+            assertThat(isSelectable).isFalse()
+        }
+    }
+
+    private companion object {
+        const val UID = 10000
+        const val PACKAGE_NAME = "package.name"
+        const val LABEL = "Label"
+        val UNBADGED_ICON = mock<Drawable>()
+    }
+}
diff --git a/tests/unit/src/com/android/settings/datausage/AppPrefLoaderTest.java b/tests/unit/src/com/android/settings/datausage/AppPrefLoaderTest.java
deleted file mode 100644
index 902906c..0000000
--- a/tests/unit/src/com/android/settings/datausage/AppPrefLoaderTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2020 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.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.drawable.Drawable;
-import android.util.ArraySet;
-
-import androidx.preference.Preference;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-public class AppPrefLoaderTest {
-
-    @Mock
-    private PackageManager mPackageManager;
-
-    private AppPrefLoader mLoader;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        final ArraySet<String> pkgs = new ArraySet<>(2);
-        pkgs.add("pkg0");
-        pkgs.add("pkg1");
-        mLoader = new AppPrefLoader(
-                ApplicationProvider.getApplicationContext(), pkgs, mPackageManager);
-    }
-
-    @Test
-    public void loadInBackground_packageNotFound_shouldReturnEmptySet()
-            throws NameNotFoundException {
-        when(mPackageManager.getApplicationInfo(anyString(), anyInt()))
-            .thenThrow(new NameNotFoundException());
-
-        assertThat(mLoader.loadInBackground()).isEmpty();
-    }
-
-    @Test
-    public void loadInBackground_shouldReturnPreference() throws NameNotFoundException {
-        ApplicationInfo info = mock(ApplicationInfo.class);
-        when(mPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(info);
-        final Drawable drawable = mock(Drawable.class);
-        final String label = "Label1";
-        when(info.loadIcon(mPackageManager)).thenReturn(drawable);
-        when(info.loadLabel(mPackageManager)).thenReturn(label);
-
-        Preference preference = mLoader.loadInBackground().valueAt(0);
-        assertThat(preference.getTitle()).isEqualTo(label);
-        assertThat(preference.getIcon()).isEqualTo(drawable);
-        assertThat(preference.isSelectable()).isFalse();
-    }
-}