Skip to content
Snippets Groups Projects
Commit 9cdfcfb2 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Support properties in path when merging overlay configs" into main

parents e19460ac efa61bef
No related branches found
No related tags found
No related merge requests found
......@@ -24,6 +24,8 @@ import android.content.pm.PackagePartitions;
import android.content.pm.PackagePartitions.SystemPartition;
import android.os.Build;
import android.os.FileUtils;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Xml;
......@@ -241,6 +243,18 @@ public final class OverlayConfigParser {
}
}
@FunctionalInterface
public interface SysPropWrapper{
/**
* Get system property
*
* @param property the key to look up.
*
* @return The property value if found, empty string otherwise.
*/
String get(String property);
}
/**
* Retrieves overlays configured within the partition in increasing priority order.
*
......@@ -319,6 +333,76 @@ public final class OverlayConfigParser {
}
}
/**
* Expand the property inside a rro configuration path.
*
* A RRO configuration can contain a property, this method expands
* the property to its value.
*
* Only read only properties allowed, prefixed with ro. Other
* properties will raise exception.
*
* Only a single property in the path is allowed.
*
* Example "${ro.boot.hardware.sku}/config.xml" would expand to
* "G020N/config.xml"
*
* @param configPath path to expand
* @param sysPropWrapper method used for reading properties
*
* @return The expanded path. Returns null if configPath is null.
*/
@VisibleForTesting
public static String expandProperty(String configPath,
SysPropWrapper sysPropWrapper) {
if (configPath == null) {
return null;
}
int propStartPos = configPath.indexOf("${");
if (propStartPos == -1) {
// No properties inside the string, return as is
return configPath;
}
final StringBuilder sb = new StringBuilder();
sb.append(configPath.substring(0, propStartPos));
// Read out the end position
int propEndPos = configPath.indexOf("}", propStartPos);
if (propEndPos == -1) {
throw new IllegalStateException("Malformed property, unmatched braces, in: "
+ configPath);
}
// Confirm that there is only one property inside the string
if (configPath.indexOf("${", propStartPos + 2) != -1) {
throw new IllegalStateException("Only a single property supported in path: "
+ configPath);
}
final String propertyName = configPath.substring(propStartPos + 2, propEndPos);
if (!propertyName.startsWith("ro.")) {
throw new IllegalStateException("Only read only properties can be used when "
+ "merging RRO config files: " + propertyName);
}
final String propertyValue = sysPropWrapper.get(propertyName);
if (TextUtils.isEmpty(propertyValue)) {
throw new IllegalStateException("Property is empty or doesn't exist: " + propertyName);
}
Log.d(TAG, String.format("Using property in overlay config path: \"%s\"", propertyName));
sb.append(propertyValue);
// propEndPos points to '}', need to step to next character, might be outside of string
propEndPos = propEndPos + 1;
// Append the remainder, if exists
if (propEndPos < configPath.length()) {
sb.append(configPath.substring(propEndPos));
}
return sb.toString();
}
/**
* Parses a <merge> tag within an overlay configuration file.
*
......@@ -331,10 +415,21 @@ public final class OverlayConfigParser {
@Nullable OverlayScanner scanner,
@Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
@NonNull ParsingContext parsingContext) {
final String path = parser.getAttributeValue(null, "path");
final String path;
try {
SysPropWrapper sysPropWrapper = p -> {
return SystemProperties.get(p, "");
};
path = expandProperty(parser.getAttributeValue(null, "path"), sysPropWrapper);
} catch (IllegalStateException e) {
throw new IllegalStateException(String.format("<merge> path expand error in %s at %s",
configFile, parser.getPositionDescription()), e);
}
if (path == null) {
throw new IllegalStateException(String.format("<merge> without path in %s at %s"
+ configFile, parser.getPositionDescription()));
throw new IllegalStateException(String.format("<merge> without path in %s at %s",
configFile, parser.getPositionDescription()));
}
if (path.startsWith("/")) {
......
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.content.res;
import static com.android.internal.content.om.OverlayConfigParser.SysPropWrapper;
import static org.junit.Assert.assertEquals;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.content.om.OverlayConfigParser;
import org.junit.Test;
import org.junit.runner.RunWith;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class OverlayConfigParserTest {
@Test(expected = IllegalStateException.class)
public void testMergePropNotRoProp() {
SysPropWrapper sysProp = p -> {
return "dummy_value";
};
OverlayConfigParser.expandProperty("${persist.value}/path", sysProp);
}
@Test(expected = IllegalStateException.class)
public void testMergePropMissingEndBracket() {
SysPropWrapper sysProp = p -> {
return "dummy_value";
};
OverlayConfigParser.expandProperty("${ro.value/path", sysProp);
}
@Test(expected = IllegalStateException.class)
public void testMergeOnlyPropStart() {
SysPropWrapper sysProp = p -> {
return "dummy_value";
};
OverlayConfigParser.expandProperty("path/${", sysProp);
}
@Test(expected = IllegalStateException.class)
public void testMergePropInProp() {
SysPropWrapper sysProp = p -> {
return "dummy_value";
};
OverlayConfigParser.expandProperty("path/${${ro.value}}", sysProp);
}
/**
* The path is only allowed to contain one property.
*/
@Test(expected = IllegalStateException.class)
public void testMergePropMultipleProps() {
SysPropWrapper sysProp = p -> {
return "dummy_value";
};
OverlayConfigParser.expandProperty("${ro.value}/path${ro.value2}/path", sysProp);
}
@Test
public void testMergePropOneProp() {
final SysPropWrapper sysProp = p -> {
if ("ro.value".equals(p)) {
return "dummy_value";
} else {
return "invalid";
}
};
// Property in the beginnig of the string
String result = OverlayConfigParser.expandProperty("${ro.value}/path",
sysProp);
assertEquals("dummy_value/path", result);
// Property in the middle of the string
result = OverlayConfigParser.expandProperty("path/${ro.value}/file",
sysProp);
assertEquals("path/dummy_value/file", result);
// Property at the of the string
result = OverlayConfigParser.expandProperty("path/${ro.value}",
sysProp);
assertEquals("path/dummy_value", result);
// Property is the entire string
result = OverlayConfigParser.expandProperty("${ro.value}",
sysProp);
assertEquals("dummy_value", result);
}
@Test
public void testMergePropNoProp() {
final SysPropWrapper sysProp = p -> {
return "dummy_value";
};
final String path = "no_props/path";
String result = OverlayConfigParser.expandProperty(path, sysProp);
assertEquals(path, result);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment