Improve the performance of AppDataUsagePreference

We only need aggregated summary, but NetworkCycleDataForUidLoader loads
usage for all cycles.

Create AppDataUsageSummaryRepository to reduce the system api call to
only once.

Fix: 304421722
Test: manual - on AppInfoSettings
Test: unit test
Change-Id: I115dfb51dbf77ed3fdde985aa1a968ff7462bebc
This commit is contained in:
Chaohui Wang
2023-10-10 13:18:30 +08:00
parent ceb23d4407
commit 252450b5fc
13 changed files with 391 additions and 211 deletions

View File

@@ -27,7 +27,6 @@ import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IconDrawableFactory;
@@ -139,14 +138,6 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC
}
}
if (mAppItem.key > 0 && UserHandle.isApp(mAppItem.key)) {
// In case we've been asked data usage for an app, automatically
// include data usage of the corresponding SDK sandbox
final int appSandboxUid = Process.toSdkSandboxUid(mAppItem.key);
if (!mAppItem.uids.get(appSandboxUid)) {
mAppItem.addUid(appSandboxUid);
}
}
mTotalUsage = findPreference(KEY_TOTAL_USAGE);
mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE);
mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE);

View File

@@ -17,12 +17,11 @@
package com.android.settings.datausage.lib
import android.app.usage.NetworkStats
import android.app.usage.NetworkStatsManager
import android.content.Context
import android.net.NetworkTemplate
import android.util.Log
import android.util.Range
import androidx.annotation.VisibleForTesting
import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.withSdkSandboxUids
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@@ -35,11 +34,12 @@ class AppDataUsageDetailsRepository @JvmOverloads constructor(
context: Context,
private val template: NetworkTemplate,
private val cycles: List<Long>?,
private val uids: List<Int>,
uids: List<Int>,
private val networkCycleDataRepository: INetworkCycleDataRepository =
NetworkCycleDataRepository(context, template)
NetworkCycleDataRepository(context, template),
) : IAppDataUsageDetailsRepository {
private val networkStatsManager = context.getSystemService(NetworkStatsManager::class.java)!!
private val withSdkSandboxUids = withSdkSandboxUids(uids)
private val networkStatsRepository = NetworkStatsRepository(context, template)
override suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData> = coroutineScope {
getCycles().map {
@@ -56,12 +56,11 @@ class AppDataUsageDetailsRepository @JvmOverloads constructor(
private fun queryDetails(range: Range<Long>): NetworkUsageDetailsData {
var totalUsage = 0L
var foregroundUsage = 0L
for (uid in uids) {
for (uid in withSdkSandboxUids) {
val usage = getUsage(range, uid, NetworkStats.Bucket.STATE_ALL)
if (usage > 0L) {
totalUsage += usage
foregroundUsage +=
getUsage(range, uid, NetworkStats.Bucket.STATE_FOREGROUND)
foregroundUsage += getUsage(range, uid, NetworkStats.Bucket.STATE_FOREGROUND)
}
}
return NetworkUsageDetailsData(
@@ -73,25 +72,6 @@ class AppDataUsageDetailsRepository @JvmOverloads constructor(
}
@VisibleForTesting
fun getUsage(range: Range<Long>, uid: Int, state: Int): Long = try {
networkStatsManager.queryDetailsForUidTagState(
template, range.lower, range.upper, uid, NetworkStats.Bucket.TAG_NONE, state,
).getTotalUsage()
} catch (e: Exception) {
Log.e(TAG, "Exception querying network detail.", e)
0
}
private fun NetworkStats.getTotalUsage(): Long = use {
var bytes = 0L
val bucket = NetworkStats.Bucket()
while (getNextBucket(bucket)) {
bytes += bucket.rxBytes + bucket.txBytes
}
return bytes
}
private companion object {
private const val TAG = "AppDataUsageDetailsRepo"
}
fun getUsage(range: Range<Long>, uid: Int, state: Int): Long =
networkStatsRepository.queryAggregateForUid(range, uid, state)?.usage ?: 0
}

View File

@@ -17,18 +17,17 @@
package com.android.settings.datausage.lib
import android.app.usage.NetworkStats
import android.app.usage.NetworkStatsManager
import android.content.Context
import android.net.NetworkPolicyManager
import android.net.NetworkTemplate
import android.os.Process
import android.os.UserHandle
import android.util.Log
import android.util.SparseArray
import android.util.SparseBooleanArray
import androidx.annotation.VisibleForTesting
import androidx.core.util.keyIterator
import com.android.settings.R
import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
import com.android.settingslib.AppItem
import com.android.settingslib.net.UidDetailProvider
import com.android.settingslib.spaprivileged.framework.common.userManager
@@ -36,14 +35,14 @@ import com.android.settingslib.spaprivileged.framework.common.userManager
class AppDataUsageRepository(
private val context: Context,
private val currentUserId: Int,
private val template: NetworkTemplate,
template: NetworkTemplate,
private val getPackageName: (AppItem) -> String?,
) {
private val networkStatsManager = context.getSystemService(NetworkStatsManager::class.java)!!
private val networkStatsRepository = NetworkStatsRepository(context, template)
fun getAppPercent(carrierId: Int?, startTime: Long, endTime: Long): List<Pair<AppItem, Int>> {
val networkStats = querySummary(startTime, endTime) ?: return emptyList()
return getAppPercent(carrierId, convertToBuckets(networkStats))
val buckets = networkStatsRepository.querySummary(startTime, endTime)
return getAppPercent(carrierId, buckets)
}
@VisibleForTesting
@@ -78,13 +77,6 @@ class AppDataUsageRepository(
}
}
private fun querySummary(startTime: Long, endTime: Long): NetworkStats? = try {
networkStatsManager.querySummary(template, startTime, endTime)
} catch (e: RuntimeException) {
Log.e(TAG, "Exception querying network detail.", e)
null
}
private fun filterItems(carrierId: Int?, items: List<AppItem>): List<AppItem> {
// When there is no specified SubscriptionInfo, Wi-Fi data usage will be displayed.
// In this case, the carrier service package also needs to be hidden.
@@ -189,14 +181,6 @@ class AppDataUsageRepository(
}
companion object {
private const val TAG = "AppDataUsageRepository"
@VisibleForTesting
data class Bucket(
val uid: Int,
val bytes: Long,
)
@JvmStatic
fun getAppUidList(uids: SparseBooleanArray) =
uids.keyIterator().asSequence().map { getAppUid(it) }.distinct().toList()
@@ -210,15 +194,20 @@ class AppDataUsageRepository(
return uid
}
private fun convertToBuckets(stats: NetworkStats): List<Bucket> {
val buckets = mutableListOf<Bucket>()
stats.use {
val bucket = NetworkStats.Bucket()
while (it.getNextBucket(bucket)) {
buckets += Bucket(uid = bucket.uid, bytes = bucket.rxBytes + bucket.txBytes)
/**
* Gets the apps' uids, also add the apps' SDK sandboxes' uids.
*
* In case we've been asked data usage for an app, include data usage of the corresponding
* SDK sandbox.
*/
fun withSdkSandboxUids(uids: List<Int>): List<Int> {
val set = uids.toMutableSet()
for (uid in uids) {
if (Process.isApplicationUid(uid)) {
set += Process.toSdkSandboxUid(uid)
}
}
return buckets
return set.toList()
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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 com.android.settings.datausage.lib.AppDataUsageRepository.Companion.withSdkSandboxUids
import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.AllTimeRange
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
interface IAppDataUsageSummaryRepository {
suspend fun querySummary(uid: Int): NetworkUsageData?
}
class AppDataUsageSummaryRepository(
context: Context,
template: NetworkTemplate,
private val networkStatsRepository: NetworkStatsRepository =
NetworkStatsRepository(context, template),
) : IAppDataUsageSummaryRepository {
override suspend fun querySummary(uid: Int): NetworkUsageData? = coroutineScope {
withSdkSandboxUids(listOf(uid)).map { uid ->
async {
networkStatsRepository.queryAggregateForUid(range = AllTimeRange, uid = uid)
}
}.awaitAll().filterNotNull().aggregate()
}
}

View File

@@ -16,16 +16,12 @@
package com.android.settings.datausage.lib
import android.app.usage.NetworkStats
import android.app.usage.NetworkStatsManager
import android.content.Context
import android.net.NetworkPolicy
import android.net.NetworkPolicyManager
import android.net.NetworkTemplate
import android.text.format.DateUtils
import android.util.Log
import android.util.Range
import androidx.annotation.VisibleForTesting
import com.android.settingslib.NetworkPolicyEditor
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@@ -41,8 +37,9 @@ interface INetworkCycleDataRepository {
class NetworkCycleDataRepository(
context: Context,
private val networkTemplate: NetworkTemplate,
private val networkStatsRepository: NetworkStatsRepository =
NetworkStatsRepository(context, networkTemplate),
) : INetworkCycleDataRepository {
private val networkStatsManager = context.getSystemService(NetworkStatsManager::class.java)!!
private val policyManager = context.getSystemService(NetworkPolicyManager::class.java)!!
@@ -57,7 +54,7 @@ class NetworkCycleDataRepository(
}
private fun queryCyclesAsFourWeeks(): List<Range<Long>> {
val timeRange = getTimeRange()
val timeRange = networkStatsRepository.getTimeRange() ?: return emptyList()
return reverseBucketRange(
startTime = timeRange.lower,
endTime = timeRange.upper,
@@ -65,22 +62,6 @@ class NetworkCycleDataRepository(
)
}
@VisibleForTesting
fun getTimeRange(): Range<Long> = getTimeRangeOf(
networkStatsManager.queryDetailsForDevice(networkTemplate, Long.MIN_VALUE, Long.MAX_VALUE)
)
private fun getTimeRangeOf(stats: NetworkStats): Range<Long> {
var start = Long.MAX_VALUE
var end = Long.MIN_VALUE
val bucket = NetworkStats.Bucket()
while (stats.getNextBucket(bucket)) {
start = start.coerceAtMost(bucket.startTimeStamp)
end = end.coerceAtLeast(bucket.endTimeStamp)
}
return Range(start, end)
}
override fun getPolicy(): NetworkPolicy? =
with(NetworkPolicyEditor(policyManager)) {
read()
@@ -88,7 +69,7 @@ class NetworkCycleDataRepository(
}
override suspend fun querySummary(startTime: Long, endTime: Long): NetworkCycleChartData? {
val usage = getUsage(startTime, endTime)
val usage = networkStatsRepository.querySummaryForDevice(startTime, endTime)
if (usage > 0L) {
return NetworkCycleChartData(
total = NetworkUsageData(startTime, endTime, usage),
@@ -108,7 +89,7 @@ class NetworkCycleDataRepository(
NetworkUsageData(
startTime = range.lower,
endTime = range.upper,
usage = getUsage(range.lower, range.upper),
usage = networkStatsRepository.querySummaryForDevice(range.lower, range.upper),
)
}
}.awaitAll()
@@ -139,17 +120,4 @@ class NetworkCycleDataRepository(
}
return buckets
}
private fun getUsage(start: Long, end: Long): Long = try {
networkStatsManager.querySummaryForDevice(networkTemplate, start, end).let {
it.rxBytes + it.txBytes
}
} catch (e: Exception) {
Log.e(TAG, "Exception querying network detail.", e)
0
}
companion object {
private const val TAG = "NetworkCycleDataRepository"
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.app.usage.NetworkStats
import android.app.usage.NetworkStatsManager
import android.content.Context
import android.net.NetworkTemplate
import android.util.Log
import android.util.Range
class NetworkStatsRepository(context: Context, private val template: NetworkTemplate) {
private val networkStatsManager = context.getSystemService(NetworkStatsManager::class.java)!!
fun queryAggregateForUid(
range: Range<Long>,
uid: Int,
state: Int = NetworkStats.Bucket.STATE_ALL,
): NetworkUsageData? = try {
networkStatsManager.queryDetailsForUidTagState(
template, range.lower, range.upper, uid, NetworkStats.Bucket.TAG_NONE, state,
).aggregate()
} catch (e: Exception) {
Log.e(TAG, "Exception queryDetailsForUidTagState", e)
null
}
fun getTimeRange(): Range<Long>? = try {
networkStatsManager.queryDetailsForDevice(template, Long.MIN_VALUE, Long.MAX_VALUE)
.aggregate()?.timeRange
} catch (e: Exception) {
Log.e(TAG, "Exception queryDetailsForDevice", e)
null
}
fun querySummaryForDevice(startTime: Long, endTime: Long): Long = try {
networkStatsManager.querySummaryForDevice(template, startTime, endTime).bytes
} catch (e: Exception) {
Log.e(TAG, "Exception querySummaryForDevice", e)
0
}
fun querySummary(startTime: Long, endTime: Long): List<Bucket> = try {
networkStatsManager.querySummary(template, startTime, endTime).convertToBuckets()
} catch (e: Exception) {
Log.e(TAG, "Exception querySummary", e)
emptyList()
}
companion object {
private const val TAG = "NetworkStatsRepository"
val AllTimeRange = Range(Long.MIN_VALUE, Long.MAX_VALUE)
data class Bucket(
val uid: Int,
val bytes: Long,
)
private fun NetworkStats.convertToBuckets(): List<Bucket> = use {
val buckets = mutableListOf<Bucket>()
val bucket = NetworkStats.Bucket()
while (getNextBucket(bucket)) {
buckets += Bucket(uid = bucket.uid, bytes = bucket.bytes)
}
buckets
}
private fun NetworkStats.aggregate(): NetworkUsageData? = use {
var startTime = Long.MAX_VALUE
var endTime = Long.MIN_VALUE
var usage = 0L
val bucket = NetworkStats.Bucket()
while (getNextBucket(bucket)) {
startTime = startTime.coerceAtMost(bucket.startTimeStamp)
endTime = endTime.coerceAtLeast(bucket.endTimeStamp)
usage += bucket.bytes
}
when {
startTime > endTime -> null
else -> NetworkUsageData(startTime, endTime, usage)
}
}
private val NetworkStats.Bucket.bytes: Long
get() = rxBytes + txBytes
}
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.datausage.lib
import android.util.Range
/**
* Base data structure representing usage data in a period.
*/
@@ -23,4 +25,15 @@ data class NetworkUsageData(
val startTime: Long,
val endTime: Long,
val usage: Long,
)
) {
val timeRange = Range(startTime, endTime)
}
fun List<NetworkUsageData>.aggregate(): NetworkUsageData? = when {
isEmpty() -> null
else -> NetworkUsageData(
startTime = minOf { it.startTime },
endTime = maxOf { it.endTime },
usage = sumOf { it.usage },
)
}