Fix RRO loading from inside APEXes.
APEXes can already contain RRO APKs by using the 'rro' apex Soong
module field. However, these RROs were not being loaded properly by
Zygote or PackageManagerService.
For all RROs inside APEXes, the RRO uses the same overlay config that
is used for other RROs on the APEX's preinstalled partition.
For RROs targeting 'android', which are installed by Zygote
using AssetManager:
1. OverlayConfig looks for active APEXes in the apex-info-list file,
which is already accessible to Zygote.
2. OverlayConfig passes the APEX module names to OverlayConfigParser,
for each preinstalled-partition.
3. OverlayConfigParser uses OverlayScanner to scan the
each /apex/<APEX>/overlay directory.
For other RROs:
1. PackageManagerService already parses and provides RROs inside APEXes
to OverlayConfig.
2. RROs inside APEXes used to have no config rule applied because their
path prefix (/apex/) did not match any partition rule. Now, their
preinstalled path is used instead.
Bug: 199200417
Test: Define a static RRO targeting 'android' inside a /vendor APEX.
Define a static RRO for settings provider inside a /vendor APEX.
Observe APEXes are enabled by default.
Test: Make a change to an RRO inside a /vendor APEX.
m <apex>; adb install <apex artifact>; adb reboot;
Observe change has taken effect.
Change-Id: I2bce9bc704789329b8c6aac6d476f17ff6718e0f
Merged-In: I2bce9bc704789329b8c6aac6d476f17ff6718e0f
This commit is contained in:
@@ -294,6 +294,7 @@ java_defaults {
|
||||
srcs: [
|
||||
":framework-non-updatable-sources",
|
||||
"core/java/**/*.logtags",
|
||||
":apex-info-list",
|
||||
],
|
||||
aidl: {
|
||||
generate_get_transaction_name: true,
|
||||
|
||||
@@ -25,17 +25,22 @@ import android.os.Trace;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.apex.ApexInfo;
|
||||
import com.android.apex.XmlParser;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
|
||||
import com.android.internal.content.om.OverlayConfigParser.ParsedConfiguration;
|
||||
import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.util.function.TriConsumer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -73,7 +78,7 @@ public class OverlayConfig {
|
||||
public interface PackageProvider {
|
||||
|
||||
/** Performs the given action for each package. */
|
||||
void forEachPackage(BiConsumer<ParsingPackageRead, Boolean> p);
|
||||
void forEachPackage(TriConsumer<ParsingPackageRead, Boolean, File> p);
|
||||
}
|
||||
|
||||
private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> {
|
||||
@@ -115,6 +120,8 @@ public class OverlayConfig {
|
||||
p)));
|
||||
}
|
||||
|
||||
ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions);
|
||||
|
||||
boolean foundConfigFile = false;
|
||||
ArrayList<ParsedOverlayInfo> packageManagerOverlayInfos = null;
|
||||
|
||||
@@ -123,7 +130,9 @@ public class OverlayConfig {
|
||||
final OverlayPartition partition = partitions.get(i);
|
||||
final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get();
|
||||
final ArrayList<ParsedConfiguration> partitionOverlays =
|
||||
OverlayConfigParser.getConfigurations(partition, scanner);
|
||||
OverlayConfigParser.getConfigurations(partition, scanner,
|
||||
activeApexesPerPartition.getOrDefault(partition.type,
|
||||
Collections.emptyList()));
|
||||
if (partitionOverlays != null) {
|
||||
foundConfigFile = true;
|
||||
overlays.addAll(partitionOverlays);
|
||||
@@ -145,7 +154,8 @@ public class OverlayConfig {
|
||||
// Filter out overlays not present in the partition.
|
||||
partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos);
|
||||
for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) {
|
||||
if (!partition.containsFile(partitionOverlayInfos.get(j).path)) {
|
||||
if (!partition.containsFile(partitionOverlayInfos.get(j)
|
||||
.getOriginalPartitionPath())) {
|
||||
partitionOverlayInfos.remove(j);
|
||||
}
|
||||
}
|
||||
@@ -292,16 +302,49 @@ public class OverlayConfig {
|
||||
private static ArrayList<ParsedOverlayInfo> getOverlayPackageInfos(
|
||||
@NonNull PackageProvider packageManager) {
|
||||
final ArrayList<ParsedOverlayInfo> overlays = new ArrayList<>();
|
||||
packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> {
|
||||
packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem,
|
||||
@Nullable File preInstalledApexPath) -> {
|
||||
if (p.getOverlayTarget() != null && isSystem) {
|
||||
overlays.add(new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(),
|
||||
p.getTargetSdkVersion(), p.isOverlayIsStatic(), p.getOverlayPriority(),
|
||||
new File(p.getBaseApkPath())));
|
||||
new File(p.getBaseApkPath()), preInstalledApexPath));
|
||||
}
|
||||
});
|
||||
return overlays;
|
||||
}
|
||||
|
||||
/** Returns a map of PartitionType to List of active APEX module names. */
|
||||
@NonNull
|
||||
private static ArrayMap<Integer, List<String>> getActiveApexes(
|
||||
@NonNull List<OverlayPartition> partitions) {
|
||||
// An Overlay in an APEX, which is an update of an APEX in a given partition,
|
||||
// is considered as belonging to that partition.
|
||||
ArrayMap<Integer, List<String>> result = new ArrayMap<>();
|
||||
for (OverlayPartition partition : partitions) {
|
||||
result.put(partition.type, new ArrayList<String>());
|
||||
}
|
||||
// Read from apex-info-list because ApexManager is not accessible to zygote.
|
||||
File apexInfoList = new File("/apex/apex-info-list.xml");
|
||||
if (apexInfoList.exists() && apexInfoList.canRead()) {
|
||||
try (FileInputStream stream = new FileInputStream(apexInfoList)) {
|
||||
List<ApexInfo> apexInfos = XmlParser.readApexInfoList(stream).getApexInfo();
|
||||
for (ApexInfo info : apexInfos) {
|
||||
if (info.getIsActive()) {
|
||||
for (OverlayPartition partition : partitions) {
|
||||
if (partition.containsPath(info.getPreinstalledModulePath())) {
|
||||
result.get(partition.type).add(info.getModuleName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error reading apex-info-list: " + e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Represents a single call to idmap create-multiple. */
|
||||
@VisibleForTesting
|
||||
public static class IdmapInvocation {
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Responsible for parsing configurations of Runtime Resource Overlays that control mutability,
|
||||
@@ -192,13 +193,19 @@ final class OverlayConfigParser {
|
||||
*/
|
||||
@Nullable
|
||||
static ArrayList<ParsedConfiguration> getConfigurations(
|
||||
@NonNull OverlayPartition partition, @Nullable OverlayScanner scanner) {
|
||||
if (partition.getOverlayFolder() == null) {
|
||||
return null;
|
||||
@NonNull OverlayPartition partition, @Nullable OverlayScanner scanner,
|
||||
@NonNull List<String> activeApexes) {
|
||||
if (scanner != null) {
|
||||
if (partition.getOverlayFolder() != null) {
|
||||
scanner.scanDir(partition.getOverlayFolder());
|
||||
}
|
||||
for (String apex : activeApexes) {
|
||||
scanner.scanDir(new File("/apex/" + apex + "/overlay/"));
|
||||
}
|
||||
}
|
||||
|
||||
if (scanner != null) {
|
||||
scanner.scanDir(partition.getOverlayFolder());
|
||||
if (partition.getOverlayFolder() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final File configFile = new File(partition.getOverlayFolder(), CONFIG_DEFAULT_FILENAME);
|
||||
|
||||
@@ -47,23 +47,38 @@ public class OverlayScanner {
|
||||
public final boolean isStatic;
|
||||
public final int priority;
|
||||
public final File path;
|
||||
@Nullable public final File preInstalledApexPath;
|
||||
|
||||
public ParsedOverlayInfo(String packageName, String targetPackageName,
|
||||
int targetSdkVersion, boolean isStatic, int priority, File path) {
|
||||
int targetSdkVersion, boolean isStatic, int priority, File path,
|
||||
@Nullable File preInstalledApexPath) {
|
||||
this.packageName = packageName;
|
||||
this.targetPackageName = targetPackageName;
|
||||
this.targetSdkVersion = targetSdkVersion;
|
||||
this.isStatic = isStatic;
|
||||
this.priority = priority;
|
||||
this.path = path;
|
||||
this.preInstalledApexPath = preInstalledApexPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + String.format("{packageName=%s"
|
||||
+ ", targetPackageName=%s, targetSdkVersion=%s, isStatic=%s"
|
||||
+ ", priority=%s, path=%s}",
|
||||
packageName, targetPackageName, targetSdkVersion, isStatic, priority, path);
|
||||
+ ", priority=%s, path=%s, preInstalledApexPath=%s}",
|
||||
packageName, targetPackageName, targetSdkVersion, isStatic,
|
||||
priority, path, preInstalledApexPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the path of the overlay in its original installation partition.
|
||||
*
|
||||
* An Overlay in an APEX, which is an update of an APEX in a given partition,
|
||||
* is considered as belonging to that partition.
|
||||
*/
|
||||
@NonNull
|
||||
public File getOriginalPartitionPath() {
|
||||
return preInstalledApexPath != null ? preInstalledApexPath : path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +153,6 @@ public class OverlayScanner {
|
||||
return apkLite.getTargetPackageName() == null ? null :
|
||||
new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
|
||||
apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
|
||||
apkLite.getOverlayPriority(), new File(apkLite.getPath()));
|
||||
apkLite.getOverlayPriority(), new File(apkLite.getPath()), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,14 @@ bool FileDescriptorAllowlist::IsAllowed(const std::string& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow Runtime Resource Overlays inside APEXes.
|
||||
static const char* kOverlayPathSuffix = "/overlay";
|
||||
if (android::base::StartsWith(path, kApexPrefix) &&
|
||||
android::base::EndsWith(android::base::Dirname(path), kOverlayPathSuffix) &&
|
||||
android::base::EndsWith(path, kApkSuffix) && path.find("/../") == std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char* kOverlayIdmapPrefix = "/data/resource-cache/";
|
||||
static const char* kOverlayIdmapSuffix = ".apk@idmap";
|
||||
if (android::base::StartsWith(path, kOverlayIdmapPrefix) &&
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.util.ArrayMap;
|
||||
import com.android.internal.content.om.OverlayConfig.PackageProvider;
|
||||
import com.android.internal.content.om.OverlayScanner;
|
||||
import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
|
||||
import com.android.internal.util.function.TriConsumer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.rules.TestRule;
|
||||
@@ -39,7 +40,6 @@ import org.mockito.invocation.InvocationOnMock;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ public class OverlayConfigIterationRule implements TestRule {
|
||||
final File canonicalPath = new File(path.getCanonicalPath());
|
||||
mOverlayStubResults.put(canonicalPath, new ParsedOverlayInfo(
|
||||
packageName, targetPackage, targetSdkVersion, isStatic, priority,
|
||||
canonicalPath));
|
||||
canonicalPath, null));
|
||||
} catch (IOException e) {
|
||||
Assert.fail("Failed to add overlay " + e);
|
||||
}
|
||||
@@ -135,8 +135,8 @@ public class OverlayConfigIterationRule implements TestRule {
|
||||
mIteration = Iteration.SYSTEM_SERVER;
|
||||
doAnswer((InvocationOnMock invocation) -> {
|
||||
final Object[] args = invocation.getArguments();
|
||||
final BiConsumer<ParsingPackageRead, Boolean> f =
|
||||
(BiConsumer<ParsingPackageRead, Boolean>) args[0];
|
||||
final TriConsumer<ParsingPackageRead, Boolean, File> f =
|
||||
(TriConsumer<ParsingPackageRead, Boolean, File>) args[0];
|
||||
for (Map.Entry<File, ParsedOverlayInfo> overlay :
|
||||
mOverlayStubResults.entrySet()) {
|
||||
final ParsingPackageRead a = Mockito.mock(ParsingPackageRead.class);
|
||||
@@ -147,7 +147,8 @@ public class OverlayConfigIterationRule implements TestRule {
|
||||
when(a.isOverlayIsStatic()).thenReturn(info.isStatic);
|
||||
when(a.getOverlayPriority()).thenReturn(info.priority);
|
||||
when(a.getBaseApkPath()).thenReturn(info.path.getPath());
|
||||
f.accept(a, !info.path.getPath().contains("data/overlay"));
|
||||
f.accept(a, !info.path.getPath().contains("data/overlay"),
|
||||
/*preInstalledApexPath=*/null);
|
||||
}
|
||||
return null;
|
||||
}).when(mPkgProvider).forEachPackage(any());
|
||||
|
||||
@@ -7337,9 +7337,16 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
|
||||
// Parse overlay configuration files to set default enable state, mutability, and
|
||||
// priority of system overlays.
|
||||
final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
|
||||
for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {
|
||||
for (String packageName : mApexManager.getApksInApex(apexInfo.apexModuleName)) {
|
||||
apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
|
||||
}
|
||||
}
|
||||
mOverlayConfig = OverlayConfig.initializeSystemInstance(
|
||||
consumer -> mPmInternal.forEachPackage(
|
||||
pkg -> consumer.accept(pkg, pkg.isSystem())));
|
||||
pkg -> consumer.accept(pkg, pkg.isSystem(),
|
||||
apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
|
||||
|
||||
// Prune any system packages that no longer exist.
|
||||
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
|
||||
|
||||
Reference in New Issue
Block a user