Merge "Return mapping of subscription id to unique display name."
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index cff8f55..a63658a 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -28,19 +28,25 @@
 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 @@
         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) {
diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
index 9e319fb..90564fa 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 @@
 
 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 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
@@ -126,6 +133,131 @@
     }
 
     @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();
     }