Refresh DataUsageSummaryPreferenceController

When re-enter the page contains it.

Fix: 341234382
Test: manual - on Data usage
Test: unit test
Change-Id: Ib6a4624e11b60d703c35cea07232cc24f1516389
This commit is contained in:
Chaohui Wang
2024-05-23 14:20:10 +08:00
parent 1302513608
commit 513ca3a808
4 changed files with 159 additions and 34 deletions

View File

@@ -21,20 +21,18 @@ import android.net.NetworkPolicy
import android.net.NetworkTemplate import android.net.NetworkTemplate
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.android.settings.R import com.android.settings.R
import com.android.settings.datausage.lib.DataUsageLib.getMobileTemplate import com.android.settings.datausage.lib.DataUsageLib.getMobileTemplate
import com.android.settings.datausage.lib.INetworkCycleDataRepository import com.android.settings.datausage.lib.INetworkCycleDataRepository
import com.android.settings.datausage.lib.NetworkCycleDataRepository import com.android.settings.datausage.lib.NetworkCycleDataRepository
import com.android.settings.network.ProxySubscriptionManager import com.android.settings.network.ProxySubscriptionManager
import com.android.settings.network.policy.NetworkPolicyRepository
import com.android.settings.network.telephony.TelephonyBasePreferenceController import com.android.settings.network.telephony.TelephonyBasePreferenceController
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlin.math.max import kotlin.math.max
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
/** /**
@@ -47,6 +45,7 @@ open class DataUsageSummaryPreferenceController @JvmOverloads constructor(
subId: Int, subId: Int,
private val proxySubscriptionManager: ProxySubscriptionManager = private val proxySubscriptionManager: ProxySubscriptionManager =
ProxySubscriptionManager.getInstance(context), ProxySubscriptionManager.getInstance(context),
private val networkPolicyRepository: NetworkPolicyRepository = NetworkPolicyRepository(context),
private val networkCycleDataRepositoryFactory: ( private val networkCycleDataRepositoryFactory: (
template: NetworkTemplate, template: NetworkTemplate,
) -> INetworkCycleDataRepository = { NetworkCycleDataRepository(context, it) }, ) -> INetworkCycleDataRepository = { NetworkCycleDataRepository(context, it) },
@@ -64,37 +63,37 @@ open class DataUsageSummaryPreferenceController @JvmOverloads constructor(
proxySubscriptionManager.getAccessibleSubscriptionInfo(mSubId) proxySubscriptionManager.getAccessibleSubscriptionInfo(mSubId)
} else null } else null
} }
private val networkTemplate by lazy { getMobileTemplate(mContext, mSubId) }
private val networkCycleDataRepository by lazy { private val networkCycleDataRepository by lazy {
networkCycleDataRepositoryFactory(getMobileTemplate(mContext, mSubId)) networkCycleDataRepositoryFactory(networkTemplate)
} }
private val policy by lazy { networkCycleDataRepository.getPolicy() }
private lateinit var preference: DataUsageSummaryPreference private lateinit var preference: DataUsageSummaryPreference
override fun getAvailabilityStatus(subId: Int) = override fun getAvailabilityStatus(subId: Int) =
if (subInfo != null && policy != null) AVAILABLE else CONDITIONALLY_UNAVAILABLE if (subInfo != null) AVAILABLE else CONDITIONALLY_UNAVAILABLE
override fun displayPreference(screen: PreferenceScreen) { override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen) super.displayPreference(screen)
preference = screen.findPreference(preferenceKey)!! preference = screen.findPreference(preferenceKey)!!
policy?.let {
preference.setLimitInfo(it.getLimitInfo())
val dataBarSize = max(it.limitBytes, it.warningBytes)
if (dataBarSize > NetworkPolicy.WARNING_DISABLED) {
setDataBarSize(dataBarSize)
}
}
} }
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
viewLifecycleOwner.lifecycleScope.launch { networkPolicyRepository.networkPolicyFlow(networkTemplate)
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { .collectLatestWithLifecycle(viewLifecycleOwner) { policy ->
update() preference.isVisible = subInfo != null && policy != null
if (policy != null) update(policy)
} }
}
} }
private suspend fun update() { private suspend fun update(policy: NetworkPolicy) {
val policy = policy ?: return preference.setLimitInfo(policy.getLimitInfo())
val dataBarSize = max(policy.limitBytes, policy.warningBytes)
if (dataBarSize > NetworkPolicy.WARNING_DISABLED) {
setDataBarSize(dataBarSize)
}
val dataPlanInfo = withContext(Dispatchers.Default) { val dataPlanInfo = withContext(Dispatchers.Default) {
dataPlanRepositoryFactory(networkCycleDataRepository).getDataPlanInfo( dataPlanRepositoryFactory(networkCycleDataRepository).getDataPlanInfo(
policy = policy, policy = policy,

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2024 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.network.policy
import android.content.Context
import android.net.NetworkPolicy
import android.net.NetworkPolicyManager
import android.net.NetworkTemplate
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
class NetworkPolicyRepository(context: Context) {
private val networkPolicyManager = context.getSystemService(NetworkPolicyManager::class.java)!!
fun getNetworkPolicy(networkTemplate: NetworkTemplate): NetworkPolicy? =
networkPolicyManager.networkPolicies.find { policy -> policy.template == networkTemplate }
fun networkPolicyFlow(networkTemplate: NetworkTemplate): Flow<NetworkPolicy?> = flow {
emit(getNetworkPolicy(networkTemplate))
}.flowOn(Dispatchers.Default)
}

View File

@@ -32,8 +32,10 @@ import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILA
import com.android.settings.datausage.lib.INetworkCycleDataRepository import com.android.settings.datausage.lib.INetworkCycleDataRepository
import com.android.settings.datausage.lib.NetworkUsageData import com.android.settings.datausage.lib.NetworkUsageData
import com.android.settings.network.ProxySubscriptionManager import com.android.settings.network.ProxySubscriptionManager
import com.android.settings.network.policy.NetworkPolicyRepository
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@@ -41,6 +43,7 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.any import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.never import org.mockito.kotlin.never
@@ -69,6 +72,10 @@ class DataUsageSummaryPreferenceControllerTest {
on { get() } doReturn mockSubscriptionManager on { get() } doReturn mockSubscriptionManager
} }
private val mockNetworkPolicyRepository = mock<NetworkPolicyRepository> {
on { networkPolicyFlow(any()) } doAnswer { flowOf(policy) }
}
private val fakeNetworkCycleDataRepository = object : INetworkCycleDataRepository { private val fakeNetworkCycleDataRepository = object : INetworkCycleDataRepository {
override fun getCycles(): List<Range<Long>> = emptyList() override fun getCycles(): List<Range<Long>> = emptyList()
override fun getPolicy() = policy override fun getPolicy() = policy
@@ -86,6 +93,7 @@ class DataUsageSummaryPreferenceControllerTest {
context = context, context = context,
subId = SUB_ID, subId = SUB_ID,
proxySubscriptionManager = mockProxySubscriptionManager, proxySubscriptionManager = mockProxySubscriptionManager,
networkPolicyRepository = mockNetworkPolicyRepository,
networkCycleDataRepositoryFactory = { fakeNetworkCycleDataRepository }, networkCycleDataRepositoryFactory = { fakeNetworkCycleDataRepository },
dataPlanRepositoryFactory = { fakeDataPlanRepository }, dataPlanRepositoryFactory = { fakeDataPlanRepository },
) )
@@ -112,7 +120,7 @@ class DataUsageSummaryPreferenceControllerTest {
} }
@Test @Test
fun getAvailabilityStatus_hasSubInfoAndPolicy_available() { fun getAvailabilityStatus_hasSubInfo_available() {
mockProxySubscriptionManager.stub { mockProxySubscriptionManager.stub {
on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn SubscriptionInfo.Builder().build() on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn SubscriptionInfo.Builder().build()
} }
@@ -134,36 +142,43 @@ class DataUsageSummaryPreferenceControllerTest {
} }
@Test @Test
fun getAvailabilityStatus_noPolicy_conditionallyUnavailable() { fun onViewCreated_noPolicy_setInvisible() = runBlocking {
policy = null policy = null
controller.displayPreference(preferenceScreen)
clearInvocations(preference)
val availabilityStatus = controller.getAvailabilityStatus(SUB_ID) controller.onViewCreated(TestLifecycleOwner())
delay(100)
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) verify(preference).isVisible = false
} }
@Test @Test
fun displayPreference_policyHasNoLimitInfo() { fun onViewCreated_policyHasNoLimitInfo() = runBlocking {
policy = mock<NetworkPolicy>().apply { policy = mock<NetworkPolicy>().apply {
warningBytes = NetworkPolicy.WARNING_DISABLED warningBytes = NetworkPolicy.WARNING_DISABLED
limitBytes = NetworkPolicy.LIMIT_DISABLED limitBytes = NetworkPolicy.LIMIT_DISABLED
} }
controller.displayPreference(preferenceScreen) controller.displayPreference(preferenceScreen)
controller.onViewCreated(TestLifecycleOwner())
delay(100)
verify(preference).setLimitInfo(null) verify(preference).setLimitInfo(null)
verify(preference, never()).setLabels(any(), any()) verify(preference, never()).setLabels(any(), any())
} }
@Test @Test
fun displayPreference_policyWarningOnly() { fun onViewCreated_policyWarningOnly() = runBlocking {
policy = mock<NetworkPolicy>().apply { policy = mock<NetworkPolicy>().apply {
warningBytes = 1L warningBytes = 1L
limitBytes = NetworkPolicy.LIMIT_DISABLED limitBytes = NetworkPolicy.LIMIT_DISABLED
} }
controller.displayPreference(preferenceScreen) controller.displayPreference(preferenceScreen)
controller.onViewCreated(TestLifecycleOwner())
delay(100)
val limitInfo = argumentCaptor { val limitInfo = argumentCaptor {
verify(preference).setLimitInfo(capture()) verify(preference).setLimitInfo(capture())
}.firstValue.toString() }.firstValue.toString()
@@ -172,14 +187,16 @@ class DataUsageSummaryPreferenceControllerTest {
} }
@Test @Test
fun displayPreference_policyLimitOnly() { fun onViewCreated_policyLimitOnly() = runBlocking {
policy = mock<NetworkPolicy>().apply { policy = mock<NetworkPolicy>().apply {
warningBytes = NetworkPolicy.WARNING_DISABLED warningBytes = NetworkPolicy.WARNING_DISABLED
limitBytes = 1L limitBytes = 1L
} }
controller.displayPreference(preferenceScreen) controller.displayPreference(preferenceScreen)
controller.onViewCreated(TestLifecycleOwner())
delay(100)
val limitInfo = argumentCaptor { val limitInfo = argumentCaptor {
verify(preference).setLimitInfo(capture()) verify(preference).setLimitInfo(capture())
}.firstValue.toString() }.firstValue.toString()
@@ -188,14 +205,16 @@ class DataUsageSummaryPreferenceControllerTest {
} }
@Test @Test
fun displayPreference_policyHasWarningAndLimit() { fun onViewCreated_policyHasWarningAndLimit() = runBlocking {
policy = mock<NetworkPolicy>().apply { policy = mock<NetworkPolicy>().apply {
warningBytes = BillingCycleSettings.GIB_IN_BYTES / 2 warningBytes = BillingCycleSettings.GIB_IN_BYTES / 2
limitBytes = BillingCycleSettings.GIB_IN_BYTES limitBytes = BillingCycleSettings.GIB_IN_BYTES
} }
controller.displayPreference(preferenceScreen) controller.displayPreference(preferenceScreen)
controller.onViewCreated(TestLifecycleOwner())
delay(100)
val limitInfo = argumentCaptor { val limitInfo = argumentCaptor {
verify(preference).setLimitInfo(capture()) verify(preference).setLimitInfo(capture())
}.firstValue.toString() }.firstValue.toString()
@@ -207,7 +226,6 @@ class DataUsageSummaryPreferenceControllerTest {
fun onViewCreated_emptyDataPlanInfo() = runBlocking { fun onViewCreated_emptyDataPlanInfo() = runBlocking {
dataPlanInfo = EMPTY_DATA_PLAN_INFO dataPlanInfo = EMPTY_DATA_PLAN_INFO
controller.displayPreference(preferenceScreen) controller.displayPreference(preferenceScreen)
clearInvocations(preference)
controller.onViewCreated(TestLifecycleOwner()) controller.onViewCreated(TestLifecycleOwner())
delay(100) delay(100)
@@ -229,7 +247,6 @@ class DataUsageSummaryPreferenceControllerTest {
fun onViewCreated_positiveDataPlanInfo() = runBlocking { fun onViewCreated_positiveDataPlanInfo() = runBlocking {
dataPlanInfo = POSITIVE_DATA_PLAN_INFO dataPlanInfo = POSITIVE_DATA_PLAN_INFO
controller.displayPreference(preferenceScreen) controller.displayPreference(preferenceScreen)
clearInvocations(preference)
controller.onViewCreated(TestLifecycleOwner()) controller.onViewCreated(TestLifecycleOwner())
delay(100) delay(100)

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2024 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.network.policy
import android.content.Context
import android.net.NetworkPolicy
import android.net.NetworkPolicyManager
import android.net.NetworkTemplate
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
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
import org.mockito.kotlin.spy
@RunWith(AndroidJUnit4::class)
class NetworkPolicyRepositoryTest {
private val mockNetworkPolicyManager = mock<NetworkPolicyManager> {
on { networkPolicies } doReturn arrayOf(Policy1, Policy2)
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(NetworkPolicyManager::class.java) } doReturn mockNetworkPolicyManager
}
private val repository = NetworkPolicyRepository(context)
@Test
fun getNetworkPolicy() {
val networkPolicy = repository.getNetworkPolicy(Template1)
assertThat(networkPolicy).isSameInstanceAs(Policy1)
}
@Test
fun networkPolicyFlow() = runBlocking {
val networkPolicy = repository.networkPolicyFlow(Template2).firstWithTimeoutOrNull()
assertThat(networkPolicy).isSameInstanceAs(Policy2)
}
private companion object {
val Template1: NetworkTemplate =
NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE).build()
val Template2: NetworkTemplate = NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
val Policy1 = mock<NetworkPolicy>().apply {
template = Template1
}
val Policy2 = mock<NetworkPolicy>().apply {
template = Template2
}
}
}