From 08e24272e4fbf6b2aff46ca8eadfe54cde587e79 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Sun, 8 Oct 2023 16:58:32 +0800 Subject: [PATCH] Create AppDataUsageAppSettingsController For better organization and testings. Bug: 240931350 Test: manual - on AppDataUsage Test: unit test Change-Id: Ie3d35f5d112cf06cca585c9859624d705fbe2655 --- res/xml/app_data_usage.xml | 3 +- .../settings/datausage/AppDataUsage.java | 43 +------- .../AppDataUsageAppSettingsController.kt | 90 +++++++++++++++++ .../datausage/AppDataUsageListController.kt | 2 +- .../AppDataUsageAppSettingsControllerTest.kt | 98 +++++++++++++++++++ 5 files changed, 195 insertions(+), 41 deletions(-) create mode 100644 src/com/android/settings/datausage/AppDataUsageAppSettingsController.kt create mode 100644 tests/spa_unit/src/com/android/settings/datausage/AppDataUsageAppSettingsControllerTest.kt diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml index d5d646c2026..7d4eaab369d 100644 --- a/res/xml/app_data_usage.xml +++ b/res/xml/app_data_usage.xml @@ -56,7 +56,8 @@ + android:title="@string/data_usage_app_settings" + settings:controller="com.android.settings.datausage.AppDataUsageAppSettingsController" /> 0) { if ((!isSimHardwareVisible(mContext)) || !UserHandle.isApp(mAppItem.key)) { final UidDetail uidDetail = uidDetailProvider.getUidDetail(mAppItem.key, true); @@ -179,14 +173,16 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC 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 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC 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 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC mPackageName = context.getPackageName(); removePreference(KEY_UNRESTRICTED_DATA); - removePreference(KEY_APP_SETTINGS); removePreference(KEY_RESTRICT_BACKGROUND); - appDataUsageListController.init(new SparseBooleanArray()); } addEntityHeader(); @@ -272,17 +248,6 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return false; } - @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 00000000000..53a18c66997 --- /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 = 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, 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 ec944f42393..e39ed7e9166 100644 --- a/src/com/android/settings/datausage/AppDataUsageListController.kt +++ b/src/com/android/settings/datausage/AppDataUsageListController.kt @@ -40,7 +40,7 @@ open class AppDataUsageListController @JvmOverloads constructor( private val repository: AppPreferenceRepository = AppPreferenceRepository(context), ) : BasePreferenceController(context, preferenceKey) { - private lateinit var uids: List + private var uids: List = 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 00000000000..220c9702fb7 --- /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() + + 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 + } +}