Create AppDataUsageAppSettingsController
For better organization and testings.
Bug: 240931350
Test: manual - on AppDataUsage
Test: unit test
Change-Id: Ie3d35f5d112cf06cca585c9859624d705fbe2655
diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml
index d5d646c..7d4eaab 100644
--- a/res/xml/app_data_usage.xml
+++ b/res/xml/app_data_usage.xml
@@ -56,7 +56,8 @@
<Preference
android:key="app_settings"
- android:title="@string/data_usage_app_settings" />
+ android:title="@string/data_usage_app_settings"
+ settings:controller="com.android.settings.datausage.AppDataUsageAppSettingsController" />
<com.android.settingslib.RestrictedSwitchPreference
android:key="restrict_background"
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
index e0dbcbf..67d33a7 100644
--- a/src/com/android/settings/datausage/AppDataUsage.java
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -21,7 +21,6 @@
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -34,7 +33,6 @@
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.Range;
-import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.AdapterView;
@@ -78,7 +76,6 @@
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_APP_SETTINGS = "app_settings";
private static final String KEY_RESTRICT_BACKGROUND = "restrict_background";
private static final String KEY_CYCLE = "cycle";
private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver";
@@ -90,7 +87,6 @@
private Preference mTotalUsage;
private Preference mForegroundUsage;
private Preference mBackgroundUsage;
- private Preference mAppSettings;
private RestrictedSwitchPreference mRestrictBackground;
private Drawable mIcon;
@@ -105,7 +101,6 @@
@VisibleForTesting
NetworkTemplate mTemplate;
private AppItem mAppItem;
- private Intent mAppSettingsIntent;
private SpinnerPreference mCycle;
private RestrictedSwitchPreference mUnrestrictedData;
private DataSaverBackend mDataSaverBackend;
@@ -169,7 +164,6 @@
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);
@@ -179,14 +173,16 @@
removePreference(KEY_RESTRICT_BACKGROUND);
} else {
if (mPackages.size() != 0) {
+ int userId = UserHandle.getUserId(mAppItem.key);
try {
final ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(
- mPackages.valueAt(0), 0, UserHandle.getUserId(mAppItem.key));
+ mPackages.valueAt(0), 0, userId);
mIcon = IconDrawableFactory.newInstance(getActivity()).getBadgedIcon(info);
mLabel = info.loadLabel(mPackageManager);
mPackageName = info.packageName;
} catch (PackageManager.NameNotFoundException e) {
}
+ use(AppDataUsageAppSettingsController.class).init(mPackages, userId);
}
mRestrictBackground = findPreference(KEY_RESTRICT_BACKGROUND);
mRestrictBackground.setOnPreferenceChangeListener(this);
@@ -194,26 +190,8 @@
mUnrestrictedData.setOnPreferenceChangeListener(this);
}
mDataSaverBackend = new DataSaverBackend(mContext);
- mAppSettings = findPreference(KEY_APP_SETTINGS);
- mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
- mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
-
- final PackageManager pm = getPackageManager();
- boolean matchFound = false;
- for (String packageName : mPackages) {
- mAppSettingsIntent.setPackage(packageName);
- if (pm.resolveActivity(mAppSettingsIntent, 0) != null) {
- matchFound = true;
- break;
- }
- }
- if (!matchFound) {
- removePreference(KEY_APP_SETTINGS);
- mAppSettings = null;
- }
- appDataUsageListController.init(mAppItem.uids);
-
+ use(AppDataUsageListController.class).init(mAppItem.uids);
} else {
final Context context = getActivity();
final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true);
@@ -222,9 +200,7 @@
mPackageName = context.getPackageName();
removePreference(KEY_UNRESTRICTED_DATA);
- removePreference(KEY_APP_SETTINGS);
removePreference(KEY_RESTRICT_BACKGROUND);
- appDataUsageListController.init(new SparseBooleanArray());
}
addEntityHeader();
@@ -273,17 +249,6 @@
}
@Override
- public boolean onPreferenceTreeClick(Preference preference) {
- if (preference == mAppSettings) {
- // TODO: target towards entire UID instead of just first package
- getActivity().startActivityAsUser(mAppSettingsIntent, new UserHandle(
- UserHandle.getUserId(mAppItem.key)));
- return true;
- }
- return super.onPreferenceTreeClick(preference);
- }
-
- @Override
protected int getPreferenceScreenResId() {
return R.xml.app_data_usage;
}
diff --git a/src/com/android/settings/datausage/AppDataUsageAppSettingsController.kt b/src/com/android/settings/datausage/AppDataUsageAppSettingsController.kt
new file mode 100644
index 0000000..53a18c6
--- /dev/null
+++ b/src/com/android/settings/datausage/AppDataUsageAppSettingsController.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.content.Intent
+import android.os.UserHandle
+import androidx.annotation.OpenForTesting
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.core.BasePreferenceController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@OpenForTesting
+open class AppDataUsageAppSettingsController(context: Context, preferenceKey: String) :
+ BasePreferenceController(context, preferenceKey) {
+
+ private var packageNames: Iterable<String> = emptyList()
+ private var userId: Int = -1
+ private lateinit var preference: Preference
+ private var resolvedIntent: Intent? = null
+
+ private val packageManager = mContext.packageManager
+
+ override fun getAvailabilityStatus() = AVAILABLE
+
+ fun init(packageNames: Iterable<String>, userId: Int) {
+ this.packageNames = packageNames
+ this.userId = userId
+ }
+
+ 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) {
+ update()
+ }
+ }
+ }
+
+ private suspend fun update() {
+ resolvedIntent = withContext(Dispatchers.Default) {
+ packageNames.map { packageName ->
+ Intent(SettingsIntent).setPackage(packageName)
+ }.firstOrNull { intent ->
+ packageManager.resolveActivityAsUser(intent, 0, userId) != null
+ }
+ }
+ preference.isVisible = resolvedIntent != null
+ }
+
+ override fun handlePreferenceTreeClick(preference: Preference): Boolean {
+ if (preference.key == mPreferenceKey) {
+ resolvedIntent?.let { mContext.startActivityAsUser(it, UserHandle.of(userId)) }
+ return true
+ }
+ return false
+ }
+
+ private companion object {
+ val SettingsIntent = Intent(Intent.ACTION_MANAGE_NETWORK_USAGE).apply {
+ addCategory(Intent.CATEGORY_DEFAULT)
+ }
+ }
+}
diff --git a/src/com/android/settings/datausage/AppDataUsageListController.kt b/src/com/android/settings/datausage/AppDataUsageListController.kt
index ec944f4..e39ed7e 100644
--- a/src/com/android/settings/datausage/AppDataUsageListController.kt
+++ b/src/com/android/settings/datausage/AppDataUsageListController.kt
@@ -40,7 +40,7 @@
private val repository: AppPreferenceRepository = AppPreferenceRepository(context),
) : BasePreferenceController(context, preferenceKey) {
- private lateinit var uids: List<Int>
+ private var uids: List<Int> = emptyList()
private lateinit var preference: PreferenceGroup
fun init(uids: SparseBooleanArray) {
diff --git a/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageAppSettingsControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageAppSettingsControllerTest.kt
new file mode 100644
index 0000000..220c970
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageAppSettingsControllerTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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.argThat
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class AppDataUsageAppSettingsControllerTest {
+ private val packageManager = mock<PackageManager>()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { packageManager } doReturn packageManager
+ }
+
+ private val controller = AppDataUsageAppSettingsController(context, KEY)
+
+ private val preference = PreferenceCategory(context).apply { key = KEY }
+
+ private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
+
+ @Before
+ fun setUp() {
+ preferenceScreen.addPreference(preference)
+ }
+
+ @Test
+ fun onViewCreated_noSettingsActivity_hidePreference(): Unit = runBlocking {
+ controller.init(listOf(PACKAGE_NAME), USER_ID)
+ controller.displayPreference(preferenceScreen)
+
+ controller.onViewCreated(TestLifecycleOwner())
+ delay(100)
+
+ assertThat(preference.isVisible).isFalse()
+ }
+
+ @Test
+ fun onViewCreated_hasSettingsActivity_showPreference(): Unit = runBlocking {
+ packageManager.stub {
+ on {
+ resolveActivityAsUser(
+ argThat {
+ action == Intent.ACTION_MANAGE_NETWORK_USAGE && getPackage() == PACKAGE_NAME
+ },
+ eq(0),
+ eq(USER_ID),
+ )
+ } doReturn ResolveInfo()
+ }
+ controller.init(listOf(PACKAGE_NAME), USER_ID)
+ controller.displayPreference(preferenceScreen)
+
+ controller.onViewCreated(TestLifecycleOwner())
+ delay(100)
+
+ assertThat(preference.isVisible).isTrue()
+ }
+
+ private companion object {
+ const val KEY = "test_key"
+ const val PACKAGE_NAME = "package.name"
+ const val USER_ID = 0
+ }
+}