Unify the default template and title

On AppDataUsage page, using single source of truth to calculate the
default template, and set title base on the current template.

Fix: 213266028
Fix: 234104784
Test: visual - on AppDataUsage
Test: unit test
Change-Id: I80facca0b000964e901905af51a344a4bc9f498b
This commit is contained in:
Chaohui Wang
2023-10-09 17:43:32 +08:00
parent b7f8c15ff0
commit 6395cf8d30
8 changed files with 119 additions and 94 deletions

View File

@@ -17,7 +17,6 @@
package com.android.settings.applications.appinfo; package com.android.settings.applications.appinfo;
import android.content.Context; import android.content.Context;
import android.net.NetworkStats;
import android.net.NetworkTemplate; import android.net.NetworkTemplate;
import android.os.Bundle; import android.os.Bundle;
import android.os.Process; import android.os.Process;
@@ -34,8 +33,8 @@ import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment; import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.datausage.AppDataUsage; import com.android.settings.datausage.AppDataUsage;
import com.android.settings.datausage.DataUsageUtils; import com.android.settings.datausage.lib.NetworkTemplates;
import com.android.settings.network.SubscriptionUtil; import com.android.settings.spa.app.appinfo.AppDataUsagePreferenceKt;
import com.android.settingslib.AppItem; import com.android.settingslib.AppItem;
import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -46,6 +45,10 @@ import com.android.settingslib.net.NetworkCycleDataForUidLoader;
import java.util.List; import java.util.List;
/**
* @deprecated Will be removed, use {@link AppDataUsagePreferenceKt} instead.
*/
@Deprecated(forRemoval = true)
public class AppDataUsagePreferenceController extends AppInfoPreferenceControllerBase public class AppDataUsagePreferenceController extends AppInfoPreferenceControllerBase
implements LoaderManager.LoaderCallbacks<List<NetworkCycleDataForUid>>, LifecycleObserver, implements LoaderManager.LoaderCallbacks<List<NetworkCycleDataForUid>>, LifecycleObserver,
OnResume, OnPause { OnResume, OnPause {
@@ -92,7 +95,7 @@ public class AppDataUsagePreferenceController extends AppInfoPreferenceControlle
@Override @Override
public Loader<List<NetworkCycleDataForUid>> onCreateLoader(int id, Bundle args) { public Loader<List<NetworkCycleDataForUid>> onCreateLoader(int id, Bundle args) {
final NetworkTemplate template = getTemplate(mContext); final NetworkTemplate template = NetworkTemplates.INSTANCE.getDefaultTemplate(mContext);
final int uid = mParent.getAppEntry().info.uid; final int uid = mParent.getAppEntry().info.uid;
final NetworkCycleDataForUidLoader.Builder builder = final NetworkCycleDataForUidLoader.Builder builder =
@@ -147,18 +150,6 @@ public class AppDataUsagePreferenceController extends AppInfoPreferenceControlle
return mContext.getString(R.string.computing_size); return mContext.getString(R.string.computing_size);
} }
private static NetworkTemplate getTemplate(Context context) {
if (SubscriptionUtil.isSimHardwareVisible(context)
&& DataUsageUtils.hasReadyMobileRadio(context)) {
return new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE).setMeteredness(
NetworkStats.METERED_YES).build();
}
if (DataUsageUtils.hasWifiRadio(context)) {
return new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build();
}
return new NetworkTemplate.Builder(NetworkTemplate.MATCH_ETHERNET).build();
}
@VisibleForTesting @VisibleForTesting
boolean isBandwidthControlEnabled() { boolean isBandwidthControlEnabled() {
return Utils.isBandwidthControlEnabled(); return Utils.isBandwidthControlEnabled();

View File

@@ -29,7 +29,6 @@ import android.net.NetworkTemplate;
import android.os.Bundle; import android.os.Bundle;
import android.os.Process; import android.os.Process;
import android.os.UserHandle; import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.IconDrawableFactory; import android.util.IconDrawableFactory;
import android.util.Log; import android.util.Log;
@@ -44,6 +43,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
import com.android.settings.datausage.lib.AppDataUsageDetailsRepository; 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.datausage.lib.NetworkUsageDetailsData;
import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.SubscriptionUtil;
import com.android.settings.widget.EntityHeaderController; import com.android.settings.widget.EntityHeaderController;
@@ -118,15 +118,16 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
mSelectedCycle = (args != null) ? args.getLong(ARG_SELECTED_CYCLE) : 0L; mSelectedCycle = (args != null) ? args.getLong(ARG_SELECTED_CYCLE) : 0L;
if (mTemplate == null) { if (mTemplate == null) {
mTemplate = DataUsageUtils.getDefaultTemplate(mContext, mTemplate = NetworkTemplates.INSTANCE.getDefaultTemplate(mContext);
SubscriptionManager.getDefaultDataSubscriptionId());
} }
final Activity activity = requireActivity();
activity.setTitle(NetworkTemplates.getTitleResId(mTemplate));
if (mAppItem == null) { if (mAppItem == null) {
int uid = (args != null) ? args.getInt(AppInfoBase.ARG_PACKAGE_UID, -1) int uid = (args != null) ? args.getInt(AppInfoBase.ARG_PACKAGE_UID, -1)
: getActivity().getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); : getActivity().getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1);
if (uid == -1) { if (uid == -1) {
// TODO: Log error. // TODO: Log error.
getActivity().finish(); activity.finish();
} else { } else {
addUid(uid); addUid(uid);
mAppItem = new AppItem(uid); mAppItem = new AppItem(uid);

View File

@@ -17,9 +17,9 @@ package com.android.settings.datausage;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings;
import android.util.Log; import android.util.Log;
import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settingslib.AppItem; import com.android.settingslib.AppItem;
@@ -61,14 +61,12 @@ public class AppDataUsageActivity extends SettingsActivity {
args.putParcelable(AppDataUsage.ARG_APP_ITEM, appItem); args.putParcelable(AppDataUsage.ARG_APP_ITEM, appItem);
intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
intent.putExtra(EXTRA_SHOW_FRAGMENT, AppDataUsage.class.getName()); intent.putExtra(EXTRA_SHOW_FRAGMENT, AppDataUsage.class.getName());
intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.data_usage_app_summary_title);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@Override @Override
protected boolean isValidFragment(String fragmentName) { protected boolean isValidFragment(String fragmentName) {
return super.isValidFragment(fragmentName) return AppDataUsage.class.getName().equals(fragmentName);
|| AppDataUsage.class.getName().equals(fragmentName);
} }
} }

View File

@@ -44,6 +44,7 @@ import androidx.preference.SwitchPreference;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.datausage.lib.NetworkTemplates;
import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settings.network.telephony.MobileNetworkUtils;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
@@ -128,8 +129,7 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements
} }
if (mNetworkTemplate == null) { if (mNetworkTemplate == null) {
mNetworkTemplate = DataUsageUtils.getDefaultTemplate(context, mNetworkTemplate = NetworkTemplates.INSTANCE.getDefaultTemplate(context);
DataUsageUtils.getDefaultSubscriptionId(context));
} }
mBillingCycle = findPreference(KEY_BILLING_CYCLE); mBillingCycle = findPreference(KEY_BILLING_CYCLE);

View File

@@ -17,7 +17,6 @@ package com.android.settings.datausage;
import static android.content.pm.PackageManager.FEATURE_ETHERNET; import static android.content.pm.PackageManager.FEATURE_ETHERNET;
import static android.content.pm.PackageManager.FEATURE_USB_HOST; import static android.content.pm.PackageManager.FEATURE_USB_HOST;
import static android.content.pm.PackageManager.FEATURE_WIFI; import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.telephony.TelephonyManager.SIM_STATE_READY;
import android.app.usage.NetworkStats.Bucket; import android.app.usage.NetworkStats.Bucket;
import android.app.usage.NetworkStatsManager; import android.app.usage.NetworkStatsManager;
@@ -49,7 +48,6 @@ import java.util.Optional;
public final class DataUsageUtils { public final class DataUsageUtils {
static final boolean TEST_RADIOS = false; static final boolean TEST_RADIOS = false;
static final String TEST_RADIOS_PROP = "test.radios"; static final String TEST_RADIOS_PROP = "test.radios";
private static final boolean LOGD = false;
private static final String ETHERNET = "ethernet"; private static final String ETHERNET = "ethernet";
private static final String TAG = "DataUsageUtils"; private static final String TAG = "DataUsageUtils";
@@ -106,44 +104,6 @@ public final class DataUsageUtils {
return tele.isDataCapable(); return tele.isDataCapable();
} }
/**
* Test if device has a mobile data radio with SIM in ready state.
*/
public static boolean hasReadyMobileRadio(Context context) {
if (DataUsageUtils.TEST_RADIOS) {
return SystemProperties.get(DataUsageUtils.TEST_RADIOS_PROP).contains("mobile");
}
final List<SubscriptionInfo> subInfoList =
ProxySubscriptionManager.getInstance(context)
.getActiveSubscriptionsInfo();
// No activated Subscriptions
if (subInfoList == null) {
if (LOGD) {
Log.d(TAG, "hasReadyMobileRadio: subInfoList=null");
}
return false;
}
final TelephonyManager tele = context.getSystemService(TelephonyManager.class);
// require both supported network and ready SIM
boolean isReady = true;
for (SubscriptionInfo subInfo : subInfoList) {
isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY;
if (LOGD) {
Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo);
}
}
final boolean isDataCapable = tele.isDataCapable();
final boolean retVal = isDataCapable && isReady;
if (LOGD) {
Log.d(TAG, "hasReadyMobileRadio:"
+ " telephonManager.isDataCapable()="
+ isDataCapable
+ " isReady=" + isReady);
}
return retVal;
}
/** /**
* Whether device has a Wi-Fi data radio. * Whether device has a Wi-Fi data radio.
*/ */

View File

@@ -0,0 +1,56 @@
/*
* 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.net.NetworkTemplate
import android.telephony.SubscriptionManager
import androidx.annotation.StringRes
import com.android.settings.R
import com.android.settings.datausage.DataUsageUtils
interface INetworkTemplates {
/**
* Returns the default network template based on the availability of mobile data, Wifi. Returns
* ethernet template if both mobile data and Wifi are not available.
*/
fun getDefaultTemplate(context: Context): NetworkTemplate
}
object NetworkTemplates : INetworkTemplates {
@JvmStatic
@StringRes
fun NetworkTemplate.getTitleResId(): Int =
when (matchRule) {
NetworkTemplate.MATCH_MOBILE,
NetworkTemplate.MATCH_CARRIER -> R.string.cellular_data_usage
NetworkTemplate.MATCH_WIFI -> R.string.wifi_data_usage
NetworkTemplate.MATCH_ETHERNET -> R.string.ethernet_data_usage
else -> R.string.data_usage_app_summary_title
}
/**
* Returns the default network template based on the availability of mobile data, Wifi. Returns
* ethernet template if both mobile data and Wifi are not available.
*/
override fun getDefaultTemplate(context: Context): NetworkTemplate =
DataUsageUtils.getDefaultTemplate(
context,
SubscriptionManager.getDefaultDataSubscriptionId(),
)
}

View File

@@ -18,13 +18,13 @@ package com.android.settings.spa.app.appinfo
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.net.NetworkStats
import android.net.NetworkTemplate import android.net.NetworkTemplate
import android.os.Process import android.os.Process
import android.text.format.DateUtils import android.text.format.DateUtils
import android.text.format.Formatter import android.text.format.Formatter
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -32,25 +32,41 @@ import com.android.settings.R
import com.android.settings.Utils import com.android.settings.Utils
import com.android.settings.applications.appinfo.AppInfoDashboardFragment import com.android.settings.applications.appinfo.AppInfoDashboardFragment
import com.android.settings.datausage.AppDataUsage import com.android.settings.datausage.AppDataUsage
import com.android.settings.datausage.DataUsageUtils import com.android.settings.datausage.lib.INetworkTemplates
import com.android.settings.datausage.lib.NetworkTemplates
import com.android.settings.datausage.lib.NetworkTemplates.getTitleResId
import com.android.settingslib.net.NetworkCycleDataForUid import com.android.settingslib.net.NetworkCycleDataForUid
import com.android.settingslib.net.NetworkCycleDataForUidLoader import com.android.settingslib.net.NetworkCycleDataForUidLoader
import com.android.settingslib.spa.framework.compose.toState import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.model.app.hasFlag import com.android.settingslib.spaprivileged.model.app.hasFlag
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
fun AppDataUsagePreference(app: ApplicationInfo) { fun AppDataUsagePreference(
app: ApplicationInfo,
networkTemplates: INetworkTemplates = NetworkTemplates,
) {
val context = LocalContext.current val context = LocalContext.current
val presenter = remember { AppDataUsagePresenter(context, app) } val coroutineScope = rememberCoroutineScope()
val presenter = remember {
AppDataUsagePresenter(context, app, coroutineScope, networkTemplates)
}
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
Preference(object : PreferenceModel { Preference(object : PreferenceModel {
override val title = stringResource(R.string.data_usage_app_summary_title) override val title = stringResource(
presenter.titleResIdFlow.collectAsStateWithLifecycle(
initialValue = R.string.summary_placeholder,
).value
)
override val summary = presenter.summaryFlow.collectAsStateWithLifecycle( override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
initialValue = stringResource(R.string.computing_size), initialValue = stringResource(R.string.computing_size),
) )
@@ -62,6 +78,8 @@ fun AppDataUsagePreference(app: ApplicationInfo) {
private class AppDataUsagePresenter( private class AppDataUsagePresenter(
private val context: Context, private val context: Context,
private val app: ApplicationInfo, private val app: ApplicationInfo,
coroutineScope: CoroutineScope,
networkTemplates: INetworkTemplates,
) { ) {
val isAvailableFlow = flow { emit(isAvailable()) } val isAvailableFlow = flow { emit(isAvailable()) }
@@ -71,10 +89,17 @@ private class AppDataUsagePresenter(
fun isEnabled() = app.hasFlag(ApplicationInfo.FLAG_INSTALLED) fun isEnabled() = app.hasFlag(ApplicationInfo.FLAG_INSTALLED)
val summaryFlow = flow { emit(getSummary()) } private val templateFlow = flow {
emit(withContext(Dispatchers.IO) {
networkTemplates.getDefaultTemplate(context)
})
}.shareIn(coroutineScope, SharingStarted.WhileSubscribed(), 1)
private suspend fun getSummary() = withContext(Dispatchers.IO) { val titleResIdFlow = templateFlow.map { it.getTitleResId() }
val appUsageData = getAppUsageData() val summaryFlow = templateFlow.map { getSummary(it) }
private suspend fun getSummary(template: NetworkTemplate) = withContext(Dispatchers.IO) {
val appUsageData = getAppUsageData(template)
val totalBytes = appUsageData.sumOf { it.totalUsage } val totalBytes = appUsageData.sumOf { it.totalUsage }
if (totalBytes == 0L) { if (totalBytes == 0L) {
context.getString(R.string.no_data_usage) context.getString(R.string.no_data_usage)
@@ -88,15 +113,15 @@ private class AppDataUsagePresenter(
} }
} }
private suspend fun getAppUsageData(): List<NetworkCycleDataForUid> = private suspend fun getAppUsageData(template: NetworkTemplate): List<NetworkCycleDataForUid> =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
createLoader().loadInBackground() ?: emptyList() createLoader(template).loadInBackground() ?: emptyList()
} }
private fun createLoader(): NetworkCycleDataForUidLoader = private fun createLoader(template: NetworkTemplate): NetworkCycleDataForUidLoader =
NetworkCycleDataForUidLoader.builder(context).apply { NetworkCycleDataForUidLoader.builder(context).apply {
setRetrieveDetail(false) setRetrieveDetail(false)
setNetworkTemplate(getTemplate()) setNetworkTemplate(template)
addUid(app.uid) addUid(app.uid)
if (Process.isApplicationUid(app.uid)) { if (Process.isApplicationUid(app.uid)) {
// Also add in network usage for the app's SDK sandbox // Also add in network usage for the app's SDK sandbox
@@ -104,18 +129,6 @@ private class AppDataUsagePresenter(
} }
}.build() }.build()
private fun getTemplate(): NetworkTemplate = when {
DataUsageUtils.hasReadyMobileRadio(context) -> {
NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
.setMeteredness(NetworkStats.METERED_YES)
.build()
}
DataUsageUtils.hasWifiRadio(context) -> {
NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
}
else -> NetworkTemplate.Builder(NetworkTemplate.MATCH_ETHERNET).build()
}
fun startActivity() { fun startActivity() {
AppInfoDashboardFragment.startAppInfoFragment( AppInfoDashboardFragment.startAppInfoFragment(
AppDataUsage::class.java, AppDataUsage::class.java,

View File

@@ -38,6 +38,7 @@ import com.android.settings.R
import com.android.settings.Utils import com.android.settings.Utils
import com.android.settings.applications.appinfo.AppInfoDashboardFragment import com.android.settings.applications.appinfo.AppInfoDashboardFragment
import com.android.settings.datausage.AppDataUsage import com.android.settings.datausage.AppDataUsage
import com.android.settings.datausage.lib.INetworkTemplates
import com.android.settingslib.net.NetworkCycleDataForUid import com.android.settingslib.net.NetworkCycleDataForUid
import com.android.settingslib.net.NetworkCycleDataForUidLoader import com.android.settingslib.net.NetworkCycleDataForUidLoader
import com.android.settingslib.spa.testutils.delay import com.android.settingslib.spa.testutils.delay
@@ -106,7 +107,7 @@ class AppDataUsagePreferenceTest {
setContent(notInstalledApp) setContent(notInstalledApp)
composeTestRule.onNodeWithText(context.getString(R.string.data_usage_app_summary_title)) composeTestRule.onNodeWithText(context.getString(R.string.cellular_data_usage))
.assertIsDisplayed() .assertIsDisplayed()
.assertIsNotEnabled() .assertIsNotEnabled()
} }
@@ -115,7 +116,7 @@ class AppDataUsagePreferenceTest {
fun whenAppInstalled_enabled() { fun whenAppInstalled_enabled() {
setContent(APP) setContent(APP)
composeTestRule.onNodeWithText(context.getString(R.string.data_usage_app_summary_title)) composeTestRule.onNodeWithText(context.getString(R.string.cellular_data_usage))
.assertIsDisplayed() .assertIsDisplayed()
.assertIsEnabled() .assertIsEnabled()
} }
@@ -169,14 +170,19 @@ class AppDataUsagePreferenceTest {
private fun setContent(app: ApplicationInfo = APP) { private fun setContent(app: ApplicationInfo = APP) {
composeTestRule.setContent { composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) { CompositionLocalProvider(LocalContext provides context) {
AppDataUsagePreference(app) AppDataUsagePreference(app, TestNetworkTemplates)
} }
} }
composeTestRule.delay() composeTestRule.delay()
} }
private object TestNetworkTemplates : INetworkTemplates {
override fun getDefaultTemplate(context: Context): NetworkTemplate =
NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE).build()
}
private companion object { private companion object {
const val PACKAGE_NAME = "packageName" const val PACKAGE_NAME = "package.name"
const val UID = 123 const val UID = 123
val APP = ApplicationInfo().apply { val APP = ApplicationInfo().apply {
packageName = PACKAGE_NAME packageName = PACKAGE_NAME