Loading src/com/android/settings/network/SubscriptionUtil.java +92 −0 Original line number Diff line number Diff line Loading @@ -28,19 +28,25 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccSlotInfo; import android.text.TextUtils; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity; import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity; import com.android.settingslib.DeviceInfoUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; public class SubscriptionUtil { private static final String TAG = "SubscriptionUtil"; Loading Loading @@ -214,6 +220,92 @@ public class SubscriptionUtil { return null; } /** * Return a mapping of active subscription ids to diaplay names. Each display name is * guaranteed to be unique in the following manner: * 1) If the original display name is not unique, the last four digits of the phone number * will be appended. * 2) If the phone number is not visible or the last four digits are shared with another * subscription, the subscription id will be appended to the original display name. * More details can be found at go/unique-sub-display-names. * * @return map of active subscription ids to diaplay names. */ @VisibleForTesting public static Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) { class DisplayInfo { public SubscriptionInfo subscriptionInfo; public CharSequence originalName; public CharSequence uniqueName; } final SubscriptionManager subscriptionManager = context.getSystemService(SubscriptionManager.class); // Map of SubscriptionId to DisplayName final Supplier<Stream<DisplayInfo>> originalInfos = () -> getActiveSubscriptions(subscriptionManager) .stream() .map(i -> { DisplayInfo info = new DisplayInfo(); info.subscriptionInfo = i; info.originalName = i.getDisplayName(); return info; }); // TODO(goldmanj) consider using a map of DisplayName to SubscriptionInfos. // A Unique set of display names Set<CharSequence> uniqueNames = new HashSet<>(); // Return the set of duplicate names final Set<CharSequence> duplicateOriginalNames = originalInfos.get() .filter(info -> !uniqueNames.add(info.originalName)) .map(info -> info.originalName) .collect(Collectors.toSet()); // If a display name is duplicate, append the final 4 digits of the phone number. // Creates a mapping of Subscription id to original display name + phone number display name final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> { if (duplicateOriginalNames.contains(info.originalName)) { // This may return null, if the user cannot view the phone number itself. final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context, info.subscriptionInfo); String lastFourDigits = ""; if (phoneNumber != null) { lastFourDigits = (phoneNumber.length() > 4) ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber; } if (TextUtils.isEmpty(lastFourDigits)) { info.uniqueName = info.originalName; } else { info.uniqueName = info.originalName + " " + lastFourDigits; } } else { info.uniqueName = info.originalName; } return info; }); // Check uniqueness a second time. // We might not have had permission to view the phone numbers. // There might also be multiple phone numbers whose last 4 digits the same. uniqueNames.clear(); final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get() .filter(info -> !uniqueNames.add(info.uniqueName)) .map(info -> info.uniqueName) .collect(Collectors.toSet()); return uniqueInfos.get().map(info -> { if (duplicatePhoneNames.contains(info.uniqueName)) { info.uniqueName = info.originalName + " " + info.subscriptionInfo.getSubscriptionId(); } return info; }).collect(Collectors.toMap( info -> info.subscriptionInfo.getSubscriptionId(), info -> info.uniqueName)); } public static String getDisplayName(SubscriptionInfo info) { final CharSequence name = info.getDisplayName(); if (name != null) { Loading tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java +132 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settings.network; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; Loading @@ -38,9 +39,15 @@ import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.List; import java.util.Map; @RunWith(AndroidJUnit4.class) public class SubscriptionUtilTest { private static final int SUBID_1 = 1; private static final int SUBID_2 = 2; private static final int SUBID_3 = 3; private static final CharSequence CARRIER_1 = "carrier1"; private static final CharSequence CARRIER_2 = "carrier2"; private Context mContext; @Mock Loading Loading @@ -125,6 +132,131 @@ public class SubscriptionUtilTest { assertThat(subs).hasSize(2); } @Test public void getUniqueDisplayNames_uniqueCarriers_originalUsed() { // Each subscription's default display name is unique. final SubscriptionInfo info1 = mock(SubscriptionInfo.class); final SubscriptionInfo info2 = mock(SubscriptionInfo.class); when(info1.getSubscriptionId()).thenReturn(SUBID_1); when(info2.getSubscriptionId()).thenReturn(SUBID_2); when(info1.getDisplayName()).thenReturn(CARRIER_1); when(info2.getDisplayName()).thenReturn(CARRIER_2); when(mSubMgr.getActiveSubscriptionInfoList()).thenReturn( Arrays.asList(info1, info2)); // Each subscription has a unique last 4 digits of the phone number. TelephonyManager sub1Telmgr = mock(TelephonyManager.class); TelephonyManager sub2Telmgr = mock(TelephonyManager.class); when(sub1Telmgr.getLine1Number()).thenReturn("1112223333"); when(sub2Telmgr.getLine1Number()).thenReturn("2223334444"); when(mTelMgr.createForSubscriptionId(SUBID_1)).thenReturn(sub1Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_2)).thenReturn(sub2Telmgr); final Map<Integer, CharSequence> idNames = SubscriptionUtil.getUniqueSubscriptionDisplayNames(mContext); assertThat(idNames).isNotNull(); assertThat(idNames).hasSize(2); assertEquals(CARRIER_1, idNames.get(SUBID_1)); assertEquals(CARRIER_2, idNames.get(SUBID_2)); } @Test public void getUniqueDisplayNames_identicalCarriers_fourDigitsUsed() { // Both subscriptoins have the same display name. final SubscriptionInfo info1 = mock(SubscriptionInfo.class); final SubscriptionInfo info2 = mock(SubscriptionInfo.class); when(info1.getSubscriptionId()).thenReturn(SUBID_1); when(info2.getSubscriptionId()).thenReturn(SUBID_2); when(info1.getDisplayName()).thenReturn(CARRIER_1); when(info2.getDisplayName()).thenReturn(CARRIER_1); when(mSubMgr.getActiveSubscriptionInfoList()).thenReturn( Arrays.asList(info1, info2)); // Each subscription has a unique last 4 digits of the phone number. TelephonyManager sub1Telmgr = mock(TelephonyManager.class); TelephonyManager sub2Telmgr = mock(TelephonyManager.class); when(sub1Telmgr.getLine1Number()).thenReturn("1112223333"); when(sub2Telmgr.getLine1Number()).thenReturn("2223334444"); when(mTelMgr.createForSubscriptionId(SUBID_1)).thenReturn(sub1Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_2)).thenReturn(sub2Telmgr); final Map<Integer, CharSequence> idNames = SubscriptionUtil.getUniqueSubscriptionDisplayNames(mContext); assertThat(idNames).isNotNull(); assertThat(idNames).hasSize(2); assertEquals(CARRIER_1 + " 3333", idNames.get(SUBID_1)); assertEquals(CARRIER_1 + " 4444", idNames.get(SUBID_2)); } @Test public void getUniqueDisplayNames_phoneNumberBlocked_subscriptoinIdFallback() { // Both subscriptoins have the same display name. final SubscriptionInfo info1 = mock(SubscriptionInfo.class); final SubscriptionInfo info2 = mock(SubscriptionInfo.class); when(info1.getSubscriptionId()).thenReturn(SUBID_1); when(info2.getSubscriptionId()).thenReturn(SUBID_2); when(info1.getDisplayName()).thenReturn(CARRIER_1); when(info2.getDisplayName()).thenReturn(CARRIER_1); when(mSubMgr.getActiveSubscriptionInfoList()).thenReturn( Arrays.asList(info1, info2)); // The subscriptions' phone numbers cannot be revealed to the user. TelephonyManager sub1Telmgr = mock(TelephonyManager.class); TelephonyManager sub2Telmgr = mock(TelephonyManager.class); when(sub1Telmgr.getLine1Number()).thenReturn(""); when(sub2Telmgr.getLine1Number()).thenReturn(""); when(mTelMgr.createForSubscriptionId(SUBID_1)).thenReturn(sub1Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_2)).thenReturn(sub2Telmgr); final Map<Integer, CharSequence> idNames = SubscriptionUtil.getUniqueSubscriptionDisplayNames(mContext); assertThat(idNames).isNotNull(); assertThat(idNames).hasSize(2); assertEquals(CARRIER_1 + " 1", idNames.get(SUBID_1)); assertEquals(CARRIER_1 + " 2", idNames.get(SUBID_2)); } @Test public void getUniqueDisplayNames_phoneNumberIdentical_subscriptoinIdFallback() { // TODO have three here from the same carrier // Both subscriptoins have the same display name. final SubscriptionInfo info1 = mock(SubscriptionInfo.class); final SubscriptionInfo info2 = mock(SubscriptionInfo.class); final SubscriptionInfo info3 = mock(SubscriptionInfo.class); when(info1.getSubscriptionId()).thenReturn(SUBID_1); when(info2.getSubscriptionId()).thenReturn(SUBID_2); when(info3.getSubscriptionId()).thenReturn(SUBID_3); when(info1.getDisplayName()).thenReturn(CARRIER_1); when(info2.getDisplayName()).thenReturn(CARRIER_1); when(info3.getDisplayName()).thenReturn(CARRIER_1); when(mSubMgr.getActiveSubscriptionInfoList()).thenReturn( Arrays.asList(info1, info2, info3)); // Subscription 1 has a unique phone number, but subscriptions 2 and 3 share the same // last four digits. TelephonyManager sub1Telmgr = mock(TelephonyManager.class); TelephonyManager sub2Telmgr = mock(TelephonyManager.class); TelephonyManager sub3Telmgr = mock(TelephonyManager.class); when(sub1Telmgr.getLine1Number()).thenReturn("1112223333"); when(sub2Telmgr.getLine1Number()).thenReturn("2223334444"); when(sub3Telmgr.getLine1Number()).thenReturn("5556664444"); when(mTelMgr.createForSubscriptionId(SUBID_1)).thenReturn(sub1Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_2)).thenReturn(sub2Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_3)).thenReturn(sub3Telmgr); final Map<Integer, CharSequence> idNames = SubscriptionUtil.getUniqueSubscriptionDisplayNames(mContext); assertThat(idNames).isNotNull(); assertThat(idNames).hasSize(3); assertEquals(CARRIER_1 + " 3333", idNames.get(SUBID_1)); assertEquals(CARRIER_1 + " 2", idNames.get(SUBID_2)); assertEquals(CARRIER_1 + " 3", idNames.get(SUBID_3)); } @Test public void isInactiveInsertedPSim_nullSubInfo_doesNotCrash() { assertThat(SubscriptionUtil.isInactiveInsertedPSim(null)).isFalse(); Loading Loading
src/com/android/settings/network/SubscriptionUtil.java +92 −0 Original line number Diff line number Diff line Loading @@ -28,19 +28,25 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccSlotInfo; import android.text.TextUtils; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity; import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity; import com.android.settingslib.DeviceInfoUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; public class SubscriptionUtil { private static final String TAG = "SubscriptionUtil"; Loading Loading @@ -214,6 +220,92 @@ public class SubscriptionUtil { return null; } /** * Return a mapping of active subscription ids to diaplay names. Each display name is * guaranteed to be unique in the following manner: * 1) If the original display name is not unique, the last four digits of the phone number * will be appended. * 2) If the phone number is not visible or the last four digits are shared with another * subscription, the subscription id will be appended to the original display name. * More details can be found at go/unique-sub-display-names. * * @return map of active subscription ids to diaplay names. */ @VisibleForTesting public static Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) { class DisplayInfo { public SubscriptionInfo subscriptionInfo; public CharSequence originalName; public CharSequence uniqueName; } final SubscriptionManager subscriptionManager = context.getSystemService(SubscriptionManager.class); // Map of SubscriptionId to DisplayName final Supplier<Stream<DisplayInfo>> originalInfos = () -> getActiveSubscriptions(subscriptionManager) .stream() .map(i -> { DisplayInfo info = new DisplayInfo(); info.subscriptionInfo = i; info.originalName = i.getDisplayName(); return info; }); // TODO(goldmanj) consider using a map of DisplayName to SubscriptionInfos. // A Unique set of display names Set<CharSequence> uniqueNames = new HashSet<>(); // Return the set of duplicate names final Set<CharSequence> duplicateOriginalNames = originalInfos.get() .filter(info -> !uniqueNames.add(info.originalName)) .map(info -> info.originalName) .collect(Collectors.toSet()); // If a display name is duplicate, append the final 4 digits of the phone number. // Creates a mapping of Subscription id to original display name + phone number display name final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> { if (duplicateOriginalNames.contains(info.originalName)) { // This may return null, if the user cannot view the phone number itself. final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context, info.subscriptionInfo); String lastFourDigits = ""; if (phoneNumber != null) { lastFourDigits = (phoneNumber.length() > 4) ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber; } if (TextUtils.isEmpty(lastFourDigits)) { info.uniqueName = info.originalName; } else { info.uniqueName = info.originalName + " " + lastFourDigits; } } else { info.uniqueName = info.originalName; } return info; }); // Check uniqueness a second time. // We might not have had permission to view the phone numbers. // There might also be multiple phone numbers whose last 4 digits the same. uniqueNames.clear(); final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get() .filter(info -> !uniqueNames.add(info.uniqueName)) .map(info -> info.uniqueName) .collect(Collectors.toSet()); return uniqueInfos.get().map(info -> { if (duplicatePhoneNames.contains(info.uniqueName)) { info.uniqueName = info.originalName + " " + info.subscriptionInfo.getSubscriptionId(); } return info; }).collect(Collectors.toMap( info -> info.subscriptionInfo.getSubscriptionId(), info -> info.uniqueName)); } public static String getDisplayName(SubscriptionInfo info) { final CharSequence name = info.getDisplayName(); if (name != null) { Loading
tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java +132 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settings.network; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; Loading @@ -38,9 +39,15 @@ import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.List; import java.util.Map; @RunWith(AndroidJUnit4.class) public class SubscriptionUtilTest { private static final int SUBID_1 = 1; private static final int SUBID_2 = 2; private static final int SUBID_3 = 3; private static final CharSequence CARRIER_1 = "carrier1"; private static final CharSequence CARRIER_2 = "carrier2"; private Context mContext; @Mock Loading Loading @@ -125,6 +132,131 @@ public class SubscriptionUtilTest { assertThat(subs).hasSize(2); } @Test public void getUniqueDisplayNames_uniqueCarriers_originalUsed() { // Each subscription's default display name is unique. final SubscriptionInfo info1 = mock(SubscriptionInfo.class); final SubscriptionInfo info2 = mock(SubscriptionInfo.class); when(info1.getSubscriptionId()).thenReturn(SUBID_1); when(info2.getSubscriptionId()).thenReturn(SUBID_2); when(info1.getDisplayName()).thenReturn(CARRIER_1); when(info2.getDisplayName()).thenReturn(CARRIER_2); when(mSubMgr.getActiveSubscriptionInfoList()).thenReturn( Arrays.asList(info1, info2)); // Each subscription has a unique last 4 digits of the phone number. TelephonyManager sub1Telmgr = mock(TelephonyManager.class); TelephonyManager sub2Telmgr = mock(TelephonyManager.class); when(sub1Telmgr.getLine1Number()).thenReturn("1112223333"); when(sub2Telmgr.getLine1Number()).thenReturn("2223334444"); when(mTelMgr.createForSubscriptionId(SUBID_1)).thenReturn(sub1Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_2)).thenReturn(sub2Telmgr); final Map<Integer, CharSequence> idNames = SubscriptionUtil.getUniqueSubscriptionDisplayNames(mContext); assertThat(idNames).isNotNull(); assertThat(idNames).hasSize(2); assertEquals(CARRIER_1, idNames.get(SUBID_1)); assertEquals(CARRIER_2, idNames.get(SUBID_2)); } @Test public void getUniqueDisplayNames_identicalCarriers_fourDigitsUsed() { // Both subscriptoins have the same display name. final SubscriptionInfo info1 = mock(SubscriptionInfo.class); final SubscriptionInfo info2 = mock(SubscriptionInfo.class); when(info1.getSubscriptionId()).thenReturn(SUBID_1); when(info2.getSubscriptionId()).thenReturn(SUBID_2); when(info1.getDisplayName()).thenReturn(CARRIER_1); when(info2.getDisplayName()).thenReturn(CARRIER_1); when(mSubMgr.getActiveSubscriptionInfoList()).thenReturn( Arrays.asList(info1, info2)); // Each subscription has a unique last 4 digits of the phone number. TelephonyManager sub1Telmgr = mock(TelephonyManager.class); TelephonyManager sub2Telmgr = mock(TelephonyManager.class); when(sub1Telmgr.getLine1Number()).thenReturn("1112223333"); when(sub2Telmgr.getLine1Number()).thenReturn("2223334444"); when(mTelMgr.createForSubscriptionId(SUBID_1)).thenReturn(sub1Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_2)).thenReturn(sub2Telmgr); final Map<Integer, CharSequence> idNames = SubscriptionUtil.getUniqueSubscriptionDisplayNames(mContext); assertThat(idNames).isNotNull(); assertThat(idNames).hasSize(2); assertEquals(CARRIER_1 + " 3333", idNames.get(SUBID_1)); assertEquals(CARRIER_1 + " 4444", idNames.get(SUBID_2)); } @Test public void getUniqueDisplayNames_phoneNumberBlocked_subscriptoinIdFallback() { // Both subscriptoins have the same display name. final SubscriptionInfo info1 = mock(SubscriptionInfo.class); final SubscriptionInfo info2 = mock(SubscriptionInfo.class); when(info1.getSubscriptionId()).thenReturn(SUBID_1); when(info2.getSubscriptionId()).thenReturn(SUBID_2); when(info1.getDisplayName()).thenReturn(CARRIER_1); when(info2.getDisplayName()).thenReturn(CARRIER_1); when(mSubMgr.getActiveSubscriptionInfoList()).thenReturn( Arrays.asList(info1, info2)); // The subscriptions' phone numbers cannot be revealed to the user. TelephonyManager sub1Telmgr = mock(TelephonyManager.class); TelephonyManager sub2Telmgr = mock(TelephonyManager.class); when(sub1Telmgr.getLine1Number()).thenReturn(""); when(sub2Telmgr.getLine1Number()).thenReturn(""); when(mTelMgr.createForSubscriptionId(SUBID_1)).thenReturn(sub1Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_2)).thenReturn(sub2Telmgr); final Map<Integer, CharSequence> idNames = SubscriptionUtil.getUniqueSubscriptionDisplayNames(mContext); assertThat(idNames).isNotNull(); assertThat(idNames).hasSize(2); assertEquals(CARRIER_1 + " 1", idNames.get(SUBID_1)); assertEquals(CARRIER_1 + " 2", idNames.get(SUBID_2)); } @Test public void getUniqueDisplayNames_phoneNumberIdentical_subscriptoinIdFallback() { // TODO have three here from the same carrier // Both subscriptoins have the same display name. final SubscriptionInfo info1 = mock(SubscriptionInfo.class); final SubscriptionInfo info2 = mock(SubscriptionInfo.class); final SubscriptionInfo info3 = mock(SubscriptionInfo.class); when(info1.getSubscriptionId()).thenReturn(SUBID_1); when(info2.getSubscriptionId()).thenReturn(SUBID_2); when(info3.getSubscriptionId()).thenReturn(SUBID_3); when(info1.getDisplayName()).thenReturn(CARRIER_1); when(info2.getDisplayName()).thenReturn(CARRIER_1); when(info3.getDisplayName()).thenReturn(CARRIER_1); when(mSubMgr.getActiveSubscriptionInfoList()).thenReturn( Arrays.asList(info1, info2, info3)); // Subscription 1 has a unique phone number, but subscriptions 2 and 3 share the same // last four digits. TelephonyManager sub1Telmgr = mock(TelephonyManager.class); TelephonyManager sub2Telmgr = mock(TelephonyManager.class); TelephonyManager sub3Telmgr = mock(TelephonyManager.class); when(sub1Telmgr.getLine1Number()).thenReturn("1112223333"); when(sub2Telmgr.getLine1Number()).thenReturn("2223334444"); when(sub3Telmgr.getLine1Number()).thenReturn("5556664444"); when(mTelMgr.createForSubscriptionId(SUBID_1)).thenReturn(sub1Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_2)).thenReturn(sub2Telmgr); when(mTelMgr.createForSubscriptionId(SUBID_3)).thenReturn(sub3Telmgr); final Map<Integer, CharSequence> idNames = SubscriptionUtil.getUniqueSubscriptionDisplayNames(mContext); assertThat(idNames).isNotNull(); assertThat(idNames).hasSize(3); assertEquals(CARRIER_1 + " 3333", idNames.get(SUBID_1)); assertEquals(CARRIER_1 + " 2", idNames.get(SUBID_2)); assertEquals(CARRIER_1 + " 3", idNames.get(SUBID_3)); } @Test public void isInactiveInsertedPSim_nullSubInfo_doesNotCrash() { assertThat(SubscriptionUtil.isInactiveInsertedPSim(null)).isFalse(); Loading