diff options
7 files changed, 316 insertions, 3 deletions
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl index a0f3d7a2b8b7..122ab486948f 100644 --- a/core/java/android/content/om/IOverlayManager.aidl +++ b/core/java/android/content/om/IOverlayManager.aidl @@ -190,4 +190,15 @@ interface IOverlayManager { * @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 fc0943bb3f63..e8bdd1d74d1f 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.content.om.OverlayScanner.ParsedOverlayInfo; 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.List; 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 @@ public class OverlayConfig { @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 @@ public class OverlayConfig { // 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 @@ public class OverlayConfig { 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 @@ public class OverlayConfig { } } + 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 @@ public class OverlayConfig { */ 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 0ab7b3d2ef86..5a86b93434f9 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.ArraySet; 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 @@ import java.util.Map; * * @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 @@ final class OverlayConfigParser { } } - 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 @@ final class OverlayConfigParser { @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 0f30cfead4f7..246a1e78bd5e 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.assertNotNull; 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 androidx.test.runner.AndroidJUnit4; 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.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.List; @Presubmit @RunWith(AndroidJUnit4.class) @@ -88,6 +91,17 @@ public class OverlayConfigTest { 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 @@ public class OverlayConfigTest { 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 362b26ec762a..bdab341714c2 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 @@ public final class OverlayManagerService extends SystemService { 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 38781fad76fd..bd0f657982b6 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 @@ final class OverlayManagerServiceImpl { 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 89939a31321f..4beb391571ec 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 @@ final class OverlayManagerShellCommand extends ShellCommand { return runLookup(); case "fabricate": return runFabricate(); + case "partition-order": + return runPartitionOrder(); default: return handleDefaultCommands(cmd); } @@ -130,6 +134,9 @@ final class OverlayManagerShellCommand extends ShellCommand { 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 @@ final class OverlayManagerShellCommand extends ShellCommand { 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) { |