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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
102
src/com/android/settings/datausage/lib/NetworkStatsRepository.kt
Normal file
102
src/com/android/settings/datausage/lib/NetworkStatsRepository.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user