Improve the latency of DataUsageList

Up to 30x faster.

Currently it load the usage detail for every day at the beginning, so
it's quite slow.

To fix,
- Not load the usage detail for every day at the beginning
- Load only the cycles first
- And only load the daily detail for the selected month

Fix: 290856342
Test: manual - on DataUsageList (cell & wifi)
Test: unit tests
Change-Id: Ie18fa68f801743389bd6b6a28e236dcf1fea00e4
This commit is contained in:
Chaohui Wang
2023-09-15 13:50:11 +08:00
parent db9fdb0de8
commit 2205762482
19 changed files with 1185 additions and 727 deletions

View File

@@ -0,0 +1,229 @@
/*
* 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.app.settings.SettingsEnums
import android.net.NetworkPolicy
import android.net.NetworkTemplate
import android.os.Bundle
import android.provider.Settings
import android.telephony.SubscriptionManager
import android.util.EventLog
import android.util.Log
import android.view.View
import androidx.annotation.OpenForTesting
import androidx.annotation.VisibleForTesting
import androidx.preference.Preference
import com.android.settings.R
import com.android.settings.datausage.lib.BillingCycleRepository
import com.android.settings.datausage.lib.NetworkUsageData
import com.android.settings.network.MobileDataEnabledListener
import com.android.settings.network.MobileNetworkRepository
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity
import com.android.settingslib.spaprivileged.framework.common.userManager
import com.android.settingslib.utils.ThreadUtils
import kotlin.jvm.optionals.getOrNull
/**
* Panel showing data usage history across various networks, including options
* to inspect based on usage cycle and control through [NetworkPolicy].
*/
@OpenForTesting
open class DataUsageList : DataUsageBaseFragment(), MobileDataEnabledListener.Client {
@VisibleForTesting
lateinit var dataStateListener: MobileDataEnabledListener
@JvmField
@VisibleForTesting
var template: NetworkTemplate? = null
@JvmField
@VisibleForTesting
var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
// Spinner will keep the selected cycle even after paused, this only keeps the displayed cycle,
// which need be cleared when resumed.
private var lastDisplayedUsageData: NetworkUsageData? = null
private lateinit var usageAmount: Preference
private var subscriptionInfoEntity: SubscriptionInfoEntity? = null
private lateinit var dataUsageListAppsController: DataUsageListAppsController
private lateinit var chartDataUsagePreferenceController: ChartDataUsagePreferenceController
private lateinit var billingCycleRepository: BillingCycleRepository
@VisibleForTesting
var dataUsageListHeaderController: DataUsageListHeaderController? = null
override fun getMetricsCategory() = SettingsEnums.DATA_USAGE_LIST
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (requireContext().userManager.isGuestUser) {
Log.e(TAG, "This setting isn't available for guest user")
EventLog.writeEvent(0x534e4554, "262741858", -1 /* UID */, "Guest user")
finish()
return
}
billingCycleRepository = createBillingCycleRepository();
if (!billingCycleRepository.isBandwidthControlEnabled()) {
Log.w(TAG, "No bandwidth control; leaving")
finish()
return
}
usageAmount = findPreference(KEY_USAGE_AMOUNT)!!
processArgument()
val template = template
if (template == null) {
Log.e(TAG, "No template; leaving")
finish()
return
}
updateSubscriptionInfoEntity()
dataStateListener = MobileDataEnabledListener(activity, this)
dataUsageListAppsController = use(DataUsageListAppsController::class.java).apply {
init(template)
}
chartDataUsagePreferenceController = use(ChartDataUsagePreferenceController::class.java)
chartDataUsagePreferenceController.init(template)
}
@VisibleForTesting
open fun createBillingCycleRepository() = BillingCycleRepository(requireContext())
override fun onViewCreated(v: View, savedInstanceState: Bundle?) {
super.onViewCreated(v, savedInstanceState)
val template = template ?: return
dataUsageListHeaderController = DataUsageListHeaderController(
setPinnedHeaderView(R.layout.apps_filter_spinner),
template,
metricsCategory,
viewLifecycleOwner,
::onCyclesLoad,
::updateSelectedCycle,
)
}
override fun onResume() {
super.onResume()
dataStateListener.start(subId)
lastDisplayedUsageData = null
updatePolicy()
}
override fun onPause() {
super.onPause()
dataStateListener.stop()
}
override fun getPreferenceScreenResId() = R.xml.data_usage_list
override fun getLogTag() = TAG
fun processArgument() {
arguments?.let {
subId = it.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID)
template = it.getParcelable(EXTRA_NETWORK_TEMPLATE, NetworkTemplate::class.java)
}
if (template == null && subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
subId = intent.getIntExtra(
Settings.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID,
)
template = intent.getParcelableExtra(
Settings.EXTRA_NETWORK_TEMPLATE,
NetworkTemplate::class.java,
) ?: DataUsageUtils.getMobileNetworkTemplateFromSubId(context, intent).getOrNull()
}
}
@VisibleForTesting
open fun updateSubscriptionInfoEntity() {
ThreadUtils.postOnBackgroundThread {
subscriptionInfoEntity =
MobileNetworkRepository.getInstance(context).getSubInfoById(subId.toString())
}
}
/**
* Implementation of `MobileDataEnabledListener.Client`
*/
override fun onMobileDataEnabledChange() {
updatePolicy()
}
/** Update chart sweeps and cycle list to reflect [NetworkPolicy] for current [template]. */
@VisibleForTesting
fun updatePolicy() {
val isBillingCycleModifiable = isBillingCycleModifiable()
dataUsageListHeaderController?.setConfigButtonVisible(isBillingCycleModifiable)
chartDataUsagePreferenceController.setBillingCycleModifiable(isBillingCycleModifiable)
}
@VisibleForTesting
open fun isBillingCycleModifiable(): Boolean {
return (billingCycleRepository.isModifiable(subId) &&
requireContext().getSystemService(SubscriptionManager::class.java)!!
.getActiveSubscriptionInfo(subId) != null)
}
private fun onCyclesLoad(networkUsageData: List<NetworkUsageData>) {
dataUsageListAppsController.updateCycles(networkUsageData)
}
/**
* Updates the chart and detail data when initial loaded or selected cycle changed.
*/
private fun updateSelectedCycle(usageData: NetworkUsageData) {
if (usageData == lastDisplayedUsageData) {
// Avoid duplicate update to avoid page flash.
return
}
lastDisplayedUsageData = usageData
Log.d(TAG, "showing cycle $usageData")
val totalPhrase = DataUsageUtils.formatDataUsage(requireContext(), usageData.usage)
usageAmount.title = getString(R.string.data_used_template, totalPhrase)
updateChart(usageData)
updateApps(usageData)
}
/** Updates chart to show selected cycle. */
private fun updateChart(usageData: NetworkUsageData) {
chartDataUsagePreferenceController.update(
startTime = usageData.startTime,
endTime = usageData.endTime,
)
}
/** Updates applications data usage. */
private fun updateApps(usageData: NetworkUsageData) {
dataUsageListAppsController.update(
carrierId = subscriptionInfoEntity?.carrierId,
startTime = usageData.startTime,
endTime = usageData.endTime,
)
}
companion object {
const val EXTRA_SUB_ID = "sub_id"
const val EXTRA_NETWORK_TEMPLATE = "network_template"
private const val TAG = "DataUsageList"
private const val KEY_USAGE_AMOUNT = "usage_amount"
}
}