From d01245e75b739ad488349baa372bec578cd921c6 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 11 Jul 2023 11:26:33 +0800 Subject: [PATCH] Convert DataUsageLib to Kotlin For better null safe. Also add unit tests. Fix: 290698845 Fix: 290684887 Test: unit test Test: manual - on Mobile Settings page Change-Id: I9c9edb9b7cbf7d688a885e5c36b61bd6dc526add --- .../settings/datausage/lib/DataUsageLib.java | 115 ------------ .../settings/datausage/lib/DataUsageLib.kt | 99 ++++++++++ .../datausage/lib/DataUsageLibTest.java | 116 ------------ .../datausage/lib/DataUsageLibTest.kt | 171 ++++++++++++++++++ 4 files changed, 270 insertions(+), 231 deletions(-) delete mode 100644 src/com/android/settings/datausage/lib/DataUsageLib.java create mode 100644 src/com/android/settings/datausage/lib/DataUsageLib.kt delete mode 100644 tests/robotests/src/com/android/settings/datausage/lib/DataUsageLibTest.java create mode 100644 tests/spa_unit/src/com/android/settings/datausage/lib/DataUsageLibTest.kt diff --git a/src/com/android/settings/datausage/lib/DataUsageLib.java b/src/com/android/settings/datausage/lib/DataUsageLib.java deleted file mode 100644 index b3f7e79bf46..00000000000 --- a/src/com/android/settings/datausage/lib/DataUsageLib.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2020 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.NetworkStats; -import android.net.NetworkTemplate; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.util.ArraySet; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.internal.util.ArrayUtils; - -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -/** - * Lib class for data usage - */ -public class DataUsageLib { - private static final String TAG = "DataUsageLib"; - - /** - * Return mobile NetworkTemplate based on {@code subId} - */ - public static NetworkTemplate getMobileTemplate(Context context, int subId) { - final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); - final int mobileDefaultSubId = telephonyManager.getSubscriptionId(); - - final SubscriptionManager subscriptionManager = - context.getSystemService(SubscriptionManager.class); - final List subInfoList = - subscriptionManager.getAvailableSubscriptionInfoList(); - if (subInfoList == null) { - Log.i(TAG, "Subscription is not inited: " + subId); - return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId); - } - - for (SubscriptionInfo subInfo : subInfoList) { - if ((subInfo != null) && (subInfo.getSubscriptionId() == subId)) { - return getNormalizedMobileTemplate(telephonyManager, subId); - } - } - Log.i(TAG, "Subscription is not active: " + subId); - return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId); - } - - private static NetworkTemplate getNormalizedMobileTemplate( - TelephonyManager telephonyManager, int subId) { - final NetworkTemplate mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId); - final String[] mergedSubscriberIds = telephonyManager - .createForSubscriptionId(subId).getMergedImsisFromGroup(); - if (ArrayUtils.isEmpty(mergedSubscriberIds)) { - Log.i(TAG, "mergedSubscriberIds is null."); - return mobileTemplate; - } - - return normalizeMobileTemplate(mobileTemplate, mergedSubscriberIds); - } - - private static NetworkTemplate normalizeMobileTemplate( - @NonNull NetworkTemplate template, @NonNull String[] merged) { - if (template.getSubscriberIds().isEmpty()) return template; - // The input template should have at most 1 subscriberId. - final String subscriberId = template.getSubscriberIds().iterator().next(); - // In some rare cases (e.g. b/243015487), merged subscriberId list might contain - // duplicated items. Deduplication for better error handling. - final ArraySet mergedSet = new ArraySet(merged); - if (mergedSet.size() != merged.length) { - Log.wtf(TAG, "Duplicated merged list detected: " + Arrays.toString(merged)); - } - if (mergedSet.contains(subscriberId)) { - // Requested template subscriber is part of the merge group; return - // a template that matches all merged subscribers. - return new NetworkTemplate.Builder(template.getMatchRule()) - .setSubscriberIds(mergedSet) - .setMeteredness(template.getMeteredness()).build(); - } - - return template; - } - - public static NetworkTemplate getMobileTemplateForSubId( - TelephonyManager telephonyManager, int subId) { - // Create template that matches any mobile network when the subscriberId is null. - String subscriberId = telephonyManager.getSubscriberId(subId); - return subscriberId != null - ? new NetworkTemplate.Builder(NetworkTemplate.MATCH_CARRIER) - .setSubscriberIds(Set.of(subscriberId)) - .setMeteredness(NetworkStats.METERED_YES) - .build() - : new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE) - .setMeteredness(NetworkStats.METERED_YES) - .build(); - } -} diff --git a/src/com/android/settings/datausage/lib/DataUsageLib.kt b/src/com/android/settings/datausage/lib/DataUsageLib.kt new file mode 100644 index 00000000000..9e94abaf7be --- /dev/null +++ b/src/com/android/settings/datausage/lib/DataUsageLib.kt @@ -0,0 +1,99 @@ +/* + * 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.NetworkStats +import android.net.NetworkTemplate +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.util.Log + +/** + * Lib class for data usage + */ +object DataUsageLib { + private const val TAG = "DataUsageLib" + + /** + * Return mobile NetworkTemplate based on `subId` + */ + @JvmStatic + fun getMobileTemplate(context: Context, subId: Int): NetworkTemplate { + val telephonyManager = context.getSystemService(TelephonyManager::class.java)!! + val mobileDefaultSubId = telephonyManager.subscriptionId + val subscriptionManager = context.getSystemService(SubscriptionManager::class.java)!! + val subInfoList = subscriptionManager.availableSubscriptionInfoList + if (subInfoList == null) { + Log.i(TAG, "Subscription is not inited: $subId") + return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId) + } + for (subInfo in subInfoList) { + if (subInfo?.subscriptionId == subId) { + return getNormalizedMobileTemplate(telephonyManager, subId) + } + } + Log.i(TAG, "Subscription is not active: $subId") + return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId) + } + + private fun getNormalizedMobileTemplate( + telephonyManager: TelephonyManager, + subId: Int, + ): NetworkTemplate { + val mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId) + val mergedSubscriberIds = + telephonyManager.createForSubscriptionId(subId).mergedImsisFromGroup + if (mergedSubscriberIds.isNullOrEmpty()) { + Log.i(TAG, "mergedSubscriberIds is empty.") + return mobileTemplate + } + return normalizeMobileTemplate(mobileTemplate, mergedSubscriberIds) + } + + private fun normalizeMobileTemplate( + template: NetworkTemplate, + merged: Array, + ): NetworkTemplate { + val subscriberId = template.subscriberIds.firstOrNull() ?: return template + // In some rare cases (e.g. b/243015487), merged subscriberId list might contain + // duplicated items. Deduplication for better error handling. + val mergedSet = merged.toSet() + if (mergedSet.size != merged.size) { + Log.wtf(TAG, "Duplicated merged list detected: " + merged.contentToString()) + } + return if (mergedSet.contains(subscriberId)) { + // Requested template subscriber is part of the merge group; return + // a template that matches all merged subscribers. + NetworkTemplate.Builder(template.matchRule) + .setSubscriberIds(mergedSet) + .setMeteredness(template.meteredness) + .build() + } else template + } + + @JvmStatic + fun getMobileTemplateForSubId(telephonyManager: TelephonyManager, subId: Int): NetworkTemplate { + // Create template that matches any mobile network when the subscriberId is null. + val subscriberId = telephonyManager.getSubscriberId(subId) + return when (subscriberId) { + null -> NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE) + else -> NetworkTemplate.Builder(NetworkTemplate.MATCH_CARRIER) + .setSubscriberIds(setOf(subscriberId)) + }.setMeteredness(NetworkStats.METERED_YES).build() + } +} diff --git a/tests/robotests/src/com/android/settings/datausage/lib/DataUsageLibTest.java b/tests/robotests/src/com/android/settings/datausage/lib/DataUsageLibTest.java deleted file mode 100644 index fccef4f1465..00000000000 --- a/tests/robotests/src/com/android/settings/datausage/lib/DataUsageLibTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2020 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 static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.net.NetworkTemplate; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -public class DataUsageLibTest { - - private static final int SUB_ID = 1; - private static final int SUB_ID_2 = 2; - private static final String SUBSCRIBER_ID = "Test Subscriber"; - private static final String SUBSCRIBER_ID_2 = "Test Subscriber 2"; - - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private SubscriptionManager mSubscriptionManager; - @Mock - private SubscriptionInfo mInfo1; - @Mock - private SubscriptionInfo mInfo2; - @Mock - private ParcelUuid mParcelUuid; - private Context mContext; - private List mInfos; - - @Before - public void setUp() throws RemoteException { - MockitoAnnotations.initMocks(this); - - mContext = spy(RuntimeEnvironment.application); - when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); - when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); - when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID); - when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2); - when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); - when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true); - } - - @Test - @Ignore - public void getMobileTemplate_infoNull_returnMobileAll() { - when(mSubscriptionManager.isActiveSubscriptionId(SUB_ID)).thenReturn(false); - - final NetworkTemplate networkTemplate = DataUsageLib.getMobileTemplate(mContext, SUB_ID); - assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID)).isTrue(); - assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID_2)).isFalse(); - } - - @Test - @Ignore - public void getMobileTemplate_groupUuidNull_returnMobileAll() { - when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1); - when(mInfo1.getGroupUuid()).thenReturn(null); - when(mTelephonyManager.getMergedImsisFromGroup()) - .thenReturn(new String[] {SUBSCRIBER_ID}); - - final NetworkTemplate networkTemplate = DataUsageLib.getMobileTemplate(mContext, SUB_ID); - assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID)).isTrue(); - assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID_2)).isFalse(); - } - - @Test - @Ignore - public void getMobileTemplate_groupUuidExist_returnMobileMerged() { - when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1); - when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid); - // In some rare cases (e.g. b/243015487), merged subscriberId list might contain - // duplicated items. The implementation should perform deduplication. - when(mTelephonyManager.getMergedImsisFromGroup()) - .thenReturn(new String[] {SUBSCRIBER_ID, SUBSCRIBER_ID, SUBSCRIBER_ID_2}); - - final NetworkTemplate networkTemplate = DataUsageLib.getMobileTemplate(mContext, SUB_ID); - assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID)).isTrue(); - assertThat(networkTemplate.getSubscriberIds().contains(SUBSCRIBER_ID_2)).isTrue(); - assertThat(networkTemplate.getSubscriberIds().size() == 2).isTrue(); - } -} diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/DataUsageLibTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/DataUsageLibTest.kt new file mode 100644 index 00000000000..5922facd96b --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/datausage/lib/DataUsageLibTest.kt @@ -0,0 +1,171 @@ +/* + * 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.NetworkStats +import android.net.NetworkTemplate +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidJUnit4::class) +class DataUsageLibTest { + + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var telephonyManager: TelephonyManager + + @Mock + private lateinit var subscriptionManager: SubscriptionManager + + @Before + fun setUp() { + whenever(context.getSystemService(TelephonyManager::class.java)) + .thenReturn(telephonyManager) + whenever(context.getSystemService(SubscriptionManager::class.java)) + .thenReturn(subscriptionManager) + + whenever(telephonyManager.subscriptionId).thenReturn(DEFAULT_SUB_ID) + whenever(telephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID) + whenever(telephonyManager.getSubscriberId(DEFAULT_SUB_ID)).thenReturn(DEFAULT_SUBSCRIBER_ID) + whenever(telephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(telephonyManager) + } + + @Test + fun getMobileTemplate_availableSubscriptionInfoListIsNull_returnDefaultSub() { + whenever(subscriptionManager.availableSubscriptionInfoList).thenReturn(null) + + val mobileTemplate = DataUsageLib.getMobileTemplate(context, SUB_ID) + + assertThat(mobileTemplate.matchRule).isEqualTo(NetworkTemplate.MATCH_CARRIER) + assertThat(mobileTemplate.subscriberIds).containsExactly(DEFAULT_SUBSCRIBER_ID) + assertThat(mobileTemplate.meteredness).isEqualTo(NetworkStats.METERED_YES) + } + + @Test + fun getMobileTemplate_subscriptionNotActive_returnDefaultSub() { + whenever(subscriptionManager.availableSubscriptionInfoList).thenReturn(listOf(null)) + + val mobileTemplate = DataUsageLib.getMobileTemplate(context, SUB_ID) + + assertThat(mobileTemplate.matchRule).isEqualTo(NetworkTemplate.MATCH_CARRIER) + assertThat(mobileTemplate.subscriberIds).containsExactly(DEFAULT_SUBSCRIBER_ID) + assertThat(mobileTemplate.meteredness).isEqualTo(NetworkStats.METERED_YES) + } + + @Test + fun getMobileTemplate_mergedImsisFromGroupNull_returnRequestedSub() { + whenever(subscriptionManager.availableSubscriptionInfoList) + .thenReturn(listOf(SUBSCRIBER_INFO)) + whenever(telephonyManager.mergedImsisFromGroup).thenReturn(null) + + val mobileTemplate = DataUsageLib.getMobileTemplate(context, SUB_ID) + + assertThat(mobileTemplate.matchRule).isEqualTo(NetworkTemplate.MATCH_CARRIER) + assertThat(mobileTemplate.subscriberIds).containsExactly(SUBSCRIBER_ID) + assertThat(mobileTemplate.meteredness).isEqualTo(NetworkStats.METERED_YES) + } + + @Test + fun getMobileTemplate_mergedImsisFromGroupEmpty_returnRequestedSub() { + whenever(subscriptionManager.availableSubscriptionInfoList) + .thenReturn(listOf(SUBSCRIBER_INFO)) + whenever(telephonyManager.mergedImsisFromGroup).thenReturn(emptyArray()) + + val mobileTemplate = DataUsageLib.getMobileTemplate(context, SUB_ID) + + assertThat(mobileTemplate.matchRule).isEqualTo(NetworkTemplate.MATCH_CARRIER) + assertThat(mobileTemplate.subscriberIds).containsExactly(SUBSCRIBER_ID) + assertThat(mobileTemplate.meteredness).isEqualTo(NetworkStats.METERED_YES) + } + + @Test + fun getMobileTemplate_mergedImsisFromGroupNotContainSub_returnRequestedSub() { + whenever(subscriptionManager.availableSubscriptionInfoList) + .thenReturn(listOf(SUBSCRIBER_INFO)) + whenever(telephonyManager.mergedImsisFromGroup).thenReturn(arrayOf(DEFAULT_SUBSCRIBER_ID)) + + val mobileTemplate = DataUsageLib.getMobileTemplate(context, SUB_ID) + + assertThat(mobileTemplate.matchRule).isEqualTo(NetworkTemplate.MATCH_CARRIER) + assertThat(mobileTemplate.subscriberIds).containsExactly(SUBSCRIBER_ID) + assertThat(mobileTemplate.meteredness).isEqualTo(NetworkStats.METERED_YES) + } + + @Test + fun getMobileTemplate_mergedImsisFromGroupContainSub_returnRequestedSub() { + whenever(subscriptionManager.availableSubscriptionInfoList) + .thenReturn(listOf(SUBSCRIBER_INFO)) + whenever(telephonyManager.mergedImsisFromGroup) + .thenReturn(arrayOf(DEFAULT_SUBSCRIBER_ID, SUBSCRIBER_ID)) + + val mobileTemplate = DataUsageLib.getMobileTemplate(context, SUB_ID) + + assertThat(mobileTemplate.matchRule).isEqualTo(NetworkTemplate.MATCH_CARRIER) + assertThat(mobileTemplate.subscriberIds) + .containsExactly(SUBSCRIBER_ID, DEFAULT_SUBSCRIBER_ID) + assertThat(mobileTemplate.meteredness).isEqualTo(NetworkStats.METERED_YES) + } + + @Test + fun getMobileTemplateForSubId_subscriberIdNotNull() { + whenever(telephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID) + + val mobileTemplate = DataUsageLib.getMobileTemplateForSubId(telephonyManager, SUB_ID) + + assertThat(mobileTemplate.matchRule).isEqualTo(NetworkTemplate.MATCH_CARRIER) + assertThat(mobileTemplate.subscriberIds).containsExactly(SUBSCRIBER_ID) + assertThat(mobileTemplate.meteredness).isEqualTo(NetworkStats.METERED_YES) + } + + @Test + fun getMobileTemplateForSubId_subscriberIdIsNull() { + whenever(telephonyManager.getSubscriberId(SUB_ID)).thenReturn(null) + + val mobileTemplate = DataUsageLib.getMobileTemplateForSubId(telephonyManager, SUB_ID) + + assertThat(mobileTemplate.matchRule).isEqualTo(NetworkTemplate.MATCH_MOBILE) + assertThat(mobileTemplate.subscriberIds).isEmpty() + assertThat(mobileTemplate.meteredness).isEqualTo(NetworkStats.METERED_YES) + } + + private companion object { + const val DEFAULT_SUB_ID = 0 + const val SUB_ID = 1 + const val DEFAULT_SUBSCRIBER_ID = "Default Test Subscriber" + const val SUBSCRIBER_ID = "Test Subscriber" + val SUBSCRIBER_INFO: SubscriptionInfo = SubscriptionInfo.Builder().setId(SUB_ID).build() + } +} \ No newline at end of file