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 },
)
}

View File

@@ -19,7 +19,6 @@ package com.android.settings.spa.app.appinfo
import android.content.Context
import android.content.pm.ApplicationInfo
import android.net.NetworkTemplate
import android.os.Process
import android.text.format.DateUtils
import android.text.format.Formatter
import androidx.compose.runtime.Composable
@@ -32,11 +31,11 @@ import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
import com.android.settings.datausage.AppDataUsage
import com.android.settings.datausage.lib.AppDataUsageSummaryRepository
import com.android.settings.datausage.lib.IAppDataUsageSummaryRepository
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.NetworkCycleDataForUidLoader
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -53,11 +52,17 @@ import kotlinx.coroutines.withContext
fun AppDataUsagePreference(
app: ApplicationInfo,
networkTemplates: INetworkTemplates = NetworkTemplates,
repositoryFactory: (
context: Context,
networkTemplate: NetworkTemplate,
) -> IAppDataUsageSummaryRepository = { context, networkTemplate ->
AppDataUsageSummaryRepository(context, networkTemplate)
}
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val presenter = remember {
AppDataUsagePresenter(context, app, coroutineScope, networkTemplates)
AppDataUsagePresenter(context, app, coroutineScope, networkTemplates, repositoryFactory)
}
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
@@ -80,6 +85,10 @@ private class AppDataUsagePresenter(
private val app: ApplicationInfo,
coroutineScope: CoroutineScope,
networkTemplates: INetworkTemplates,
private val repositoryFactory: (
context: Context,
networkTemplate: NetworkTemplate,
) -> IAppDataUsageSummaryRepository,
) {
val isAvailableFlow = flow { emit(isAvailable()) }
@@ -99,36 +108,18 @@ private class AppDataUsagePresenter(
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 }
if (totalBytes == 0L) {
val appUsageData = repositoryFactory(context, template).querySummary(app.uid)
if (appUsageData == null || appUsageData.usage == 0L) {
context.getString(R.string.no_data_usage)
} else {
val startTime = appUsageData.minOfOrNull { it.startTime } ?: System.currentTimeMillis()
context.getString(
R.string.data_summary_format,
Formatter.formatFileSize(context, totalBytes, Formatter.FLAG_IEC_UNITS),
DateUtils.formatDateTime(context, startTime, DATE_FORMAT),
Formatter.formatFileSize(context, appUsageData.usage, Formatter.FLAG_IEC_UNITS),
DateUtils.formatDateTime(context, appUsageData.startTime, DATE_FORMAT),
)
}
}
private suspend fun getAppUsageData(template: NetworkTemplate): List<NetworkCycleDataForUid> =
withContext(Dispatchers.IO) {
createLoader(template).loadInBackground() ?: emptyList()
}
private fun createLoader(template: NetworkTemplate): NetworkCycleDataForUidLoader =
NetworkCycleDataForUidLoader.builder(context).apply {
setRetrieveDetail(false)
setNetworkTemplate(template)
addUid(app.uid)
if (Process.isApplicationUid(app.uid)) {
// Also add in network usage for the app's SDK sandbox
addUid(Process.toSdkSandboxUid(app.uid))
}
}.build()
fun startActivity() {
AppInfoDashboardFragment.startAppInfoFragment(
AppDataUsage::class.java,

View File

@@ -26,7 +26,7 @@ import android.os.UserManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.Bucket
import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
import com.android.settingslib.AppItem
import com.android.settingslib.spaprivileged.framework.common.userManager
import com.google.common.truth.Truth.assertThat

View File

@@ -0,0 +1,74 @@
/*
* 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 androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
class AppDataUsageSummaryRepositoryTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val template = mock<NetworkTemplate>()
private val networkStatsRepository = mock<NetworkStatsRepository> {
on {
queryAggregateForUid(range = NetworkStatsRepository.AllTimeRange, uid = APP_UID)
} doReturn NetworkUsageData(APP_START_TIME, APP_END_TIME, APP_USAGE)
on {
queryAggregateForUid(range = NetworkStatsRepository.AllTimeRange, uid = SDK_SANDBOX_UID)
} doReturn NetworkUsageData(SDK_SANDBOX_START_TIME, SDK_SANDBOX_END_TIME, SDK_SANDBOX_USAGE)
}
private val repository =
AppDataUsageSummaryRepository(context, template, networkStatsRepository)
@Test
fun querySummary(): Unit = runBlocking {
val networkUsageData = repository.querySummary(APP_UID)
assertThat(networkUsageData).isEqualTo(
NetworkUsageData(
startTime = APP_START_TIME,
endTime = SDK_SANDBOX_END_TIME,
usage = APP_USAGE + SDK_SANDBOX_USAGE,
)
)
}
private companion object {
const val APP_UID = 10000
const val APP_START_TIME = 10L
const val APP_END_TIME = 30L
const val APP_USAGE = 3L
const val SDK_SANDBOX_UID = 20000
const val SDK_SANDBOX_START_TIME = 20L
const val SDK_SANDBOX_END_TIME = 40L
const val SDK_SANDBOX_USAGE = 5L
}
}

View File

@@ -16,8 +16,6 @@
package com.android.settings.datausage.lib
import android.app.usage.NetworkStats.Bucket
import android.app.usage.NetworkStatsManager
import android.content.Context
import android.net.NetworkPolicy
import android.net.NetworkTemplate
@@ -32,44 +30,37 @@ import java.time.ZonedDateTime
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class NetworkCycleDataRepositoryTest {
private val mockNetworkStatsManager = mock<NetworkStatsManager> {
on { querySummaryForDevice(any(), eq(CYCLE1_START_TIME), eq(CYCLE1_END_TIME)) } doReturn
CYCLE1_BUCKET
private val mockNetworkStatsRepository = mock<NetworkStatsRepository> {
on { querySummaryForDevice(CYCLE1_START_TIME, CYCLE1_END_TIME) } doReturn CYCLE1_BYTES
on {
querySummaryForDevice(
any(),
eq(CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4),
eq(CYCLE2_END_TIME),
startTime = CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4,
endTime = CYCLE2_END_TIME,
)
} doReturn CYCLE2_BUCKET
} doReturn CYCLE2_BYTES
on { querySummaryForDevice(any(), eq(CYCLE3_START_TIME), eq(CYCLE4_END_TIME)) } doReturn
CYCLE3_AND_4_BUCKET
on { querySummaryForDevice(CYCLE3_START_TIME, CYCLE4_END_TIME) } doReturn
CYCLE3_BYTES + CYCLE4_BYTES
on { querySummaryForDevice(any(), eq(CYCLE3_START_TIME), eq(CYCLE3_END_TIME)) } doReturn
CYCLE3_BUCKET
on { querySummaryForDevice(any(), eq(CYCLE4_START_TIME), eq(CYCLE4_END_TIME)) } doReturn
CYCLE4_BUCKET
on { querySummaryForDevice(CYCLE3_START_TIME, CYCLE3_END_TIME) } doReturn CYCLE3_BYTES
on { querySummaryForDevice(CYCLE4_START_TIME, CYCLE4_END_TIME) } doReturn CYCLE4_BYTES
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(NetworkStatsManager::class.java) } doReturn mockNetworkStatsManager
}
private val context: Context = ApplicationProvider.getApplicationContext()
private val template = mock<NetworkTemplate>()
private val repository = spy(NetworkCycleDataRepository(context, template))
private val repository =
spy(NetworkCycleDataRepository(context, template, mockNetworkStatsRepository))
@Test
fun loadCycles_byPolicy() = runTest {
@@ -82,13 +73,17 @@ class NetworkCycleDataRepositoryTest {
val cycles = repository.loadCycles()
assertThat(cycles).containsExactly(NetworkUsageData(startTime = 1, endTime = 2, usage = 11))
assertThat(cycles).containsExactly(
NetworkUsageData(startTime = 1, endTime = 2, usage = CYCLE1_BYTES),
)
}
@Test
fun loadCycles_asFourWeeks() = runTest {
doReturn(null).whenever(repository).getPolicy()
doReturn(Range(CYCLE2_START_TIME, CYCLE2_END_TIME)).whenever(repository).getTimeRange()
mockNetworkStatsRepository.stub {
on { getTimeRange() } doReturn Range(CYCLE2_START_TIME, CYCLE2_END_TIME)
}
val cycles = repository.loadCycles()
@@ -96,7 +91,7 @@ class NetworkCycleDataRepositoryTest {
NetworkUsageData(
startTime = CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4,
endTime = CYCLE2_END_TIME,
usage = 22,
usage = CYCLE2_BYTES,
),
)
}
@@ -110,18 +105,18 @@ class NetworkCycleDataRepositoryTest {
total = NetworkUsageData(
startTime = CYCLE3_START_TIME,
endTime = CYCLE4_END_TIME,
usage = 77,
usage = CYCLE3_BYTES + CYCLE4_BYTES,
),
dailyUsage = listOf(
NetworkUsageData(
startTime = CYCLE3_START_TIME,
endTime = CYCLE3_END_TIME,
usage = 33,
usage = CYCLE3_BYTES,
),
NetworkUsageData(
startTime = CYCLE4_START_TIME,
endTime = CYCLE4_END_TIME,
usage = 44,
usage = CYCLE4_BYTES,
),
),
)
@@ -134,35 +129,18 @@ class NetworkCycleDataRepositoryTest {
private companion object {
const val CYCLE1_START_TIME = 1L
const val CYCLE1_END_TIME = 2L
val CYCLE1_BUCKET = mock<Bucket> {
on { rxBytes } doReturn 1
on { txBytes } doReturn 10
}
const val CYCLE1_BYTES = 11L
const val CYCLE2_START_TIME = 1695555555000L
const val CYCLE2_END_TIME = 1695566666000L
val CYCLE2_BUCKET = mock<Bucket> {
on { rxBytes } doReturn 2
on { txBytes } doReturn 20
}
const val CYCLE2_BYTES = 22L
const val CYCLE3_START_TIME = 1695555555000L
const val CYCLE3_END_TIME = CYCLE3_START_TIME + DateUtils.DAY_IN_MILLIS
val CYCLE3_BUCKET = mock<Bucket> {
on { rxBytes } doReturn 3
on { txBytes } doReturn 30
}
const val CYCLE3_BYTES = 33L
const val CYCLE4_START_TIME = CYCLE3_END_TIME
const val CYCLE4_END_TIME = CYCLE4_START_TIME + DateUtils.DAY_IN_MILLIS
val CYCLE4_BUCKET = mock<Bucket> {
on { rxBytes } doReturn 4
on { txBytes } doReturn 40
}
val CYCLE3_AND_4_BUCKET = mock<Bucket> {
on { rxBytes } doReturn 7
on { txBytes } doReturn 70
}
const val CYCLE4_BYTES = 44L
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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 androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
@RunWith(AndroidJUnit4::class)
class NetworkStatsRepositoryTest {
private val template = mock<NetworkTemplate>()
private val mockNetworkStatsManager = mock<NetworkStatsManager> {
on { querySummaryForDevice(template, START_TIME, END_TIME) } doReturn BUCKET
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(NetworkStatsManager::class.java) } doReturn mockNetworkStatsManager
}
private val repository = NetworkStatsRepository(context, template)
@Test
fun querySummaryForDevice() {
val bytes = repository.querySummaryForDevice(START_TIME, END_TIME)
assertThat(bytes).isEqualTo(11)
}
private companion object {
const val START_TIME = 1L
const val END_TIME = 2L
val BUCKET = mock<NetworkStats.Bucket> {
on { rxBytes } doReturn 1
on { txBytes } doReturn 10
}
}
}

View File

@@ -38,9 +38,9 @@ import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
import com.android.settings.datausage.AppDataUsage
import com.android.settings.datausage.lib.IAppDataUsageSummaryRepository
import com.android.settings.datausage.lib.INetworkTemplates
import com.android.settingslib.net.NetworkCycleDataForUid
import com.android.settingslib.net.NetworkCycleDataForUidLoader
import com.android.settings.datausage.lib.NetworkUsageData
import com.android.settingslib.spa.testutils.delay
import com.android.settingslib.spa.testutils.waitUntilExists
import org.junit.After
@@ -48,11 +48,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoSession
import org.mockito.Spy
import org.mockito.quality.Strictness
import org.mockito.Mockito.`when` as whenever
@@ -63,28 +59,26 @@ class AppDataUsagePreferenceTest {
private lateinit var mockSession: MockitoSession
@Spy
private val context: Context = ApplicationProvider.getApplicationContext()
@Mock
private lateinit var builder: NetworkCycleDataForUidLoader.Builder<NetworkCycleDataForUidLoader>
private var networkUsageData: NetworkUsageData? = null
@Mock
private lateinit var loader: NetworkCycleDataForUidLoader
private inner class TestRepository : IAppDataUsageSummaryRepository {
override suspend fun querySummary(uid: Int): NetworkUsageData? = when (uid) {
UID -> networkUsageData
else -> null
}
}
@Before
fun setUp() {
mockSession = mockitoSession()
.initMocks(this)
.mockStatic(Utils::class.java)
.mockStatic(NetworkCycleDataForUidLoader::class.java)
.mockStatic(NetworkTemplate::class.java)
.mockStatic(AppInfoDashboardFragment::class.java)
.strictness(Strictness.LENIENT)
.startMocking()
whenever(Utils.isBandwidthControlEnabled()).thenReturn(true)
whenever(NetworkCycleDataForUidLoader.builder(context)).thenReturn(builder)
whenever(builder.build()).thenReturn(loader)
}
@After
@@ -121,17 +115,9 @@ class AppDataUsagePreferenceTest {
.assertIsEnabled()
}
@Test
fun setCorrectValuesForBuilder() {
setContent()
verify(builder).setRetrieveDetail(false)
verify(builder).addUid(UID)
}
@Test
fun whenNoDataUsage() {
whenever(loader.loadInBackground()).thenReturn(emptyList())
networkUsageData = null
setContent()
@@ -140,10 +126,11 @@ class AppDataUsagePreferenceTest {
@Test
fun whenHasDataUsage() {
val cycleData = mock(NetworkCycleDataForUid::class.java)
whenever(cycleData.totalUsage).thenReturn(123)
whenever(cycleData.startTime).thenReturn(1666666666666)
whenever(loader.loadInBackground()).thenReturn(listOf(cycleData))
networkUsageData = NetworkUsageData(
startTime = 1666666666666L,
endTime = 1666666666666L,
usage = 123L,
)
setContent()
@@ -152,8 +139,6 @@ class AppDataUsagePreferenceTest {
@Test
fun whenClick_startActivity() {
whenever(loader.loadInBackground()).thenReturn(emptyList())
setContent()
composeTestRule.onRoot().performClick()
@@ -170,7 +155,9 @@ class AppDataUsagePreferenceTest {
private fun setContent(app: ApplicationInfo = APP) {
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
AppDataUsagePreference(app, TestNetworkTemplates)
AppDataUsagePreference(app, TestNetworkTemplates) { _, _ ->
TestRepository()
}
}
}
composeTestRule.delay()