Merge changes from topic 'sort-order'

* changes:
  Migrate phone account sorting to use lamba expressions and add test.
  PhoneAccounts sorting
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 8cdec30..074f325 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -73,6 +73,7 @@
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -1089,6 +1090,52 @@
         }
     }
 
+    private void sortPhoneAccounts() {
+        if (mState.accounts.size() > 1) {
+            // Sort the phone accounts using sort order:
+            // 1) SIM accounts first, followed by non-sim accounts
+            // 2) Sort order, with those specifying no sort order last.
+            // 3) Label
+
+            // Comparator to sort SIM subscriptions before non-sim subscriptions.
+            Comparator<PhoneAccount> bySimCapability = (p1, p2) -> {
+                if (p1.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                        && !p2.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+                    return -1;
+                } else if (!p1.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                        && p2.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+                    return 1;
+                } else {
+                    return 0;
+                }
+            };
+
+            // Create a string comparator which will sort strings, placing nulls last.
+            Comparator<String> nullSafeStringComparator = Comparator.nullsLast(
+                    String::compareTo);
+
+            // Comparator which places PhoneAccounts with a specified sort order first, followed by
+            // those with no sort order.
+            Comparator<PhoneAccount> bySortOrder = (p1, p2) -> {
+                String sort1 = p1.getExtras() == null ? null :
+                        p1.getExtras().getString(PhoneAccount.EXTRA_SORT_ORDER, null);
+                String sort2 = p2.getExtras() == null ? null :
+                        p2.getExtras().getString(PhoneAccount.EXTRA_SORT_ORDER, null);
+                return nullSafeStringComparator.compare(sort1, sort2);
+            };
+
+            // Comparator which sorts PhoneAccounts by label.
+            Comparator<PhoneAccount> byLabel = (p1, p2) -> {
+                String s1 = p1.getLabel() == null ? null : p1.getLabel().toString();
+                String s2 = p2.getLabel() == null ? null : p2.getLabel().toString();
+                return nullSafeStringComparator.compare(s1, s2);
+            };
+
+            // Sort the phone accounts.
+            mState.accounts.sort(bySimCapability.thenComparing(bySortOrder.thenComparing(byLabel)));
+        }
+    }
+
     ////////////////////////////////////////////////////////////////////////////////////////////////
     //
     // State management
@@ -1115,6 +1162,7 @@
 
     private void write() {
         try {
+            sortPhoneAccounts();
             ByteArrayOutputStream os = new ByteArrayOutputStream();
             XmlSerializer serializer = new FastXmlSerializer();
             serializer.setOutput(os, "utf-8");
@@ -1870,4 +1918,8 @@
             return null;
         }
     };
+
+    private String nullToEmpty(String str) {
+        return str == null ? "" : str;
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index 2898457..a98712c 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -51,6 +51,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 
 import static org.mockito.Matchers.anyInt;
@@ -577,6 +578,192 @@
         assertEquals(PhoneAccount.CAPABILITY_SELF_MANAGED, registeredAccount.getCapabilities());
     }
 
+    @MediumTest
+    public void testSortSimFirst() throws Exception {
+        ComponentName componentA = new ComponentName("a", "a");
+        ComponentName componentB = new ComponentName("b", "b");
+        mComponentContextFixture.addConnectionService(componentA,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentB,
+                Mockito.mock(IConnectionService.class));
+
+        PhoneAccount simAccount = new PhoneAccount.Builder(
+                makeQuickAccountHandle(componentB, "2"), "2")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .setIsEnabled(true)
+                .build();
+
+        PhoneAccount nonSimAccount = new PhoneAccount.Builder(
+                makeQuickAccountHandle(componentA, "1"), "1")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .setIsEnabled(true)
+                .build();
+
+        registerAndEnableAccount(nonSimAccount);
+        registerAndEnableAccount(simAccount);
+
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        assertTrue(accounts.get(0).getLabel().toString().equals("2"));
+        assertTrue(accounts.get(1).getLabel().toString().equals("1"));
+    }
+
+    @MediumTest
+    public void testSortBySortOrder() throws Exception {
+        ComponentName componentA = new ComponentName("a", "a");
+        ComponentName componentB = new ComponentName("b", "b");
+        ComponentName componentC = new ComponentName("c", "c");
+        mComponentContextFixture.addConnectionService(componentA,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentB,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentC,
+                Mockito.mock(IConnectionService.class));
+
+        PhoneAccount account1 = new PhoneAccount.Builder(
+                makeQuickAccountHandle(componentA, "c"), "c")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "A"))
+                .build();
+
+        PhoneAccount account2 = new PhoneAccount.Builder(
+                makeQuickAccountHandle(componentB, "b"), "b")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "B"))
+                .build();
+
+        PhoneAccount account3 = new PhoneAccount.Builder(
+                makeQuickAccountHandle(componentC, "c"), "a")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+
+        registerAndEnableAccount(account3);
+        registerAndEnableAccount(account2);
+        registerAndEnableAccount(account1);
+
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        assertTrue(accounts.get(0).getLabel().toString().equals("c"));
+        assertTrue(accounts.get(1).getLabel().toString().equals("b"));
+        assertTrue(accounts.get(2).getLabel().toString().equals("a"));
+    }
+
+    @MediumTest
+    public void testSortByLabel() throws Exception {
+        ComponentName componentA = new ComponentName("a", "a");
+        ComponentName componentB = new ComponentName("b", "b");
+        ComponentName componentC = new ComponentName("c", "c");
+        mComponentContextFixture.addConnectionService(componentA,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentB,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentC,
+                Mockito.mock(IConnectionService.class));
+
+        PhoneAccount account1 = new PhoneAccount.Builder(makeQuickAccountHandle(componentA, "c"),
+                "c")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+
+        PhoneAccount account2 = new PhoneAccount.Builder(makeQuickAccountHandle(componentB, "b"),
+                "b")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+
+        PhoneAccount account3 = new PhoneAccount.Builder(makeQuickAccountHandle(componentC, "a"),
+                "a")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+
+        registerAndEnableAccount(account1);
+        registerAndEnableAccount(account2);
+        registerAndEnableAccount(account3);
+
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        assertTrue(accounts.get(0).getLabel().toString().equals("a"));
+        assertTrue(accounts.get(1).getLabel().toString().equals("b"));
+        assertTrue(accounts.get(2).getLabel().toString().equals("c"));
+    }
+
+    @MediumTest
+    public void testSortAll() throws Exception {
+        ComponentName componentA = new ComponentName("a", "a");
+        ComponentName componentB = new ComponentName("b", "b");
+        ComponentName componentC = new ComponentName("c", "c");
+        ComponentName componentW = new ComponentName("w", "w");
+        ComponentName componentX = new ComponentName("x", "x");
+        ComponentName componentY = new ComponentName("y", "y");
+        ComponentName componentZ = new ComponentName("z", "z");
+        mComponentContextFixture.addConnectionService(componentA,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentB,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentC,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentW,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentX,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentY,
+                Mockito.mock(IConnectionService.class));
+        mComponentContextFixture.addConnectionService(componentZ,
+                Mockito.mock(IConnectionService.class));
+        PhoneAccount account1 = new PhoneAccount.Builder(makeQuickAccountHandle(
+                makeQuickConnectionServiceComponentName(), "y"), "y")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "2"))
+                .build();
+
+        PhoneAccount account2 = new PhoneAccount.Builder(makeQuickAccountHandle(
+                makeQuickConnectionServiceComponentName(), "z"), "z")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "1"))
+                .build();
+
+        PhoneAccount account3 = new PhoneAccount.Builder(makeQuickAccountHandle(
+                makeQuickConnectionServiceComponentName(), "x"), "x")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .build();
+
+        PhoneAccount account4 = new PhoneAccount.Builder(makeQuickAccountHandle(
+                makeQuickConnectionServiceComponentName(), "w"), "w")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .build();
+
+        PhoneAccount account5 = new PhoneAccount.Builder(makeQuickAccountHandle(
+                makeQuickConnectionServiceComponentName(), "b"), "b")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+
+        PhoneAccount account6 = new PhoneAccount.Builder(makeQuickAccountHandle(
+                makeQuickConnectionServiceComponentName(), "c"), "a")
+                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+
+        registerAndEnableAccount(account1);
+        registerAndEnableAccount(account2);
+        registerAndEnableAccount(account3);
+        registerAndEnableAccount(account4);
+        registerAndEnableAccount(account5);
+        registerAndEnableAccount(account6);
+
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        // Sim accts ordered by sort order first
+        assertTrue(accounts.get(0).getLabel().toString().equals("z"));
+        assertTrue(accounts.get(1).getLabel().toString().equals("y"));
+
+        // Sim accts with no sort order next
+        assertTrue(accounts.get(2).getLabel().toString().equals("w"));
+        assertTrue(accounts.get(3).getLabel().toString().equals("x"));
+
+        // Other accts sorted by label next
+        assertTrue(accounts.get(4).getLabel().toString().equals("a"));
+        assertTrue(accounts.get(5).getLabel().toString().equals("b"));
+    }
+
     private static ComponentName makeQuickConnectionServiceComponentName() {
         return new ComponentName(
                 "com.android.server.telecom.tests",