diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml index aacc1f6027c..d5d646c2026 100644 --- a/res/xml/app_data_usage.xml +++ b/res/xml/app_data_usage.xml @@ -76,6 +76,7 @@ + android:title="@string/data_usage_other_apps" + settings:controller="com.android.settings.datausage.AppDataUsageListController" /> diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java index 33307a38418..e0dbcbf3472 100644 --- a/src/com/android/settings/datausage/AppDataUsage.java +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -16,6 +16,8 @@ package com.android.settings.datausage; 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.ArraySet; 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.app.LoaderManager; 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 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC 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 mPackages = new ArraySet<>(); @@ -92,7 +92,6 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC private Preference mBackgroundUsage; private Preference mAppSettings; private RestrictedSwitchPreference mRestrictBackground; - private PreferenceCategory mAppList; private Drawable mIcon; @VisibleForTesting @@ -170,6 +169,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC 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 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC 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 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC 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 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC } 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 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC } }; - private final LoaderManager.LoaderCallbacks> mAppPrefCallbacks = - new LoaderManager.LoaderCallbacks<>() { - @Override - @NonNull - public Loader> onCreateLoader(int i, Bundle bundle) { - return new AppPrefLoader(getPrefContext(), mPackages, getPackageManager()); - } - - @Override - public void onLoadFinished(@NonNull Loader> loader, - ArraySet preferences) { - if (preferences != null && mAppList != null) { - for (Preference preference : preferences) { - mAppList.addPreference(preference); - } - } - } - - @Override - public void onLoaderReset(@NonNull Loader> 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 00000000000..ec944f42393 --- /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 + 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 1e0a55434f1..00000000000 --- 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> { - private ArraySet mPackages; - private PackageManager mPackageManager; - private Context mPrefContext; - - public AppPrefLoader(Context prefContext, ArraySet pkgs, PackageManager pm) { - super(prefContext); - mPackages = pkgs; - mPackageManager = pm; - mPrefContext = prefContext; - } - - @Override - public ArraySet loadInBackground() { - ArraySet 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 result) { - } -} diff --git a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt index 074a5559edd..ccd3e60e13f 100644 --- a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt +++ b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt @@ -126,12 +126,7 @@ class AppDataUsageRepository( 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 @@ class AppDataUsageRepository( 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 { val buckets = mutableListOf() 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 00000000000..a71bc8fcf20 --- /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): List = uids.flatMap { uid -> + val userId = UserHandle.getUserId(uid) + getPackagesForUid(uid).mapNotNull { packageName -> + getPreference(packageName, userId) + } + } + + private fun getPackagesForUid(uid: Int): Array = + 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 6a1c0a69de5..e4b91c6a795 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.settings.widget.EntityHeaderController; 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 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { 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 packages = new ArraySet<>(); packages.add("pkg"); final AppItem appItem = new AppItem(123456789); @@ -221,10 +210,7 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { @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 @@ public class AppDataUsageTest { 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 @@ public class AppDataUsageTest { 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 @@ public class AppDataUsageTest { 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 @@ public class AppDataUsageTest { assertTrue(mFragment.mTemplate.getSubscriberIds().isEmpty()); assertTrue(mFragment.mTemplate.getWifiNetworkKeys().isEmpty()); } + + private static class TestFragment extends AppDataUsage { + @Override + protected T use(Class 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 00000000000..67272329192 --- /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 { + 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 00000000000..c7371ee4e96 --- /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 { + 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 { + 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() + } +} 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 902906c93d7..00000000000 --- 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 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(); - } -}