Provided a solution to sort partitions from order in
/product/overlay/partition_order.xml, added a debugging command
to dump the partition order and how it get established.

Bug: b/278850109
Test: Added and verified affected tests pass.
Change-Id: I707ace74dc55ed5e37b672f191636e8d0ff78e95
Merged-In: I707ace74dc55ed5e37b672f191636e8d0ff78e95
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index a0f3d7a..122ab48 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -190,4 +190,15 @@
      * @throws SecurityException if the transaction failed
      */
     void commit(in OverlayManagerTransaction transaction);
+
+    /**
+     * Returns a String of a list of partitions from low priority to high.
+     */
+    String getPartitionOrder();
+
+    /**
+     * Returns a boolean which represent whether the partition list is sorted by default.
+     * If not then it should be sorted by /product/overlay/partition_order.xml.
+     */
+    boolean isDefaultPartitionOrder();
 }
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index fc0943b..e8bdd1d 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -34,8 +34,15 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.TriConsumer;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -46,6 +53,10 @@
 import java.util.Map;
 import java.util.function.Supplier;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
 /**
  * Responsible for reading overlay configuration files and handling queries of overlay mutability,
  * default-enabled state, and priority.
@@ -61,6 +72,8 @@
     @VisibleForTesting
     public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE;
 
+    public static final String PARTITION_ORDER_FILE_PATH = "/product/overlay/partition_order.xml";
+
     @VisibleForTesting
     public static final class Configuration {
         @Nullable
@@ -119,6 +132,10 @@
     // Singleton instance only assigned in system server
     private static OverlayConfig sInstance;
 
+    private final String mPartitionOrder;
+
+    private final boolean mIsDefaultPartitionOrder;
+
     @VisibleForTesting
     public OverlayConfig(@Nullable File rootDirectory,
             @Nullable Supplier<OverlayScanner> scannerFactory,
@@ -137,6 +154,8 @@
                             new File(rootDirectory, p.getNonConicalFolder().getPath()),
                             p)));
         }
+        mIsDefaultPartitionOrder = !sortPartitions(PARTITION_ORDER_FILE_PATH, partitions);
+        mPartitionOrder = generatePartitionOrderString(partitions);
 
         ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions);
 
@@ -198,6 +217,96 @@
         }
     }
 
+    private static String generatePartitionOrderString(List<OverlayPartition> partitions) {
+        if (partitions == null || partitions.size() == 0) {
+            return "";
+        }
+        StringBuilder partitionOrder = new StringBuilder();
+        partitionOrder.append(partitions.get(0).getName());
+        for (int i = 1; i < partitions.size(); i++) {
+            partitionOrder.append(", ").append(partitions.get(i).getName());
+        }
+        return partitionOrder.toString();
+    }
+
+    private static boolean parseAndValidatePartitionsOrderXml(String partitionOrderFilePath,
+            Map<String, Integer> orderMap, List<OverlayPartition> partitions) {
+        try {
+            File file = new File(partitionOrderFilePath);
+            if (!file.exists()) {
+                Log.w(TAG, "partition_order.xml does not exist.");
+                return false;
+            }
+            var dbFactory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+            Document doc = dBuilder.parse(file);
+            doc.getDocumentElement().normalize();
+
+            Element root = doc.getDocumentElement();
+            if (!root.getNodeName().equals("partition-order")) {
+                Log.w(TAG, "Invalid partition_order.xml, "
+                        + "xml root element is not partition-order");
+                return false;
+            }
+
+            NodeList partitionList = doc.getElementsByTagName("partition");
+            for (int order = 0; order < partitionList.getLength(); order++) {
+                Node partitionNode = partitionList.item(order);
+                if (partitionNode.getNodeType() == Node.ELEMENT_NODE) {
+                    Element partitionElement = (Element) partitionNode;
+                    String partitionName = partitionElement.getAttribute("name");
+                    if (orderMap.containsKey(partitionName)) {
+                        Log.w(TAG, "Invalid partition_order.xml, "
+                                + "it has duplicate partition: " + partitionName);
+                        return false;
+                    }
+                    orderMap.put(partitionName, order);
+                }
+            }
+
+            if (orderMap.keySet().size() != partitions.size()) {
+                Log.w(TAG, "Invalid partition_order.xml, partition_order.xml has "
+                        + orderMap.keySet().size() + " partitions, "
+                        + "which is different from SYSTEM_PARTITIONS");
+                return false;
+            }
+            for (int i = 0; i < partitions.size(); i++) {
+                if (!orderMap.keySet().contains(partitions.get(i).getName())) {
+                    Log.w(TAG, "Invalid Parsing partition_order.xml, "
+                            + "partition_order.xml does not have partition: "
+                            + partitions.get(i).getName());
+                    return false;
+                }
+            }
+        } catch (ParserConfigurationException | SAXException | IOException e) {
+            Log.w(TAG, "Parsing or validating partition_order.xml failed, "
+                    + "exception thrown: " + e.getMessage());
+            return false;
+        }
+        Log.i(TAG, "Sorting partitions in the specified order from partitions_order.xml");
+        return true;
+    }
+
+    /**
+     * Sort partitions by order in partition_order.xml if the file exists.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static boolean sortPartitions(String partitionOrderFilePath,
+            List<OverlayPartition> partitions) {
+        Map<String, Integer> orderMap = new HashMap<>();
+        if (!parseAndValidatePartitionsOrderXml(partitionOrderFilePath, orderMap, partitions)) {
+            return false;
+        }
+
+        Comparator<OverlayPartition> partitionComparator = Comparator.comparingInt(
+                o -> orderMap.get(o.getName()));
+        Collections.sort(partitions, partitionComparator);
+
+        return true;
+    }
+
     /**
      * Creates an instance of OverlayConfig for use in the zygote process.
      * This instance will not include information of static overlays existing outside of a partition
@@ -476,4 +585,19 @@
      */
     private static native String[] createIdmap(@NonNull String targetPath,
             @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable);
+
+    /**
+     * @hide
+     */
+    public boolean isDefaultPartitionOrder() {
+        return mIsDefaultPartitionOrder;
+    }
+
+    /**
+     * @hide
+     */
+    public String getPartitionOrder() {
+        return mPartitionOrder;
+    }
+
 }
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index 0ab7b3d..5a86b93 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
@@ -53,8 +54,11 @@
  *
  * @see #parseOverlay(File, XmlPullParser, OverlayScanner, ParsingContext)
  * @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext)
+ *
+ * @hide
  **/
-final class OverlayConfigParser {
+@VisibleForTesting
+public final class OverlayConfigParser {
 
     // Default values for overlay configurations.
     static final boolean DEFAULT_ENABLED_STATE = false;
@@ -115,7 +119,11 @@
         }
     }
 
-    static class OverlayPartition extends SystemPartition {
+    /**
+     * @hide
+     **/
+    @VisibleForTesting
+    public static class OverlayPartition extends SystemPartition {
         // Policies passed to idmap2 during idmap creation.
         // Keep partition policy constants in sync with f/b/cmds/idmap2/include/idmap2/Policies.h.
         static final String POLICY_ODM = "odm";
@@ -128,7 +136,11 @@
         @NonNull
         public final String policy;
 
-        OverlayPartition(@NonNull SystemPartition partition) {
+        /**
+         * @hide
+         **/
+        @VisibleForTesting
+        public OverlayPartition(@NonNull SystemPartition partition) {
             super(partition);
             this.policy = policyForPartition(partition);
         }
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index 0f30cfe..246a1e7 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.content.pm.PackagePartitions;
 import android.os.FileUtils;
 import android.os.SystemProperties;
 import android.platform.test.annotations.Presubmit;
@@ -32,6 +33,7 @@
 import com.android.frameworks.coretests.R;
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.content.om.OverlayConfig.IdmapInvocation;
+import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
 import com.android.internal.content.om.OverlayScanner;
 
 import org.junit.Rule;
@@ -46,6 +48,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.List;
 
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -88,6 +91,17 @@
         assertEquals(configIndex, config.configIndex);
     }
 
+    private String generatePartitionOrderString(List<OverlayPartition> partitions) {
+        StringBuilder partitionOrder = new StringBuilder();
+        for (int i = 0; i < partitions.size(); i++) {
+            partitionOrder.append(partitions.get(i).getName());
+            if (i < partitions.size() - 1) {
+                partitionOrder.append(", ");
+            }
+        }
+        return partitionOrder.toString();
+    }
+
     @Test
     public void testImmutableAfterNonImmutableFails() throws IOException {
         mExpectedException.expect(IllegalStateException.class);
@@ -685,4 +699,122 @@
         OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three");
         assertNotNull(o3);
     }
+
+    @Test
+    public void testSortPartitionsWithoutXml() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithInvalidXmlRootElement() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-list>\n"
+                        + "  <partition name=\"system_ext\"/>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-list>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithInvalidPartition() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-order>\n"
+                        + "  <partition name=\"INVALID\"/>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-order>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithDuplicatePartition() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-order>\n"
+                        + "  <partition name=\"system_ext\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-order>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithMissingPartition() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-order>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-order>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system, vendor, odm, oem, product, system_ext",
+                generatePartitionOrderString(partitions));
+    }
+
+    @Test
+    public void testSortPartitionsWithCorrectPartitionOrderXml() throws IOException {
+        ArrayList<OverlayPartition> partitions = new ArrayList<>(
+                PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        createFile("/product/overlay/partition_order.xml",
+                "<partition-order>\n"
+                        + "  <partition name=\"system_ext\"/>\n"
+                        + "  <partition name=\"vendor\"/>\n"
+                        + "  <partition name=\"oem\"/>\n"
+                        + "  <partition name=\"odm\"/>\n"
+                        + "  <partition name=\"product\"/>\n"
+                        + "  <partition name=\"system\"/>\n"
+                        + "</partition-order>\n");
+        final OverlayConfig overlayConfig = createConfigImpl();
+        String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
+                "/product/overlay/partition_order.xml");
+        assertEquals(true, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
+        assertEquals("system_ext, vendor, oem, odm, product, system",
+                generatePartitionOrderString(partitions));
+    }
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 362b26e..bdab341 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1109,6 +1109,21 @@
             int callingUid = Binder.getCallingUid();
             mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, realUserId);
         }
+
+        /**
+         * @hide
+         */
+        public String getPartitionOrder() {
+            return mImpl.getOverlayConfig().getPartitionOrder();
+        }
+
+        /**
+         * @hide
+         */
+        public boolean isDefaultPartitionOrder() {
+            return mImpl.getOverlayConfig().isDefaultPartitionOrder();
+        }
+
     };
 
     private static final class PackageManagerHelperImpl implements PackageManagerHelper {
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 38781fa..bd0f657 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -876,4 +876,8 @@
             super(message, cause);
         }
     }
+
+    OverlayConfig getOverlayConfig() {
+        return mOverlayConfig;
+    }
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 89939a3..4beb391 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -16,6 +16,8 @@
 
 package com.android.server.om;
 
+import static com.android.internal.content.om.OverlayConfig.PARTITION_ORDER_FILE_PATH;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -79,6 +81,8 @@
                     return runLookup();
                 case "fabricate":
                     return runFabricate();
+                case "partition-order":
+                    return runPartitionOrder();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -130,6 +134,9 @@
         out.println("    Create an overlay from a single resource. Caller must be root. Example:");
         out.println("      fabricate --target android --name LighterGray \\");
         out.println("                android:color/lighter_gray 0x1c 0xffeeeeee");
+        out.println("  partition-order");
+        out.println("    Print the partition order from overlay config and how this order");
+        out.println("    got established, by default or by " + PARTITION_ORDER_FILE_PATH);
     }
 
     private int runList() throws RemoteException {
@@ -230,6 +237,14 @@
         return 0;
     }
 
+    private int runPartitionOrder() throws RemoteException {
+        final PrintWriter out = getOutPrintWriter();
+        out.println("Partition order (low to high priority): " + mInterface.getPartitionOrder());
+        out.println("Established by " + (mInterface.isDefaultPartitionOrder() ? "default"
+                : PARTITION_ORDER_FILE_PATH));
+        return 0;
+    }
+
     private int runFabricate() throws RemoteException {
         final PrintWriter err = getErrPrintWriter();
         if (Binder.getCallingUid() != Process.ROOT_UID) {