diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index cff8f554a32..a63658a0824 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -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"; @@ -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 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> 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 uniqueNames = new HashSet<>(); + // Return the set of duplicate names + final Set 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> 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 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) { diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java index 9e319fb6ac8..90564fabe63 100644 --- a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java +++ b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java @@ -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; @@ -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 @@ -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 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 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 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 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();