Merge "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." into main
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -876,4 +876,8 @@ final class OverlayManagerServiceImpl {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
OverlayConfig getOverlayConfig() {
|
||||
return mOverlayConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user