/* * 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.datausage import android.content.Context import android.net.NetworkPolicy import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionPlan import android.telephony.TelephonyManager import android.util.Range import androidx.lifecycle.testing.TestLifecycleOwner import androidx.preference.PreferenceManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.core.BasePreferenceController.AVAILABLE import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE 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 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 import org.mockito.kotlin.spy import org.mockito.kotlin.stub import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class DataUsageSummaryPreferenceControllerTest { private var policy: NetworkPolicy? = mock() private val mockTelephonyManager = mock { on { isDataCapable } doReturn true } private val context: Context = spy(ApplicationProvider.getApplicationContext()) { on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager } private val mockSubscriptionManager = mock { on { getSubscriptionPlans(any()) } doReturn emptyList() } private val mockProxySubscriptionManager = mock { 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 override fun queryUsage(range: Range) = NetworkUsageData.AllZero } private var dataPlanInfo = EMPTY_DATA_PLAN_INFO private val fakeDataPlanRepository = object : DataPlanRepository { override fun getDataPlanInfo(policy: NetworkPolicy, plans: List) = dataPlanInfo } private val controller = DataUsageSummaryPreferenceController( context = context, subId = SUB_ID, proxySubscriptionManager = mockProxySubscriptionManager, networkPolicyRepository = mockNetworkPolicyRepository, networkCycleDataRepositoryFactory = { fakeNetworkCycleDataRepository }, dataPlanRepositoryFactory = { fakeDataPlanRepository }, ) private val preference = mock { on { key } doReturn controller.preferenceKey } private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context) @Before fun setUp() { preferenceScreen.addPreference(preference) } @Test fun getAvailabilityStatus_noMobileData_conditionallyUnavailable() { mockTelephonyManager.stub { on { isDataCapable } doReturn false } val availabilityStatus = controller.getAvailabilityStatus(SUB_ID) assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) } @Test fun getAvailabilityStatus_hasSubInfo_available() { mockProxySubscriptionManager.stub { on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn SubscriptionInfo.Builder().build() } val availabilityStatus = controller.getAvailabilityStatus(SUB_ID) assertThat(availabilityStatus).isEqualTo(AVAILABLE) } @Test fun getAvailabilityStatus_noSubInfo_conditionallyUnavailable() { mockProxySubscriptionManager.stub { on { getAccessibleSubscriptionInfo(SUB_ID) } doReturn null } val availabilityStatus = controller.getAvailabilityStatus(SUB_ID) assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) } @Test fun onViewCreated_noPolicy_setInvisible() = runBlocking { policy = null controller.displayPreference(preferenceScreen) clearInvocations(preference) controller.onViewCreated(TestLifecycleOwner()) delay(100) verify(preference).isVisible = false } @Test 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 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() assertThat(limitInfo).isEqualTo("1 B data warning") verify(preference).setLabels("0 B", "1 B") } @Test 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() assertThat(limitInfo).isEqualTo("1 B data limit") verify(preference).setLabels("0 B", "1 B") } @Test 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() assertThat(limitInfo).isEqualTo("512 MB data warning / 1.00 GB data limit") verify(preference).setLabels("0 B", "1.00 GB") } @Test fun onViewCreated_emptyDataPlanInfo() = runBlocking { dataPlanInfo = EMPTY_DATA_PLAN_INFO controller.displayPreference(preferenceScreen) controller.onViewCreated(TestLifecycleOwner()) delay(100) verify(preference).setUsageNumbers( EMPTY_DATA_PLAN_INFO.dataPlanUse, EMPTY_DATA_PLAN_INFO.dataPlanSize, ) verify(preference).setChartEnabled(false) verify(preference).setUsageInfo( EMPTY_DATA_PLAN_INFO.cycleEnd, EMPTY_DATA_PLAN_INFO.snapshotTime, null, EMPTY_DATA_PLAN_INFO.dataPlanCount, ) } @Test fun onViewCreated_positiveDataPlanInfo() = runBlocking { dataPlanInfo = POSITIVE_DATA_PLAN_INFO controller.displayPreference(preferenceScreen) controller.onViewCreated(TestLifecycleOwner()) delay(100) verify(preference).setUsageNumbers( POSITIVE_DATA_PLAN_INFO.dataPlanUse, POSITIVE_DATA_PLAN_INFO.dataPlanSize, ) verify(preference).setChartEnabled(true) verify(preference).setLabels("0 B", "9 B") val progress = argumentCaptor { verify(preference).setProgress(capture()) }.firstValue assertThat(progress).isEqualTo(0.8888889f) verify(preference).setUsageInfo( POSITIVE_DATA_PLAN_INFO.cycleEnd, POSITIVE_DATA_PLAN_INFO.snapshotTime, null, POSITIVE_DATA_PLAN_INFO.dataPlanCount, ) } private companion object { const val SUB_ID = 1234 val EMPTY_DATA_PLAN_INFO = DataPlanInfo( dataPlanCount = 0, dataPlanSize = SubscriptionPlan.BYTES_UNKNOWN, dataBarSize = SubscriptionPlan.BYTES_UNKNOWN, dataPlanUse = 0, cycleEnd = null, snapshotTime = SubscriptionPlan.TIME_UNKNOWN, ) val POSITIVE_DATA_PLAN_INFO = DataPlanInfo( dataPlanCount = 0, dataPlanSize = 10L, dataBarSize = 9L, dataPlanUse = 8L, cycleEnd = 7L, snapshotTime = 6L, ) } }