Create AppDataUsageCycleController

To improve performance and better organization and testings.

Fix: 240931350
Test: manual - on AppDataUsage
Test: unit test
Change-Id: I277133b55378a3445aceb826d771b14c0fc91e4a
This commit is contained in:
Chaohui Wang
2023-10-08 19:46:32 +08:00
parent 0bcf5b79f8
commit 741979bc02
15 changed files with 536 additions and 270 deletions

View File

@@ -0,0 +1,97 @@
/*
* 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
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
interface IAppDataUsageDetailsRepository {
suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData>
}
class AppDataUsageDetailsRepository @JvmOverloads constructor(
context: Context,
private val template: NetworkTemplate,
private val cycles: List<Long>?,
private val uids: List<Int>,
private val networkCycleDataRepository: INetworkCycleDataRepository =
NetworkCycleDataRepository(context, template)
) : IAppDataUsageDetailsRepository {
private val networkStatsManager = context.getSystemService(NetworkStatsManager::class.java)!!
override suspend fun queryDetailsForCycles(): List<NetworkUsageDetailsData> = coroutineScope {
getCycles().map {
async {
queryDetails(it)
}
}.awaitAll().filter { it.totalUsage > 0 }
}
private fun getCycles(): List<Range<Long>> =
cycles?.zipWithNext { endTime, startTime -> Range(startTime, endTime) }
?: networkCycleDataRepository.getCycles()
private fun queryDetails(range: Range<Long>): NetworkUsageDetailsData {
var totalUsage = 0L
var foregroundUsage = 0L
for (uid in uids) {
val usage = getUsage(range, uid, NetworkStats.Bucket.STATE_ALL)
if (usage > 0L) {
totalUsage += usage
foregroundUsage +=
getUsage(range, uid, NetworkStats.Bucket.STATE_FOREGROUND)
}
}
return NetworkUsageDetailsData(
range = range,
totalUsage = totalUsage,
foregroundUsage = foregroundUsage,
backgroundUsage = totalUsage - foregroundUsage,
)
}
@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"
}
}

View File

@@ -25,7 +25,9 @@ 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.settingslib.AppItem
import com.android.settingslib.net.UidDetailProvider
@@ -195,6 +197,10 @@ class AppDataUsageRepository(
val bytes: Long,
)
@JvmStatic
fun getAppUidList(uids: SparseBooleanArray) =
uids.keyIterator().asSequence().map { getAppUid(it) }.distinct().toList()
@JvmStatic
fun getAppUid(uid: Int): Int {
if (Process.isSdkSandboxUid(uid)) {

View File

@@ -33,6 +33,7 @@ import kotlinx.coroutines.coroutineScope
interface INetworkCycleDataRepository {
suspend fun loadCycles(): List<NetworkUsageData>
fun getCycles(): List<Range<Long>>
fun getPolicy(): NetworkPolicy?
suspend fun querySummary(startTime: Long, endTime: Long): NetworkCycleChartData?
}
@@ -48,7 +49,7 @@ class NetworkCycleDataRepository(
override suspend fun loadCycles(): List<NetworkUsageData> =
getCycles().queryUsage().filter { it.usage > 0 }
private fun getCycles(): List<Range<Long>> {
override fun getCycles(): List<Range<Long>> {
val policy = getPolicy() ?: return queryCyclesAsFourWeeks()
return policy.cycleIterator().asSequence().map {
Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())

View File

@@ -0,0 +1,38 @@
/*
* 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.util.Range
/**
* Details data structure representing usage data in a period.
*/
data class NetworkUsageDetailsData(
val range: Range<Long>,
val totalUsage: Long,
val foregroundUsage: Long,
val backgroundUsage: Long,
) {
companion object {
val AllZero = NetworkUsageDetailsData(
range = Range(0, 0),
totalUsage = 0,
foregroundUsage = 0,
backgroundUsage = 0,
)
}
}