From 513ca3a8086be211cdca10784ce7000e5b41c9ea Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 23 May 2024 14:20:10 +0800 Subject: [PATCH] Refresh DataUsageSummaryPreferenceController When re-enter the page contains it. Fix: 341234382 Test: manual - on Data usage Test: unit test Change-Id: Ib6a4624e11b60d703c35cea07232cc24f1516389 --- .../DataUsageSummaryPreferenceController.kt | 39 +++++----- .../network/policy/NetworkPolicyRepository.kt | 37 ++++++++++ ...ataUsageSummaryPreferenceControllerTest.kt | 45 ++++++++---- .../policy/NetworkPolicyRepositoryTest.kt | 72 +++++++++++++++++++ 4 files changed, 159 insertions(+), 34 deletions(-) create mode 100644 src/com/android/settings/network/policy/NetworkPolicyRepository.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/policy/NetworkPolicyRepositoryTest.kt diff --git a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.kt b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.kt index 8b31f6755c1..6f62c300a84 100644 --- a/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.kt +++ b/src/com/android/settings/datausage/DataUsageSummaryPreferenceController.kt @@ -21,20 +21,18 @@ import android.net.NetworkPolicy import android.net.NetworkTemplate import android.text.TextUtils import android.util.Log -import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settings.datausage.lib.DataUsageLib.getMobileTemplate import com.android.settings.datausage.lib.INetworkCycleDataRepository import com.android.settings.datausage.lib.NetworkCycleDataRepository import com.android.settings.network.ProxySubscriptionManager +import com.android.settings.network.policy.NetworkPolicyRepository import com.android.settings.network.telephony.TelephonyBasePreferenceController +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlin.math.max import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** @@ -47,6 +45,7 @@ open class DataUsageSummaryPreferenceController @JvmOverloads constructor( subId: Int, private val proxySubscriptionManager: ProxySubscriptionManager = ProxySubscriptionManager.getInstance(context), + private val networkPolicyRepository: NetworkPolicyRepository = NetworkPolicyRepository(context), private val networkCycleDataRepositoryFactory: ( template: NetworkTemplate, ) -> INetworkCycleDataRepository = { NetworkCycleDataRepository(context, it) }, @@ -64,37 +63,37 @@ open class DataUsageSummaryPreferenceController @JvmOverloads constructor( proxySubscriptionManager.getAccessibleSubscriptionInfo(mSubId) } else null } + + private val networkTemplate by lazy { getMobileTemplate(mContext, mSubId) } + private val networkCycleDataRepository by lazy { - networkCycleDataRepositoryFactory(getMobileTemplate(mContext, mSubId)) + networkCycleDataRepositoryFactory(networkTemplate) } - private val policy by lazy { networkCycleDataRepository.getPolicy() } + private lateinit var preference: DataUsageSummaryPreference 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) { super.displayPreference(screen) 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) { - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - update() + networkPolicyRepository.networkPolicyFlow(networkTemplate) + .collectLatestWithLifecycle(viewLifecycleOwner) { policy -> + preference.isVisible = subInfo != null && policy != null + if (policy != null) update(policy) } - } } - private suspend fun update() { - val policy = policy ?: return + private suspend fun update(policy: NetworkPolicy) { + preference.setLimitInfo(policy.getLimitInfo()) + val dataBarSize = max(policy.limitBytes, policy.warningBytes) + if (dataBarSize > NetworkPolicy.WARNING_DISABLED) { + setDataBarSize(dataBarSize) + } val dataPlanInfo = withContext(Dispatchers.Default) { dataPlanRepositoryFactory(networkCycleDataRepository).getDataPlanInfo( policy = policy, diff --git a/src/com/android/settings/network/policy/NetworkPolicyRepository.kt b/src/com/android/settings/network/policy/NetworkPolicyRepository.kt new file mode 100644 index 00000000000..a0cab141129 --- /dev/null +++ b/src/com/android/settings/network/policy/NetworkPolicyRepository.kt @@ -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 = flow { + emit(getNetworkPolicy(networkTemplate)) + }.flowOn(Dispatchers.Default) +} diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.kt index 8ebb9c530ed..35a1a235bbe 100644 --- a/tests/spa_unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.kt @@ -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.NetworkUsageData import com.android.settings.network.ProxySubscriptionManager +import com.android.settings.network.policy.NetworkPolicyRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test @@ -41,6 +43,7 @@ import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -69,6 +72,10 @@ class DataUsageSummaryPreferenceControllerTest { on { get() } doReturn mockSubscriptionManager } + private val mockNetworkPolicyRepository = mock { + on { networkPolicyFlow(any()) } doAnswer { flowOf(policy) } + } + private val fakeNetworkCycleDataRepository = object : INetworkCycleDataRepository { override fun getCycles(): List> = emptyList() override fun getPolicy() = policy @@ -86,6 +93,7 @@ class DataUsageSummaryPreferenceControllerTest { context = context, subId = SUB_ID, proxySubscriptionManager = mockProxySubscriptionManager, + networkPolicyRepository = mockNetworkPolicyRepository, networkCycleDataRepositoryFactory = { fakeNetworkCycleDataRepository }, dataPlanRepositoryFactory = { fakeDataPlanRepository }, ) @@ -112,7 +120,7 @@ class DataUsageSummaryPreferenceControllerTest { } @Test - fun getAvailabilityStatus_hasSubInfoAndPolicy_available() { + fun getAvailabilityStatus_hasSubInfo_available() { mockProxySubscriptionManager.stub { on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn SubscriptionInfo.Builder().build() } @@ -134,36 +142,43 @@ class DataUsageSummaryPreferenceControllerTest { } @Test - fun getAvailabilityStatus_noPolicy_conditionallyUnavailable() { + fun onViewCreated_noPolicy_setInvisible() = runBlocking { 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 - fun displayPreference_policyHasNoLimitInfo() { + fun onViewCreated_policyHasNoLimitInfo() = runBlocking { policy = mock().apply { warningBytes = NetworkPolicy.WARNING_DISABLED limitBytes = NetworkPolicy.LIMIT_DISABLED } - controller.displayPreference(preferenceScreen) + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + verify(preference).setLimitInfo(null) verify(preference, never()).setLabels(any(), any()) } @Test - fun displayPreference_policyWarningOnly() { + fun onViewCreated_policyWarningOnly() = runBlocking { policy = mock().apply { warningBytes = 1L limitBytes = NetworkPolicy.LIMIT_DISABLED } - controller.displayPreference(preferenceScreen) + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + val limitInfo = argumentCaptor { verify(preference).setLimitInfo(capture()) }.firstValue.toString() @@ -172,14 +187,16 @@ class DataUsageSummaryPreferenceControllerTest { } @Test - fun displayPreference_policyLimitOnly() { + fun onViewCreated_policyLimitOnly() = runBlocking { policy = mock().apply { warningBytes = NetworkPolicy.WARNING_DISABLED limitBytes = 1L } - controller.displayPreference(preferenceScreen) + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + val limitInfo = argumentCaptor { verify(preference).setLimitInfo(capture()) }.firstValue.toString() @@ -188,14 +205,16 @@ class DataUsageSummaryPreferenceControllerTest { } @Test - fun displayPreference_policyHasWarningAndLimit() { + fun onViewCreated_policyHasWarningAndLimit() = runBlocking { policy = mock().apply { warningBytes = BillingCycleSettings.GIB_IN_BYTES / 2 limitBytes = BillingCycleSettings.GIB_IN_BYTES } - controller.displayPreference(preferenceScreen) + controller.onViewCreated(TestLifecycleOwner()) + delay(100) + val limitInfo = argumentCaptor { verify(preference).setLimitInfo(capture()) }.firstValue.toString() @@ -207,7 +226,6 @@ class DataUsageSummaryPreferenceControllerTest { fun onViewCreated_emptyDataPlanInfo() = runBlocking { dataPlanInfo = EMPTY_DATA_PLAN_INFO controller.displayPreference(preferenceScreen) - clearInvocations(preference) controller.onViewCreated(TestLifecycleOwner()) delay(100) @@ -229,7 +247,6 @@ class DataUsageSummaryPreferenceControllerTest { fun onViewCreated_positiveDataPlanInfo() = runBlocking { dataPlanInfo = POSITIVE_DATA_PLAN_INFO controller.displayPreference(preferenceScreen) - clearInvocations(preference) controller.onViewCreated(TestLifecycleOwner()) delay(100) diff --git a/tests/spa_unit/src/com/android/settings/network/policy/NetworkPolicyRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/policy/NetworkPolicyRepositoryTest.kt new file mode 100644 index 00000000000..4e782cc8f4d --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/policy/NetworkPolicyRepositoryTest.kt @@ -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 { + 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().apply { + template = Template1 + } + val Policy2 = mock().apply { + template = Template2 + } + } +}