diff --git a/Android.bp b/Android.bp index 0c1095d82be84..e1cb037b252e0 100644 --- a/Android.bp +++ b/Android.bp @@ -297,6 +297,7 @@ java_defaults { srcs: [ ":framework-non-updatable-sources", "core/java/**/*.logtags", + ":apex-info-list", ], aidl: { generate_get_transaction_name: true, diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java index 6c73e7073caeb..29b31d37e862f 100644 --- a/core/java/com/android/internal/content/om/OverlayConfig.java +++ b/core/java/com/android/internal/content/om/OverlayConfig.java @@ -26,20 +26,25 @@ import android.util.ArrayMap; import android.util.IndentingPrintWriter; 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.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import java.util.function.Supplier; /** @@ -77,7 +82,7 @@ public class OverlayConfig { public interface PackageProvider { /** Performs the given action for each package. */ - void forEachPackage(BiConsumer p); + void forEachPackage(TriConsumer p); } private static final Comparator sStaticOverlayComparator = (c1, c2) -> { @@ -119,6 +124,8 @@ public class OverlayConfig { p))); } + ArrayMap> activeApexesPerPartition = getActiveApexes(partitions); + boolean foundConfigFile = false; final Map packageManagerOverlayInfos = packageProvider == null ? null : getOverlayPackageInfos(packageProvider); @@ -129,7 +136,9 @@ public class OverlayConfig { final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get(); final ArrayList partitionOverlays = OverlayConfigParser.getConfigurations(partition, scanner, - packageManagerOverlayInfos); + packageManagerOverlayInfos, + activeApexesPerPartition.getOrDefault(partition.type, + Collections.emptyList())); if (partitionOverlays != null) { foundConfigFile = true; overlays.addAll(partitionOverlays); @@ -147,7 +156,8 @@ public class OverlayConfig { // Filter out overlays not present in the partition. partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos.values()); 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); } } @@ -294,16 +304,50 @@ public class OverlayConfig { private static Map getOverlayPackageInfos( @NonNull PackageProvider packageManager) { final HashMap overlays = new HashMap<>(); - packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> { + packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem, + @Nullable File preInstalledApexPath) -> { if (p.getOverlayTarget() != null && isSystem) { overlays.put(p.getPackageName(), new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(), p.getTargetSdkVersion(), p.isOverlayIsStatic(), - p.getOverlayPriority(), new File(p.getBaseApkPath()))); + p.getOverlayPriority(), new File(p.getBaseApkPath()), + preInstalledApexPath)); } }); return overlays; } + /** Returns a map of PartitionType to List of active APEX module names. */ + @NonNull + private static ArrayMap> getActiveApexes( + @NonNull List 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> result = new ArrayMap<>(); + for (OverlayPartition partition : partitions) { + result.put(partition.type, new ArrayList()); + } + // 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 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 { diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java index 053a341dc83ea..0ab7b3d2ef867 100644 --- a/core/java/com/android/internal/content/om/OverlayConfigParser.java +++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java @@ -41,6 +41,7 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -195,13 +196,19 @@ final class OverlayConfigParser { @Nullable static ArrayList getConfigurations( @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner, - @Nullable Map packageManagerOverlayInfos) { - if (partition.getOverlayFolder() == null) { - return null; + @Nullable Map packageManagerOverlayInfos, + @NonNull List 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); diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java index 4387d1bd12009..e4e0228a04291 100644 --- a/core/java/com/android/internal/content/om/OverlayScanner.java +++ b/core/java/com/android/internal/content/om/OverlayScanner.java @@ -54,23 +54,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; } } @@ -189,6 +204,6 @@ public class OverlayScanner { } return new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(), apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(), - apkLite.getOverlayPriority(), new File(apkLite.getPath())); + apkLite.getOverlayPriority(), new File(apkLite.getPath()), null); } } diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 6f5cc5314d0be..40f6e4f63cd77 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -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) && diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java index 3c093d89723d5..2c31b08bebdc6 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java @@ -30,6 +30,7 @@ import android.util.Pair; 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; @@ -42,7 +43,6 @@ import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import java.util.function.Supplier; /** @@ -74,7 +74,7 @@ public class OverlayConfigIterationRule implements TestRule { TestOverlayInfo(String packageName, String targetPackageName, int targetSdkVersion, boolean isStatic, int priority, File path, String requiredSystemPropertyName, String requiredSystemPropertyValue) { - super(packageName, targetPackageName, targetSdkVersion, isStatic, priority, path); + super(packageName, targetPackageName, targetSdkVersion, isStatic, priority, path, null); this.requiredSystemPropertyName = requiredSystemPropertyName; this.requiredSystemPropertyValue = requiredSystemPropertyValue; } @@ -174,8 +174,8 @@ public class OverlayConfigIterationRule implements TestRule { mIteration = Iteration.SYSTEM_SERVER; doAnswer((InvocationOnMock invocation) -> { final Object[] args = invocation.getArguments(); - final BiConsumer f = - (BiConsumer) args[0]; + final TriConsumer f = + (TriConsumer) args[0]; for (Map.Entry overlay : mTestOverlayInfos.entrySet()) { final ParsingPackageRead a = Mockito.mock(ParsingPackageRead.class); @@ -191,7 +191,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()); diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java index 722198f362df7..5ff0a6f7a59e1 100644 --- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java +++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java @@ -172,9 +172,16 @@ final class InitAndSystemPackageHelper { scanSystemDirs(packageParser, executorService); // Parse overlay configuration files to set default enable state, mutability, and // priority of system overlays. + final ArrayMap apkInApexPreInstalledPaths = new ArrayMap<>(); + for (ApexManager.ActiveApexInfo apexInfo : mPm.mApexManager.getActiveApexInfos()) { + for (String packageName : mPm.mApexManager.getApksInApex(apexInfo.apexModuleName)) { + apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath); + } + } OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance( consumer -> mPm.forEachPackage( - pkg -> consumer.accept(pkg, pkg.isSystem()))); + pkg -> consumer.accept(pkg, pkg.isSystem(), + apkInApexPreInstalledPaths.get(pkg.getPackageName())))); cleanupSystemPackagesAndInstallStubs(packageParser, executorService, packageSettings, startTime, userIds); packageParser.close();