summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/idmap2/Android.bp1
-rw-r--r--cmds/idmap2/idmap2/Commands.h1
-rw-r--r--cmds/idmap2/idmap2/CreateMultiple.cpp144
-rw-r--r--cmds/idmap2/idmap2/Main.cpp4
-rw-r--r--core/java/android/content/pm/PackageParser.java50
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java35
-rw-r--r--core/java/android/content/res/AssetManager.java14
-rw-r--r--core/java/com/android/internal/content/om/OverlayConfig.java412
-rw-r--r--core/java/com/android/internal/content/om/OverlayConfigParser.java391
-rw-r--r--core/java/com/android/internal/content/om/OverlayScanner.java138
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/com_android_internal_content_om_OverlayConfig.cpp122
-rw-r--r--core/tests/coretests/Android.bp1
-rw-r--r--core/tests/coretests/apks/overlay_config/Android.bp4
-rw-r--r--core/tests/coretests/apks/overlay_config/AndroidManifest.xml26
-rw-r--r--core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java162
-rw-r--r--core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java603
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java49
19 files changed, 2108 insertions, 52 deletions
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 41a17064c3ba..66f5c3908e4b 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -146,6 +146,7 @@ cc_binary {
host_supported: true,
srcs: [
"idmap2/Create.cpp",
+ "idmap2/CreateMultiple.cpp",
"idmap2/Dump.cpp",
"idmap2/Lookup.cpp",
"idmap2/Main.cpp",
diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h
index 718e361b38ab..e626738a2895 100644
--- a/cmds/idmap2/idmap2/Commands.h
+++ b/cmds/idmap2/idmap2/Commands.h
@@ -23,6 +23,7 @@
#include "idmap2/Result.h"
android::idmap2::Result<android::idmap2::Unit> Create(const std::vector<std::string>& args);
+android::idmap2::Result<android::idmap2::Unit> CreateMultiple(const std::vector<std::string>& args);
android::idmap2::Result<android::idmap2::Unit> Dump(const std::vector<std::string>& args);
android::idmap2::Result<android::idmap2::Unit> Lookup(const std::vector<std::string>& args);
android::idmap2::Result<android::idmap2::Unit> Scan(const std::vector<std::string>& args);
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
new file mode 100644
index 000000000000..0b0541fb6221
--- /dev/null
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <sys/stat.h> // umask
+#include <sys/types.h> // umask
+
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/Policies.h"
+#include "idmap2/SysTrace.h"
+
+using android::ApkAssets;
+using android::base::StringPrintf;
+using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Error;
+using android::idmap2::Idmap;
+using android::idmap2::PoliciesToBitmask;
+using android::idmap2::PolicyBitmask;
+using android::idmap2::PolicyFlags;
+using android::idmap2::Result;
+using android::idmap2::Unit;
+using android::idmap2::utils::kIdmapCacheDir;
+using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::UidHasWriteAccessToPath;
+
+Result<Unit> CreateMultiple(const std::vector<std::string>& args) {
+ SYSTRACE << "CreateMultiple " << args;
+ std::string target_apk_path;
+ std::string idmap_dir = kIdmapCacheDir;
+ std::vector<std::string> overlay_apk_paths;
+ std::vector<std::string> policies;
+ bool ignore_overlayable = false;
+
+ const CommandLineOptions opts =
+ CommandLineOptions("idmap2 create-multiple")
+ .MandatoryOption("--target-apk-path",
+ "input: path to apk which will have its resources overlaid",
+ &target_apk_path)
+ .MandatoryOption("--overlay-apk-path",
+ "input: path to apk which contains the new resource values",
+ &overlay_apk_paths)
+ .OptionalOption("--idmap-dir",
+ StringPrintf("output: path to the directory in which to write idmap file"
+ " (defaults to %s)",
+ kIdmapCacheDir),
+ &idmap_dir)
+ .OptionalOption("--policy",
+ "input: an overlayable policy this overlay fulfills"
+ " (if none or supplied, the overlay policy will default to \"public\")",
+ &policies)
+ .OptionalFlag("--ignore-overlayable", "disables overlayable and policy checks",
+ &ignore_overlayable);
+ const auto opts_ok = opts.Parse(args);
+ if (!opts_ok) {
+ return opts_ok.GetError();
+ }
+
+ PolicyBitmask fulfilled_policies = 0;
+ auto conv_result = PoliciesToBitmask(policies);
+ if (conv_result) {
+ fulfilled_policies |= *conv_result;
+ } else {
+ return conv_result.GetError();
+ }
+
+ if (fulfilled_policies == 0) {
+ fulfilled_policies |= PolicyFlags::POLICY_PUBLIC;
+ }
+
+ const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ if (!target_apk) {
+ return Error("failed to load apk %s", target_apk_path.c_str());
+ }
+
+ std::vector<std::string> idmap_paths;
+ for (const std::string& overlay_apk_path : overlay_apk_paths) {
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(idmap_dir, overlay_apk_path);
+ const uid_t uid = getuid();
+ if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+ LOG(WARNING) << "uid " << uid << "does not have write access to " << idmap_path.c_str();
+ continue;
+ }
+
+ const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ if (!overlay_apk) {
+ LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
+ continue;
+ }
+
+ const auto idmap =
+ Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable);
+ if (!idmap) {
+ LOG(WARNING) << "failed to create idmap";
+ continue;
+ }
+
+ umask(kIdmapFilePermissionMask);
+ std::ofstream fout(idmap_path);
+ if (fout.fail()) {
+ LOG(WARNING) << "failed to open idmap path " << idmap_path.c_str();
+ continue;
+ }
+
+ BinaryStreamVisitor visitor(fout);
+ (*idmap)->accept(&visitor);
+ fout.close();
+ if (fout.fail()) {
+ LOG(WARNING) << "failed to write to idmap path %s" << idmap_path.c_str();
+ continue;
+ }
+
+ idmap_paths.emplace_back(idmap_path);
+ }
+
+ for (const std::string& idmap_path : idmap_paths) {
+ std::cout << idmap_path << std::endl;
+ }
+
+ return Unit{};
+}
diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp
index 87949085cf1d..a07e793d9f47 100644
--- a/cmds/idmap2/idmap2/Main.cpp
+++ b/cmds/idmap2/idmap2/Main.cpp
@@ -53,7 +53,9 @@ void PrintUsage(const NameToFunctionMap& commands, std::ostream& out) {
int main(int argc, char** argv) {
SYSTRACE << "main";
const NameToFunctionMap commands = {
- {"create", Create}, {"dump", Dump}, {"lookup", Lookup}, {"scan", Scan}, {"verify", Verify},
+ {"create", Create}, {"create-multiple", CreateMultiple},
+ {"dump", Dump}, {"lookup", Lookup},
+ {"scan", Scan}, {"verify", Verify},
};
if (argc <= 1) {
PrintUsage(commands, std::cerr);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index b6b27b6e514a..5be5afbd4918 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -484,6 +484,9 @@ public class PackageParser {
public final boolean isolatedSplits;
public final boolean isSplitRequired;
public final boolean useEmbeddedDex;
+ public final String targetPackageName;
+ public final boolean overlayIsStatic;
+ public final int overlayPriority;
public ApkLite(String codePath, String packageName, String splitName,
boolean isFeatureSplit,
@@ -493,6 +496,7 @@ public class PackageParser {
SigningDetails signingDetails, boolean coreApp,
boolean debuggable, boolean multiArch, boolean use32bitAbi,
boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits,
+ String targetPackageName, boolean overlayIsStatic, int overlayPriority,
int minSdkVersion, int targetSdkVersion) {
this.codePath = codePath;
this.packageName = packageName;
@@ -514,6 +518,9 @@ public class PackageParser {
this.extractNativeLibs = extractNativeLibs;
this.isolatedSplits = isolatedSplits;
this.isSplitRequired = isSplitRequired;
+ this.targetPackageName = targetPackageName;
+ this.overlayIsStatic = overlayIsStatic;
+ this.overlayPriority = overlayPriority;
this.minSdkVersion = minSdkVersion;
this.targetSdkVersion = targetSdkVersion;
}
@@ -1797,6 +1804,12 @@ public class PackageParser {
boolean useEmbeddedDex = false;
String configForSplit = null;
String usesSplitName = null;
+ String targetPackage = null;
+ boolean overlayIsStatic = false;
+ int overlayPriority = 0;
+
+ String requiredSystemPropertyName = null;
+ String requiredSystemPropertyValue = null;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
final String attr = attrs.getAttributeName(i);
@@ -1861,6 +1874,21 @@ public class PackageParser {
useEmbeddedDex = attrs.getAttributeBooleanValue(i, false);
}
}
+ } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) {
+ for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+ final String attr = attrs.getAttributeName(i);
+ if ("requiredSystemPropertyName".equals(attr)) {
+ requiredSystemPropertyName = attrs.getAttributeValue(i);
+ } else if ("requiredSystemPropertyValue".equals(attr)) {
+ requiredSystemPropertyValue = attrs.getAttributeValue(i);
+ } else if ("targetPackage".equals(attr)) {
+ targetPackage = attrs.getAttributeValue(i);;
+ } else if ("isStatic".equals(attr)) {
+ overlayIsStatic = attrs.getAttributeBooleanValue(i, false);
+ } else if ("priority".equals(attr)) {
+ overlayPriority = attrs.getAttributeIntValue(i, 0);
+ }
+ }
} else if (TAG_USES_SPLIT.equals(parser.getName())) {
if (usesSplitName != null) {
Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
@@ -1887,11 +1915,22 @@ public class PackageParser {
}
}
+ // Check to see if overlay should be excluded based on system property condition
+ if (!checkRequiredSystemProperty(requiredSystemPropertyName,
+ requiredSystemPropertyValue)) {
+ Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+ + codePath + ": overlay ignored due to required system property: "
+ + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue);
+ targetPackage = null;
+ overlayIsStatic = false;
+ overlayPriority = 0;
+ }
+
return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor,
revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable,
multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits,
- minSdkVersion, targetSdkVersion);
+ targetPackage, overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion);
}
/**
@@ -2175,7 +2214,7 @@ public class PackageParser {
}
// check to see if overlay should be excluded based on system property condition
- if (!checkOverlayRequiredSystemProperty(propName, propValue)) {
+ if (!checkRequiredSystemProperty(propName, propValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and "
+ pkg.baseCodePath+ ": overlay ignored due to required system property: "
+ propName + " with value: " + propValue);
@@ -2603,8 +2642,11 @@ public class PackageParser {
return pkg;
}
- private boolean checkOverlayRequiredSystemProperty(String propName, String propValue) {
-
+ /**
+ * Returns {@code true} if both the property name and value are empty or if the given system
+ * property is set to the specified value. In all other cases, returns {@code false}
+ */
+ public static boolean checkRequiredSystemProperty(String propName, String propValue) {
if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
// malformed condition - incomplete
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5be9c910744d..9087f422cef2 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -289,6 +289,12 @@ public class ApkLiteParseUtils {
boolean useEmbeddedDex = false;
String configForSplit = null;
String usesSplitName = null;
+ String targetPackage = null;
+ boolean overlayIsStatic = false;
+ int overlayPriority = 0;
+
+ String requiredSystemPropertyName = null;
+ String requiredSystemPropertyValue = null;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
final String attr = attrs.getAttributeName(i);
@@ -365,6 +371,21 @@ public class ApkLiteParseUtils {
break;
}
}
+ } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) {
+ for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+ final String attr = attrs.getAttributeName(i);
+ if ("requiredSystemPropertyName".equals(attr)) {
+ requiredSystemPropertyName = attrs.getAttributeValue(i);
+ } else if ("requiredSystemPropertyValue".equals(attr)) {
+ requiredSystemPropertyValue = attrs.getAttributeValue(i);
+ } else if ("targetPackage".equals(attr)) {
+ targetPackage = attrs.getAttributeValue(i);;
+ } else if ("isStatic".equals(attr)) {
+ overlayIsStatic = attrs.getAttributeBooleanValue(i, false);
+ } else if ("priority".equals(attr)) {
+ overlayPriority = attrs.getAttributeIntValue(i, 0);
+ }
+ }
} else if (PackageParser.TAG_USES_SPLIT.equals(parser.getName())) {
if (usesSplitName != null) {
Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
@@ -391,11 +412,23 @@ public class ApkLiteParseUtils {
}
}
+ // Check to see if overlay should be excluded based on system property condition
+ if (!PackageParser.checkRequiredSystemProperty(requiredSystemPropertyName,
+ requiredSystemPropertyValue)) {
+ Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+ + codePath + ": overlay ignored due to required system property: "
+ + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue);
+ targetPackage = null;
+ overlayIsStatic = false;
+ overlayPriority = 0;
+ }
+
return new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second,
isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode,
versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs,
- isolatedSplits, minSdkVersion, targetSdkVersion);
+ isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, minSdkVersion,
+ targetSdkVersion);
}
public static VerifierInfo parseVerifier(AttributeSet attrs) {
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 1b0175812949..f295f8c531e9 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -38,6 +38,7 @@ import android.util.TypedValue;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayConfig;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -242,14 +243,11 @@ public final class AssetManager implements AutoCloseable {
try {
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
apkAssets.add(ApkAssets.loadFromPath(frameworkPath, true /*system*/));
- final String[] systemIdmapPaths = nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
- if (systemIdmapPaths != null) {
- for (String idmapPath : systemIdmapPaths) {
- apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
- }
- } else {
- Log.w(TAG, "'idmap2 --scan' failed: no static=\"true\" overlays targeting "
- + "\"android\" will be loaded");
+
+ final String[] systemIdmapPaths =
+ OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote();
+ for (String idmapPath : systemIdmapPaths) {
+ apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
}
sSystemApkAssetsSet = new ArraySet<>(apkAssets);
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
new file mode 100644
index 000000000000..1a862fa016f5
--- /dev/null
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2020 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.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackagePartitions;
+import android.content.pm.parsing.AndroidPackage;
+import android.os.Build;
+import android.os.Process;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Log;
+
+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 java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Responsible for reading overlay configuration files and handling queries of overlay mutability,
+ * default-enabled state, and priority.
+ *
+ * @see OverlayConfigParser
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+final public class OverlayConfig {
+ static final String TAG = "OverlayConfig";
+
+ // The default priority of an overlay that has not been configured. Overlays with default
+ // priority have a higher precedence than configured overlays.
+ private static final int DEFAULT_PRIORITY = Integer.MAX_VALUE;
+
+ @VisibleForTesting
+ public static final class Configuration {
+ @Nullable
+ public final ParsedConfiguration parsedConfig;
+
+ public final int configIndex;
+
+ public Configuration(@Nullable ParsedConfiguration parsedConfig, int configIndex) {
+ this.parsedConfig = parsedConfig;
+ this.configIndex = configIndex;
+ }
+ }
+
+ /**
+ * Interface for providing information on scanned packages.
+ * TODO(147840005): Remove this when android:isStatic and android:priority are fully deprecated
+ */
+ public interface AndroidPackageProvider {
+
+ /** Performs the given action for each package. */
+ void forEachPackage(Consumer<AndroidPackage> p);
+ }
+
+ private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> {
+ final ParsedOverlayInfo o1 = c1.parsedInfo;
+ final ParsedOverlayInfo o2 = c2.parsedInfo;
+ Preconditions.checkArgument(o1.isStatic && o2.isStatic,
+ "attempted to sort non-static overlay");
+
+ if (!o1.targetPackageName.equals(o2.targetPackageName)) {
+ return o1.targetPackageName.compareTo(o2.targetPackageName);
+ }
+
+ final int comparedPriority = o1.priority - o2.priority;
+ return comparedPriority == 0 ? o1.path.compareTo(o2.path) : comparedPriority;
+ };
+
+ // Map of overlay package name to configured overlay settings
+ private final ArrayMap<String, Configuration> mConfigurations = new ArrayMap<>();
+
+ // Singleton instance only assigned in system server
+ private static OverlayConfig sInstance;
+
+ @VisibleForTesting
+ public OverlayConfig(@Nullable File rootDirectory,
+ @Nullable Supplier<OverlayScanner> scannerFactory,
+ @Nullable AndroidPackageProvider packageProvider) {
+ Preconditions.checkArgument((scannerFactory == null) != (packageProvider == null),
+ "scannerFactory and packageProvider cannot be both null or both non-null");
+
+ final ArrayList<OverlayPartition> partitions;
+ if (rootDirectory == null) {
+ partitions = new ArrayList<>(
+ PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+ } else {
+ // Rebase the system partitions and settings file on the specified root directory.
+ partitions = new ArrayList<>(PackagePartitions.getOrderedPartitions(
+ p -> new OverlayPartition(new File(rootDirectory, p.folder.getPath()), p)));
+ }
+
+ boolean foundConfigFile = false;
+ ArrayList<ParsedOverlayInfo> packageManagerOverlayInfos = null;
+
+ final ArrayList<ParsedConfiguration> overlays = new ArrayList<>();
+ for (int i = 0, n = partitions.size(); i < n; i++) {
+ final OverlayPartition partition = partitions.get(i);
+ final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get();
+ final ArrayList<ParsedConfiguration> partitionOverlays =
+ OverlayConfigParser.getConfigurations(partition, scanner);
+ if (partitionOverlays != null) {
+ foundConfigFile = true;
+ overlays.addAll(partitionOverlays);
+ continue;
+ }
+
+ // If the configuration file is not present, then use android:isStatic and
+ // android:priority to configure the overlays in the partition.
+ // TODO(147840005): Remove converting static overlays to immutable, default-enabled
+ // overlays when android:siStatic and android:priority are fully deprecated.
+ final ArrayList<ParsedOverlayInfo> partitionOverlayInfos;
+ if (scannerFactory != null) {
+ partitionOverlayInfos = new ArrayList<>(scanner.getAllParsedInfos());
+ } else {
+ if (packageManagerOverlayInfos == null) {
+ packageManagerOverlayInfos = getOverlayPackageInfos(packageProvider);
+ }
+
+ // Filter out overlays not present in the partition.
+ partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos);
+ for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) {
+ if (!partition.containsPath(partitionOverlayInfos.get(j).path.getPath())) {
+ partitionOverlayInfos.remove(j);
+ }
+ }
+ }
+
+ // Static overlays are configured as immutable, default-enabled overlays.
+ final ArrayList<ParsedConfiguration> partitionConfigs = new ArrayList<>();
+ for (int j = 0, m = partitionOverlayInfos.size(); j < m; j++) {
+ final ParsedOverlayInfo p = partitionOverlayInfos.get(j);
+ if (p.isStatic) {
+ partitionConfigs.add(new ParsedConfiguration(p.packageName,
+ true /* enabled */, false /* mutable */, partition.policy, p));
+ }
+ }
+
+ partitionConfigs.sort(sStaticOverlayComparator);
+ overlays.addAll(partitionConfigs);
+ }
+
+ if (!foundConfigFile) {
+ // If no overlay configuration files exist, disregard partition precedence and allow
+ // android:priority to reorder overlays across partition boundaries.
+ overlays.sort(sStaticOverlayComparator);
+ }
+
+ for (int i = 0, n = overlays.size(); i < n; i++) {
+ // Add the configurations to a map so definitions of an overlay in an earlier
+ // partition can be replaced by an overlay with the same package name in a later
+ // partition.
+ final ParsedConfiguration config = overlays.get(i);
+ mConfigurations.put(config.packageName, new Configuration(config, i));
+ }
+ }
+
+ /**
+ * 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
+ * overlay directory.
+ */
+ @NonNull
+ public static OverlayConfig getZygoteInstance() {
+ if (Process.myUid() != Process.ROOT_UID) {
+ // Scan the overlays in the zygote process to generate configuration settings for
+ // overlays on the system image. Do not cache this instance so OverlayConfig will not
+ // be present in applications by default.
+ throw new IllegalStateException("Can only be invoked in the root process");
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#getZygoteInstance");
+ try {
+ return new OverlayConfig(null /* rootDirectory */, OverlayScanner::new,
+ null /* packageProvider */);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RRO);
+ }
+ }
+
+ /**
+ * Initializes a singleton instance for use in the system process.
+ * Can only be called once. This instance is cached so future invocations of
+ * {@link #getSystemInstance()} will return the initialized instance.
+ */
+ @NonNull
+ public static OverlayConfig initializeSystemInstance(AndroidPackageProvider packageProvider) {
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ throw new IllegalStateException("Can only be invoked in the system process");
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#initializeSystemInstance");
+ sInstance = new OverlayConfig(null, null, packageProvider);
+ Trace.traceEnd(Trace.TRACE_TAG_RRO);
+ return sInstance;
+ }
+
+ /**
+ * Retrieves the singleton instance initialized by
+ * {@link #initializeSystemInstance(AndroidPackageProvider)}.
+ */
+ @NonNull
+ public static OverlayConfig getSystemInstance() {
+ if (sInstance == null) {
+ throw new IllegalStateException("System instance not initialized");
+ }
+
+ return sInstance;
+ }
+
+ @VisibleForTesting
+ @Nullable
+ public Configuration getConfiguration(@NonNull String packageName) {
+ return mConfigurations.get(packageName);
+ }
+
+ /**
+ * Returns whether the overlay is enabled by default.
+ * Overlays that are not configured are disabled by default mutable.
+ */
+ public boolean isEnabled(String packageName) {
+ final Configuration config = mConfigurations.get(packageName);
+
+ // STOPSHIP(149499802): Enabling a mutable overlay currently has no effect. Either implement
+ // some behavior for default-enabled, mutable overlays or prevent parsing of the enabled
+ // attribute on overlays that are mutable.
+ if (config != null && config.parsedConfig.mutable) {
+ Log.w(TAG, "Default-enabled configuration for mutable overlay "
+ + config.parsedConfig.packageName + " has no effect");
+ return OverlayConfigParser.DEFAULT_ENABLED_STATE;
+ }
+
+ return config == null? OverlayConfigParser.DEFAULT_ENABLED_STATE
+ : config.parsedConfig.enabled;
+ }
+
+ /**
+ * Returns whether the overlay is mutable and can have its enabled state changed dynamically.
+ * Overlays that are not configured are mutable.
+ */
+ public boolean isMutable(String packageName) {
+ final Configuration config = mConfigurations.get(packageName);
+ return config == null ? OverlayConfigParser.DEFAULT_MUTABILITY
+ : config.parsedConfig.mutable;
+ }
+
+ /**
+ * Returns an integer corresponding to the priority of the overlay.
+ * When multiple overlays override the same resource, the overlay with the highest priority will
+ * will have its value chosen. Overlays that are not configured have a priority of
+ * {@link Integer#MAX_VALUE}.
+ */
+ public int getPriority(String packageName) {
+ final Configuration config = mConfigurations.get(packageName);
+ return config == null ? DEFAULT_PRIORITY : config.configIndex;
+ }
+
+ @NonNull
+ private ArrayList<Configuration> getSortedOverlays() {
+ final ArrayList<Configuration> sortedOverlays = new ArrayList<>();
+ for (int i = 0, n = mConfigurations.size(); i < n; i++) {
+ sortedOverlays.add(mConfigurations.valueAt(i));
+ }
+ sortedOverlays.sort(Comparator.comparingInt(o -> o.configIndex));
+ return sortedOverlays;
+ }
+
+ @NonNull
+ private static ArrayList<ParsedOverlayInfo> getOverlayPackageInfos(
+ @NonNull AndroidPackageProvider packageManager) {
+ final ArrayList<ParsedOverlayInfo> overlays = new ArrayList<>();
+ packageManager.forEachPackage((AndroidPackage p) -> {
+ if (p.getOverlayTarget() != null && p.isSystem()) {
+ overlays.add(new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(),
+ p.getTargetSdkVersion(), p.isOverlayIsStatic(), p.getOverlayPriority(),
+ new File(p.getBaseCodePath())));
+ }
+ });
+ return overlays;
+ }
+
+ /** Represents a single call to idmap create-multiple. */
+ @VisibleForTesting
+ public static class IdmapInvocation {
+ public final boolean enforceOverlayable;
+ public final String policy;
+ public final ArrayList<String> overlayPaths = new ArrayList<>();
+
+ IdmapInvocation(boolean enforceOverlayable, @NonNull String policy) {
+ this.enforceOverlayable = enforceOverlayable;
+ this.policy = policy;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + String.format("{enforceOverlayable=%s, policy=%s"
+ + ", overlayPaths=[%s]}", enforceOverlayable, policy,
+ String.join(", ", overlayPaths));
+ }
+ }
+
+ /**
+ * Retrieves a list of immutable framework overlays in order of least precedence to greatest
+ * precedence.
+ */
+ @VisibleForTesting
+ public ArrayList<IdmapInvocation> getImmutableFrameworkOverlayIdmapInvocations() {
+ final ArrayList<IdmapInvocation> idmapInvocations = new ArrayList<>();
+ final ArrayList<Configuration> sortedConfigs = getSortedOverlays();
+ for (int i = 0, n = sortedConfigs.size(); i < n; i++) {
+ final Configuration overlay = sortedConfigs.get(i);
+ if (overlay.parsedConfig.mutable || !overlay.parsedConfig.enabled
+ || !"android".equals(overlay.parsedConfig.parsedInfo.targetPackageName)) {
+ continue;
+ }
+
+ // Only enforce that overlays targeting packages with overlayable declarations abide by
+ // those declarations if the target sdk of the overlay is at least Q (when overlayable
+ // was introduced).
+ final boolean enforceOverlayable = overlay.parsedConfig.parsedInfo.targetSdkVersion
+ >= Build.VERSION_CODES.Q;
+
+ // Determine if the idmap for the current overlay can be generated in the last idmap
+ // create-multiple invocation.
+ IdmapInvocation invocation = null;
+ if (!idmapInvocations.isEmpty()) {
+ final IdmapInvocation last = idmapInvocations.get(idmapInvocations.size() - 1);
+ if (last.enforceOverlayable == enforceOverlayable
+ && last.policy.equals(overlay.parsedConfig.policy)) {
+ invocation = last;
+ }
+ }
+
+ if (invocation == null) {
+ invocation = new IdmapInvocation(enforceOverlayable, overlay.parsedConfig.policy);
+ idmapInvocations.add(invocation);
+ }
+
+ invocation.overlayPaths.add(overlay.parsedConfig.parsedInfo.path.getAbsolutePath());
+ }
+ return idmapInvocations;
+ }
+
+ /**
+ * Creates idmap files for immutable overlays targeting the framework packages. Currently the
+ * android package is the only preloaded system package. Only the zygote can invoke this method.
+ *
+ * @return the paths of the created idmap files
+ */
+ @NonNull
+ public String[] createImmutableFrameworkIdmapsInZygote() {
+ if (Process.myUid() != Process.ROOT_UID) {
+ throw new IllegalStateException("This method can only be called from the root process");
+ }
+
+ final String targetPath = "/system/framework/framework-res.apk";
+ final ArrayList<String> idmapPaths = new ArrayList<>();
+ final ArrayList<IdmapInvocation> idmapInvocations =
+ getImmutableFrameworkOverlayIdmapInvocations();
+
+ for (int i = 0, n = idmapInvocations.size(); i < n; i++) {
+ final IdmapInvocation invocation = idmapInvocations.get(i);
+ final String[] idmaps = createIdmap(targetPath,
+ invocation.overlayPaths.toArray(new String[0]),
+ new String[]{OverlayConfigParser.OverlayPartition.POLICY_PUBLIC,
+ invocation.policy},
+ invocation.enforceOverlayable);
+
+ if (idmaps == null) {
+ Log.w(TAG, "'idmap2 create-multiple' failed: no mutable=\"false\" overlays"
+ + " targeting \"android\" will be loaded");
+ return new String[0];
+ }
+
+ idmapPaths.addAll(Arrays.asList(idmaps));
+ }
+
+ return idmapPaths.toArray(new String[0]);
+ }
+
+ /**
+ * For each overlay APK, this creates the idmap file that allows the overlay to override the
+ * target package.
+ *
+ * @return the paths of the created idmap
+ */
+ private static native String[] createIdmap(@NonNull String targetPath,
+ @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable);
+}
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
new file mode 100644
index 000000000000..139607ff7196
--- /dev/null
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2020 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.om;
+
+import static com.android.internal.content.om.OverlayConfig.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackagePartitions;
+import android.content.pm.PackagePartitions.SystemPartition;
+import android.os.FileUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Responsible for parsing configurations of Runtime Resource Overlays that control mutability,
+ * default enable state, and priority. To configure an overlay, create or modify the file located
+ * at {@code partition}/overlay/config/config.xml where {@code partition} is the partition of the
+ * overlay to be configured. In order to be configured, an overlay must reside in the overlay
+ * directory of the partition in which the overlay is configured.
+ *
+ * @see #parseOverlay(File, XmlPullParser, OverlayScanner, ParsingContext)
+ * @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext)
+ **/
+final class OverlayConfigParser {
+
+ // Default values for overlay configurations.
+ static final boolean DEFAULT_ENABLED_STATE = false;
+ static final boolean DEFAULT_MUTABILITY = true;
+
+ // Maximum recursive depth of processing merge tags.
+ private static final int MAXIMUM_MERGE_DEPTH = 5;
+
+ // The subdirectory within a partition's overlay directory that contains the configuration files
+ // for the partition.
+ private static final String CONFIG_DIRECTORY = "config";
+
+ /**
+ * The name of the configuration file to parse for overlay configurations. This class does not
+ * scan for overlay configuration files within the {@link #CONFIG_DIRECTORY}; rather, other
+ * files can be included at a particular position within this file using the <merge> tag.
+ *
+ * @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext)
+ */
+ private static final String CONFIG_DEFAULT_FILENAME = CONFIG_DIRECTORY + "/config.xml";
+
+ /** Represents the configurations of a particular overlay. */
+ public static class ParsedConfiguration {
+ @NonNull
+ public final String packageName;
+
+ /** Whether or not the overlay is enabled by default. */
+ public final boolean enabled;
+
+ /**
+ * Whether or not the overlay is mutable and can have its enabled state changed dynamically
+ * using the {@code OverlayManagerService}.
+ **/
+ public final boolean mutable;
+
+ /** The policy granted to overlays on the partition in which the overlay is located. */
+ @NonNull
+ public final String policy;
+
+ /** Information extracted from the manifest of the overlay. */
+ @NonNull
+ public final ParsedOverlayInfo parsedInfo;
+
+ ParsedConfiguration(@NonNull String packageName, boolean enabled, boolean mutable,
+ @NonNull String policy, @NonNull ParsedOverlayInfo parsedInfo) {
+ this.packageName = packageName;
+ this.enabled = enabled;
+ this.mutable = mutable;
+ this.policy = policy;
+ this.parsedInfo = parsedInfo;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + String.format("{packageName=%s, enabled=%s"
+ + ", mutable=%s, policy=%s, parsedInfo=%s}", packageName, enabled,
+ mutable, policy, parsedInfo);
+ }
+ }
+
+ 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";
+ static final String POLICY_OEM = "oem";
+ static final String POLICY_PRODUCT = "product";
+ static final String POLICY_PUBLIC = "public";
+ static final String POLICY_SYSTEM = "system";
+ static final String POLICY_VENDOR = "vendor";
+
+ @NonNull
+ public final String policy;
+
+ OverlayPartition(@NonNull SystemPartition partition) {
+ super(partition);
+ this.policy = policyForPartition(partition);
+ }
+
+ /**
+ * Creates a partition containing the same folders as the original partition but with a
+ * different root folder.
+ */
+ OverlayPartition(@NonNull File folder, @NonNull SystemPartition original) {
+ super(folder, original);
+ this.policy = policyForPartition(original);
+ }
+
+ private static String policyForPartition(SystemPartition partition) {
+ switch (partition.type) {
+ case PackagePartitions.PARTITION_SYSTEM:
+ case PackagePartitions.PARTITION_SYSTEM_EXT:
+ return POLICY_SYSTEM;
+ case PackagePartitions.PARTITION_VENDOR:
+ return POLICY_VENDOR;
+ case PackagePartitions.PARTITION_ODM:
+ return POLICY_ODM;
+ case PackagePartitions.PARTITION_OEM:
+ return POLICY_OEM;
+ case PackagePartitions.PARTITION_PRODUCT:
+ return POLICY_PRODUCT;
+ default:
+ throw new IllegalStateException("Unable to determine policy for "
+ + partition.folder);
+ }
+ }
+ }
+
+ /** This class holds state related to parsing the configurations of a partition. */
+ private static class ParsingContext {
+ // The overlay directory of the partition
+ private final OverlayPartition mPartition;
+
+ // The ordered list of configured overlays
+ private final ArrayList<ParsedConfiguration> mOrderedConfigurations = new ArrayList<>();
+
+ // The packages configured in the partition
+ private final ArraySet<String> mConfiguredOverlays = new ArraySet<>();
+
+ // Whether an mutable overlay has been configured in the partition
+ private boolean mFoundMutableOverlay;
+
+ // The current recursive depth of merging configuration files
+ private int mMergeDepth;
+
+ private ParsingContext(OverlayPartition partition) {
+ mPartition = partition;
+ }
+ }
+
+ /**
+ * Retrieves overlays configured within the partition in increasing priority order.
+ *
+ * If {@code scanner} is null, then the {@link ParsedConfiguration#parsedInfo} fields of the
+ * added configured overlays will be null and the parsing logic will not assert that the
+ * configured overlays exist within the partition.
+ *
+ * @return list of configured overlays if configuration file exists; otherwise, null
+ */
+ @Nullable
+ static ArrayList<ParsedConfiguration> getConfigurations(
+ @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner) {
+ if (partition.getOverlayFolder() == null) {
+ return null;
+ }
+
+ if (scanner != null) {
+ scanner.scanDir(partition.getOverlayFolder());
+ }
+
+ final File configFile = new File(partition.getOverlayFolder(), CONFIG_DEFAULT_FILENAME);
+ if (!configFile.exists()) {
+ return null;
+ }
+
+ final ParsingContext parsingContext = new ParsingContext(partition);
+ readConfigFile(configFile, scanner, parsingContext);
+ return parsingContext.mOrderedConfigurations;
+ }
+
+ private static void readConfigFile(@NonNull File configFile, @Nullable OverlayScanner scanner,
+ @NonNull ParsingContext parsingContext) {
+ FileReader configReader;
+ try {
+ configReader = new FileReader(configFile);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Couldn't find or open overlay configuration file " + configFile);
+ return;
+ }
+
+ try {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(configReader);
+ XmlUtils.beginDocument(parser, "config");
+
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ final String name = parser.getName();
+ switch (name) {
+ case "merge":
+ parseMerge(configFile, parser, scanner, parsingContext);
+ break;
+ case "overlay":
+ parseOverlay(configFile, parser, scanner, parsingContext);
+ break;
+ default:
+ Log.w(TAG, String.format("Tag %s is unknown in %s at %s",
+ name, configFile, parser.getPositionDescription()));
+ break;
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Log.w(TAG, "Got exception parsing overlay configuration.", e);
+ } finally {
+ IoUtils.closeQuietly(configReader);
+ }
+ }
+
+ /**
+ * Parses a <merge> tag within an overlay configuration file.
+ *
+ * Merge tags allow for other configuration files to be "merged" at the current parsing
+ * position into the current configuration file being parsed. The {@code path} attribute of the
+ * tag represents the path of the file to merge relative to the directory containing overlay
+ * configuration files.
+ */
+ private static void parseMerge(@NonNull File configFile, @NonNull XmlPullParser parser,
+ @Nullable OverlayScanner scanner, @NonNull ParsingContext parsingContext) {
+ final String path = parser.getAttributeValue(null, "path");
+ if (path == null) {
+ throw new IllegalStateException(String.format("<merge> without path in %s at %s"
+ + configFile, parser.getPositionDescription()));
+ }
+
+ if (path.startsWith("/")) {
+ throw new IllegalStateException(String.format(
+ "Path %s must be relative to the directory containing overlay configurations "
+ + " files in %s at %s ", path, configFile,
+ parser.getPositionDescription()));
+ }
+
+ if (parsingContext.mMergeDepth++ == MAXIMUM_MERGE_DEPTH) {
+ throw new IllegalStateException(String.format(
+ "Maximum <merge> depth exceeded in %s at %s", configFile,
+ parser.getPositionDescription()));
+ }
+
+ final File configDirectory;
+ final File includedConfigFile;
+ try {
+ configDirectory = new File(parsingContext.mPartition.getOverlayFolder(),
+ CONFIG_DIRECTORY).getCanonicalFile();
+ includedConfigFile = new File(configDirectory, path).getCanonicalFile();
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ String.format("Couldn't find or open merged configuration file %s in %s at %s",
+ path, configFile, parser.getPositionDescription()), e);
+ }
+
+ if (!includedConfigFile.exists()) {
+ throw new IllegalStateException(
+ String.format("Merged configuration file %s does not exist in %s at %s",
+ path, configFile, parser.getPositionDescription()));
+ }
+
+ if (!FileUtils.contains(configDirectory, includedConfigFile)) {
+ throw new IllegalStateException(
+ String.format(
+ "Merged file %s outside of configuration directory in %s at %s",
+ includedConfigFile.getAbsolutePath(), includedConfigFile,
+ parser.getPositionDescription()));
+ }
+
+ readConfigFile(includedConfigFile, scanner, parsingContext);
+ parsingContext.mMergeDepth--;
+ }
+
+ /**
+ * Parses an <overlay> tag within an overlay configuration file.
+ *
+ * Requires a {@code package} attribute that indicates which package is being configured.
+ * The optional {@code enabled} attribute controls whether or not the overlay is enabled by
+ * default (default is false). The optional {@code mutable} attribute controls whether or
+ * not the overlay is mutable and can have its enabled state changed at runtime (default is
+ * true).
+ *
+ * The order in which overlays that override the same resources are configured matters. An
+ * overlay will have a greater priority than overlays with configurations preceding its own
+ * configuration.
+ *
+ * Configurations of immutable overlays must precede configurations of mutable overlays.
+ * An overlay cannot be configured in multiple locations. All configured overlay must exist
+ * within the partition of the configuration file. An overlay cannot be configured multiple
+ * times in a single partition.
+ *
+ * Overlays not listed within a configuration file will be mutable and disabled by default. The
+ * order of non-configured overlays when enabled by the OverlayManagerService is undefined.
+ */
+ private static void parseOverlay(@NonNull File configFile, @NonNull XmlPullParser parser,
+ @Nullable OverlayScanner scanner, @NonNull ParsingContext parsingContext) {
+ final String packageName = parser.getAttributeValue(null, "package");
+ if (packageName == null) {
+ throw new IllegalStateException(String.format("\"<overlay> without package in %s at %s",
+ configFile, parser.getPositionDescription()));
+ }
+
+ // Ensure the overlay being configured is present in the partition during zygote
+ // initialization.
+ ParsedOverlayInfo info = null;
+ if (scanner != null) {
+ info = scanner.getParsedInfo(packageName);
+ if (info == null|| !parsingContext.mPartition.containsOverlay(info.path)) {
+ throw new IllegalStateException(
+ String.format("overlay %s not present in partition %s in %s at %s",
+ packageName, parsingContext.mPartition.getOverlayFolder(),
+ configFile, parser.getPositionDescription()));
+ }
+ }
+
+ if (parsingContext.mConfiguredOverlays.contains(packageName)) {
+ throw new IllegalStateException(
+ String.format("overlay %s configured multiple times in a single partition"
+ + " in %s at %s", packageName, configFile,
+ parser.getPositionDescription()));
+ }
+
+ boolean isEnabled = DEFAULT_ENABLED_STATE;
+ final String enabled = parser.getAttributeValue(null, "enabled");
+ if (enabled != null) {
+ isEnabled = !"false".equals(enabled);
+ }
+
+ boolean isMutable = DEFAULT_MUTABILITY;
+ final String mutable = parser.getAttributeValue(null, "mutable");
+ if (mutable != null) {
+ isMutable = !"false".equals(mutable);
+ if (!isMutable && parsingContext.mFoundMutableOverlay) {
+ throw new IllegalStateException(String.format(
+ "immutable overlays must precede mutable overlays:"
+ + " found in %s at %s",
+ configFile, parser.getPositionDescription()));
+ }
+ }
+
+ if (isMutable) {
+ parsingContext.mFoundMutableOverlay = true;
+ } else if (!isEnabled) {
+ // Default disabled, immutable overlays may be a misconfiguration of the system so warn
+ // developers.
+ Log.w(TAG, "found default-disabled immutable overlay " + packageName);
+ }
+
+ final ParsedConfiguration Config = new ParsedConfiguration(packageName, isEnabled,
+ isMutable, parsingContext.mPartition.policy, info);
+ parsingContext.mConfiguredOverlays.add(packageName);
+ parsingContext.mOrderedConfigurations.add(Config);
+ }
+}
diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java
new file mode 100644
index 000000000000..a85cf56068cd
--- /dev/null
+++ b/core/java/com/android/internal/content/om/OverlayScanner.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 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.om;
+
+import static com.android.internal.content.om.OverlayConfig.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * This class scans a directory containing overlay APKs and extracts information from the overlay
+ * manifests by parsing the overlay manifests.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class OverlayScanner {
+
+ /** Represents information parsed from the manifest of an overlay. */
+ public static class ParsedOverlayInfo {
+ public final String packageName;
+ public final String targetPackageName;
+ public final int targetSdkVersion;
+ public final boolean isStatic;
+ public final int priority;
+ public final File path;
+
+ public ParsedOverlayInfo(String packageName, String targetPackageName,
+ int targetSdkVersion, boolean isStatic, int priority, File path) {
+ this.packageName = packageName;
+ this.targetPackageName = targetPackageName;
+ this.targetSdkVersion = targetSdkVersion;
+ this.isStatic = isStatic;
+ this.priority = priority;
+ this.path = path;
+ }
+
+ @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);
+ }
+ }
+
+ /**
+ * A map of overlay package name to the parsed manifest information of the latest version of
+ * the overlay.
+ */
+ private final ArrayMap<String, ParsedOverlayInfo> mParsedOverlayInfos = new ArrayMap<>();
+
+ /** Retrieves information parsed from the overlay with the package name. */
+ @Nullable
+ public final ParsedOverlayInfo getParsedInfo(String packageName) {
+ return mParsedOverlayInfos.get(packageName);
+ }
+
+ /** Retrieves all of the scanned overlays. */
+ @NonNull
+ final Collection<ParsedOverlayInfo> getAllParsedInfos() {
+ return mParsedOverlayInfos.values();
+ }
+
+ /**
+ * Recursively searches the directory for overlay APKs. If an overlay is found with the same
+ * package name as a previously scanned overlay, the info of the new overlay will replace the
+ * info of the previously scanned overlay.
+ */
+ public void scanDir(File partitionOverlayDir) {
+ if (!partitionOverlayDir.exists() || !partitionOverlayDir.isDirectory()) {
+ return;
+ }
+
+ if (!partitionOverlayDir.canRead()) {
+ Log.w(TAG, "Directory " + partitionOverlayDir + " cannot be read");
+ return;
+ }
+
+ final File[] files = partitionOverlayDir.listFiles();
+ if (files == null) {
+ return;
+ }
+
+ for (int i = 0; i < files.length; i++) {
+ final File f = files[i];
+ if (f.isDirectory()) {
+ scanDir(f);
+ }
+
+ if (!f.isFile() || !f.getPath().endsWith(".apk")) {
+ continue;
+ }
+
+ final ParsedOverlayInfo info = parseOverlayManifest(f);
+ if (info == null) {
+ continue;
+ }
+
+ mParsedOverlayInfos.put(info.packageName, info);
+ }
+ }
+
+ /** Extracts information about the overlay from its manifest. */
+ @VisibleForTesting
+ public ParsedOverlayInfo parseOverlayManifest(File overlayApk) {
+ try {
+ final PackageParser.ApkLite apkLite = PackageParser.parseApkLite(overlayApk, 0);
+ return apkLite.targetPackageName == null ? null :
+ new ParsedOverlayInfo(apkLite.packageName, apkLite.targetPackageName,
+ apkLite.targetSdkVersion, apkLite.overlayIsStatic,
+ apkLite.overlayPriority, new File(apkLite.codePath));
+ } catch (PackageParser.PackageParserException e) {
+ Log.w(TAG, "Got exception loading overlay.", e);
+ return null;
+ }
+ }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8959d6fb845e..8704805f8925 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -197,6 +197,7 @@ cc_library_shared {
"android_content_res_ObbScanner.cpp",
"android_content_res_Configuration.cpp",
"android_security_Scrypt.cpp",
+ "com_android_internal_content_om_OverlayConfig.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_Zygote.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b47b7e39d1e8..019b92f2f9c7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -187,6 +187,7 @@ extern int register_android_content_res_Configuration(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
extern int register_android_security_Scrypt(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
+extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
@@ -1511,6 +1512,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_android_os_SharedMemory),
REG_JNI(register_android_os_incremental_IncrementalManager),
+ REG_JNI(register_com_android_internal_content_om_OverlayConfig),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
REG_JNI(register_com_android_internal_os_Zygote),
REG_JNI(register_com_android_internal_os_ZygoteInit),
diff --git a/core/jni/com_android_internal_content_om_OverlayConfig.cpp b/core/jni/com_android_internal_content_om_OverlayConfig.cpp
new file mode 100644
index 000000000000..6aa7c10509bc
--- /dev/null
+++ b/core/jni/com_android_internal_content_om_OverlayConfig.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "core_jni_helpers.h"
+
+#include "android-base/logging.h"
+#include "androidfw/PosixUtils.h"
+
+using ::android::util::ExecuteBinary;
+
+static jclass g_stringClass = nullptr;
+
+static jobjectArray createIdmap(JNIEnv* env, jclass /*clazz*/, jstring targetPath,
+ jobjectArray overlayPath, jobjectArray policies,
+ jboolean enforceOverlayable) {
+ if (access("/system/bin/idmap2", X_OK) == -1) {
+ PLOG(WARNING) << "unable to execute idmap2";
+ return nullptr;
+ }
+
+ const char* targetApkPath = env->GetStringUTFChars(targetPath, NULL /* isCopy */);
+ std::vector<std::string> argv{"/system/bin/idmap2",
+ "create-multiple",
+ "--target-apk-path", targetApkPath,
+ };
+ env->ReleaseStringUTFChars(targetPath, targetApkPath);
+
+ // Add the overlays for which to generate idmap files to the idmap arguments.
+ for (size_t i = 0, count = env->GetArrayLength(overlayPath); i < count; ++i) {
+ jstring element = (jstring) env->GetObjectArrayElement(overlayPath, i);
+ const char* overlayApkPath = env->GetStringUTFChars(element, NULL /* isCopy */);
+ argv.emplace_back("--overlay-apk-path");
+ argv.emplace_back(overlayApkPath);
+ env->ReleaseStringUTFChars(element, overlayApkPath);
+ }
+
+ // Add the policies the overlays fulfill to the idmap arguments.
+ for (size_t i = 0, count = env->GetArrayLength(policies); i < count; ++i) {
+ jstring element = (jstring)env->GetObjectArrayElement(policies, i);
+ const char* policy = env->GetStringUTFChars(element, NULL /* isCopy */);
+ argv.emplace_back("--policy");
+ argv.emplace_back(policy);
+ env->ReleaseStringUTFChars(element, policy);
+ }
+
+ if (!enforceOverlayable) {
+ argv.emplace_back("--ignore-overlayable");
+ }
+
+ const auto result = ExecuteBinary(argv);
+ if (!result) {
+ LOG(ERROR) << "failed to execute idmap2";
+ return nullptr;
+ }
+
+ if (result->status != 0) {
+ LOG(ERROR) << "idmap2: " << result->stderr;
+ return nullptr;
+ }
+
+ // Return the paths of the idmaps created or updated during the idmap invocation.
+ std::vector<std::string> idmap_paths;
+ std::istringstream input(result->stdout);
+ std::string path;
+ while (std::getline(input, path)) {
+ idmap_paths.push_back(path);
+ }
+
+ jobjectArray array = env->NewObjectArray(idmap_paths.size(), g_stringClass, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+ for (size_t i = 0; i < idmap_paths.size(); i++) {
+ const std::string path = idmap_paths[i];
+ jstring java_string = env->NewStringUTF(path.c_str());
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ env->SetObjectArrayElement(array, i, java_string);
+ env->DeleteLocalRef(java_string);
+ }
+
+ return array;
+}
+
+static const JNINativeMethod g_methods[] = {
+ { "createIdmap",
+ "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Z)[Ljava/lang/String;",
+ (void *)createIdmap },
+};
+
+static const char* const kOverlayConfigPathName = "com/android/internal/content/om/OverlayConfig";
+
+namespace android {
+
+int register_com_android_internal_content_om_OverlayConfig(JNIEnv* env) {
+ jclass stringClass = FindClassOrDie(env, "java/lang/String");
+ g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+
+ return RegisterMethodsOrDie(env, kOverlayConfigPathName, g_methods, NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e04d3de622d8..72e19c06bb90 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -113,6 +113,7 @@ java_genrule {
":FrameworksCoreTests_keyset_splata_api",
":FrameworksCoreTests_keyset_splat_api",
":FrameworksCoreTests_locales",
+ ":FrameworksCoreTests_overlay_config",
":FrameworksCoreTests_version_1",
":FrameworksCoreTests_version_1_diff",
":FrameworksCoreTests_version_1_nosys",
diff --git a/core/tests/coretests/apks/overlay_config/Android.bp b/core/tests/coretests/apks/overlay_config/Android.bp
new file mode 100644
index 000000000000..957355726fe8
--- /dev/null
+++ b/core/tests/coretests/apks/overlay_config/Android.bp
@@ -0,0 +1,4 @@
+android_test_helper_app {
+ name: "FrameworksCoreTests_overlay_config",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+}
diff --git a/core/tests/coretests/apks/overlay_config/AndroidManifest.xml b/core/tests/coretests/apks/overlay_config/AndroidManifest.xml
new file mode 100644
index 000000000000..b15338eb545b
--- /dev/null
+++ b/core/tests/coretests/apks/overlay_config/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.overlay_config">
+
+ <application android:hasCode="false" />
+
+ <uses-sdk android:targetSdkVersion="21"/>
+
+ <overlay android:targetPackage="android"
+ android:targetName="TestResources" />
+</manifest>
diff --git a/core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java
new file mode 100644
index 000000000000..23655a08397d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.parsing.AndroidPackage;
+import android.os.Build;
+import android.util.ArrayMap;
+
+import com.android.internal.content.om.OverlayConfig.AndroidPackageProvider;
+import com.android.internal.content.om.OverlayScanner;
+import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
+
+import org.junit.Assert;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * A {@link TestRule} that runs a test case twice. First, the test case runs with a non-null
+ * {@link OverlayScanner} as if the zygote process is scanning the overlay packages
+ * and parsing configuration files. The test case then runs with a non-null
+ * {@link AndroidPackageProvider} as if the system server is parsing configuration files.
+ *
+ * This simulates what will happen on device. If an exception would be thrown in the zygote, then
+ * the exception should be thrown in the first run of the test case.
+ */
+public class OverlayConfigIterationRule implements TestRule {
+
+ enum Iteration {
+ ZYGOTE,
+ SYSTEM_SERVER,
+ }
+
+ private final ArrayMap<File, ParsedOverlayInfo> mOverlayStubResults = new ArrayMap<>();
+ private Supplier<OverlayScanner> mOverlayScanner;
+ private AndroidPackageProvider mAndroidPackageProvider;
+ private Iteration mIteration;
+
+ /**
+ * Mocks the parsing of the file to make it appear to the scanner that the file is a valid
+ * overlay APK.
+ **/
+ void addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion,
+ boolean isStatic, int priority) {
+ try {
+ final File canonicalPath = new File(path.getCanonicalPath());
+ mOverlayStubResults.put(canonicalPath, new ParsedOverlayInfo(
+ packageName, targetPackage, targetSdkVersion, isStatic, priority,
+ canonicalPath));
+ } catch (IOException e) {
+ Assert.fail("Failed to add overlay " + e);
+ }
+ }
+
+ void addOverlay(File path, String packageName) {
+ addOverlay(path, packageName, "target");
+ }
+
+ void addOverlay(File path, String packageName, String targetPackage) {
+ addOverlay(path, packageName, targetPackage, Build.VERSION_CODES.CUR_DEVELOPMENT);
+ }
+
+ void addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion) {
+ addOverlay(path, packageName, targetPackage, targetSdkVersion, false, 0);
+ }
+
+ /** Retrieves the {@link OverlayScanner} for the current run of the test. */
+ Supplier<OverlayScanner> getScannerFactory() {
+ return mOverlayScanner;
+ }
+
+ /** Retrieves the {@link AndroidPackageProvider} for the current run of the test. */
+ AndroidPackageProvider getPackageProvider() {
+ return mAndroidPackageProvider;
+ }
+
+ /** Retrieves the current iteration of the test. */
+ Iteration getIteration() {
+ return mIteration;
+ }
+
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ // Run the test once as if the zygote process is scanning the overlay packages
+ // and parsing configuration files.
+ mOverlayScanner = () -> {
+ OverlayScanner scanner = Mockito.spy(new OverlayScanner());
+ for (Map.Entry<File, ParsedOverlayInfo> overlay :
+ mOverlayStubResults.entrySet()) {
+ doReturn(overlay.getValue()).when(scanner)
+ .parseOverlayManifest(overlay.getKey());
+ }
+ return scanner;
+ };
+ mAndroidPackageProvider = null;
+ mIteration = Iteration.ZYGOTE;
+ base.evaluate();
+
+ // Run the test once more (if the first test did not throw an exception) as if
+ // the system server is parsing the configuration files and using PackageManager to
+ // retrieving information of overlays.
+ mOverlayScanner = null;
+ mAndroidPackageProvider = Mockito.mock(AndroidPackageProvider.class);
+ mIteration = Iteration.SYSTEM_SERVER;
+ doAnswer((InvocationOnMock invocation) -> {
+ final Object[] args = invocation.getArguments();
+ final Consumer<AndroidPackage> f = (Consumer<AndroidPackage>) args[0];
+ for (Map.Entry<File, ParsedOverlayInfo> overlay :
+ mOverlayStubResults.entrySet()) {
+ final AndroidPackage a = Mockito.mock(AndroidPackage.class);
+ final ParsedOverlayInfo info = overlay.getValue();
+ when(a.getPackageName()).thenReturn(info.packageName);
+ when(a.getOverlayTarget()).thenReturn(info.targetPackageName);
+ when(a.getTargetSdkVersion()).thenReturn(info.targetSdkVersion);
+ when(a.isOverlayIsStatic()).thenReturn(info.isStatic);
+ when(a.getOverlayPriority()).thenReturn(info.priority);
+ when(a.getBaseCodePath()).thenReturn(info.path.getPath());
+ when(a.isSystem()).thenReturn(
+ !info.path.getPath().contains("data/overlay"));
+ f.accept(a);
+ }
+ return null;
+ }).when(mAndroidPackageProvider).forEachPackage(any());
+
+ base.evaluate();
+ }
+ };
+ }
+}
+
+
diff --git a/core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java
new file mode 100644
index 000000000000..dee118fbe0fe
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.FileUtils;
+
+import androidx.test.InstrumentationRegistry;
+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.OverlayScanner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class OverlayConfigTest {
+ private static final String TEST_APK_PACKAGE_NAME =
+ "com.android.frameworks.coretests.overlay_config";
+
+ private ExpectedException mExpectedException = ExpectedException.none();
+ private OverlayConfigIterationRule mScannerRule = new OverlayConfigIterationRule();
+ private TemporaryFolder mTestFolder = new TemporaryFolder();
+
+ @Rule
+ public RuleChain chain = RuleChain.outerRule(mExpectedException)
+ .around(mTestFolder).around(mScannerRule);
+
+ private OverlayConfig createConfigImpl() throws IOException {
+ return new OverlayConfig(mTestFolder.getRoot().getCanonicalFile(),
+ mScannerRule.getScannerFactory(), mScannerRule.getPackageProvider());
+ }
+
+ private File createFile(String fileName) throws IOException {
+ return createFile(fileName, "");
+ }
+
+ private File createFile(String fileName, String content) throws IOException {
+ final File f = new File(String.format("%s/%s", mTestFolder.getRoot(), fileName));
+ if (!f.getParentFile().equals(mTestFolder.getRoot())) {
+ f.getParentFile().mkdirs();
+ }
+ FileUtils.stringToFile(f.getPath(), content);
+ return f;
+ }
+
+ private static void assertConfig(OverlayConfig overlayConfig, String packageName,
+ boolean mutable, boolean enabled, int configIndex) {
+ final OverlayConfig.Configuration config = overlayConfig.getConfiguration(packageName);
+ assertNotNull(config);
+ assertEquals(mutable, config.parsedConfig.mutable);
+ assertEquals(enabled, config.parsedConfig.enabled);
+ assertEquals(configIndex, config.configIndex);
+ }
+
+ @Test
+ public void testImmutableAfterNonImmutableFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("immutable overlays must precede mutable overlays");
+
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" enabled=\"true\" />"
+ + " <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two");
+ createConfigImpl();
+ }
+
+ @Test
+ public void testConfigureAbsentPackageFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("not present in partition");
+
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" enabled=\"true\" />"
+ + "</config>");
+
+ createConfigImpl();
+ }
+
+ @Test
+ public void testConfigurePackageTwiceFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("configured multiple times in a single partition");
+
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" enabled=\"true\" />"
+ + " <overlay package=\"one\" mutable=\"false\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+ createConfigImpl();
+ }
+
+ @Test
+ public void testConfigureOverlayAcrossPartitionsFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("not present in partition");
+
+ createFile("/vendor/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+ createConfigImpl();
+ }
+
+ @Test
+ public void testConfigureOverlayOutsideOverlayDirFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("not present in partition");
+
+ createFile("/vendor/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/product/app/one.apk"), "one");
+ createConfigImpl();
+ }
+
+ @Test
+ public void testMergeOAbsolutePathFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("must be relative to the directory");
+
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <merge path=\"/product/overlay/config/auto-generated-config.xml\" />"
+ + "</config>");
+
+ createConfigImpl();
+ }
+
+ @Test
+ public void testMergeOutsideDirFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("outside of configuration directory");
+
+ createFile("/product/overlay/auto-generated-config.xml");
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <merge path=\"../auto-generated-config.xml\" />"
+ + "</config>");
+
+ createConfigImpl();
+ }
+
+ @Test
+ public void testMergeOutsidePartitionFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("outside of configuration directory");
+
+ createFile("/vendor/overlay/config/config2.xml");
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <merge path=\"../../../vendor/overlay/config/config2.xml\" />"
+ + "</config>");
+
+ createConfigImpl();
+ }
+
+ @Test
+ public void testMergeCircularFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("Maximum <merge> depth exceeded");
+
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <merge path=\"config2.xml\" />"
+ + "</config>");
+ createFile("/product/overlay/config/config2.xml",
+ "<config>"
+ + " <merge path=\"config.xml\" />"
+ + "</config>");
+
+ createConfigImpl();
+ }
+
+ @Test
+ public void testMergeMissingFileFails() throws IOException {
+ mExpectedException.expect(IllegalStateException.class);
+ mExpectedException.expectMessage("does not exist");
+
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <merge path=\"config2.xml\" />"
+ + "</config>");
+ createConfigImpl();
+ }
+
+ @Test
+ public void testProductOverridesVendor() throws IOException {
+ createFile("/vendor/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" enabled=\"false\" />"
+ + "</config>");
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one");
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", true, true, 1);
+ }
+
+ @Test
+ public void testPartitionPrecedence() throws IOException {
+ createFile("/vendor/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" enabled=\"true\" />"
+ + "</config>");
+ createFile("/odm/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"two\" enabled=\"true\" />"
+ + "</config>");
+ createFile("/oem/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"three\" enabled=\"true\" />"
+ + "</config>");
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"four\" enabled=\"true\" />"
+ + "</config>");
+ createFile("/system_ext/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"five\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one");
+ mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two");
+ mScannerRule.addOverlay(createFile("/oem/overlay/three.apk"), "three");
+ mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four");
+ mScannerRule.addOverlay(createFile("/system_ext/overlay/five.apk"), "five");
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", true, true, 0);
+ assertConfig(overlayConfig, "two", true, true, 1);
+ assertConfig(overlayConfig, "three", true, true, 2);
+ assertConfig(overlayConfig, "four", true, true, 3);
+ assertConfig(overlayConfig, "five", true, true, 4);
+ }
+
+ @Test
+ public void testImmutable() throws IOException {
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" mutable=\"false\" />"
+ + " <overlay package=\"two\" />"
+ + " <overlay package=\"three\" mutable=\"true\" />"
+ + "</config>");
+
+
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two");
+ mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three");
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", false, false, 0);
+ assertConfig(overlayConfig, "two", true, false, 1);
+ assertConfig(overlayConfig, "three", true, false, 2);
+ }
+
+ @Test
+ public void testEnabled() throws IOException {
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" />"
+ + " <overlay package=\"two\" enabled=\"true\" />"
+ + " <overlay package=\"three\" enabled=\"false\" />"
+ + "</config>");
+
+
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two");
+ mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three");
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", true, false, 0);
+ assertConfig(overlayConfig, "two", true, true, 1);
+ assertConfig(overlayConfig, "three", true, false, 2);
+ }
+
+ @Test
+ public void testMerge() throws IOException {
+ createFile("/product/overlay/config/auto-generated-config.xml",
+ "<config>"
+ + " <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+ + " <overlay package=\"three\" mutable=\"false\" enabled=\"true\" />"
+ + "</config>");
+
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" mutable=\"false\" enabled=\"true\" />"
+ + " <merge path=\"auto-generated-config.xml\" />"
+ + " <overlay package=\"four\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two");
+ mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three");
+ mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four");
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ OverlayConfig.Configuration o1 = overlayConfig.getConfiguration("one");
+ assertNotNull(o1);
+ assertFalse(o1.parsedConfig.mutable);
+ assertTrue(o1.parsedConfig.enabled);
+ assertEquals(0, o1.configIndex);
+
+ OverlayConfig.Configuration o2 = overlayConfig.getConfiguration("two");
+ assertNotNull(o2);
+ assertFalse(o2.parsedConfig.mutable);
+ assertTrue(o2.parsedConfig.enabled);
+ assertEquals(1, o2.configIndex);
+
+ OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three");
+ assertNotNull(o3);
+ assertFalse(o3.parsedConfig.mutable);
+ assertTrue(o3.parsedConfig.enabled);
+ assertEquals(2, o3.configIndex);
+
+ OverlayConfig.Configuration o4 = overlayConfig.getConfiguration("four");
+ assertNotNull(o4);
+ assertTrue(o4.parsedConfig.mutable);
+ assertTrue(o4.parsedConfig.enabled);
+ assertEquals(3, o4.configIndex);
+ }
+
+ @Test
+ public void testIdmapInvocationsFrameworkImmutable() throws IOException {
+ createFile("/vendor/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" mutable=\"false\" enabled=\"true\" />"
+ + " <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+ + " <overlay package=\"three\" enabled=\"true\" />"
+ + "</config>");
+
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"four\" mutable=\"false\" enabled=\"true\" />"
+ + " <overlay package=\"five\" mutable=\"false\" enabled=\"true\" />"
+ + " <overlay package=\"six\" mutable=\"false\" enabled=\"false\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android");
+ mScannerRule.addOverlay(createFile("/vendor/overlay/two.apk"), "two", "android");
+ mScannerRule.addOverlay(createFile("/vendor/overlay/three.apk"), "three", "android");
+ mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four", "android");
+ mScannerRule.addOverlay(createFile("/product/overlay/five.apk"), "five");
+ mScannerRule.addOverlay(createFile("/product/overlay/six.apk"), "six", "android");
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ if (mScannerRule.getIteration() == OverlayConfigIterationRule.Iteration.ZYGOTE) {
+ final ArrayList<IdmapInvocation> idmapInvocations =
+ overlayConfig.getImmutableFrameworkOverlayIdmapInvocations();
+ assertEquals(2, idmapInvocations.size());
+
+ final IdmapInvocation i0 = idmapInvocations.get(0);
+ assertTrue(i0.enforceOverlayable);
+ assertEquals("vendor", i0.policy);
+ assertEquals(2, i0.overlayPaths.size());
+ assertTrue(i0.overlayPaths.get(0).endsWith("/vendor/overlay/one.apk"));
+ assertTrue(i0.overlayPaths.get(1).endsWith("/vendor/overlay/two.apk"));
+
+ final IdmapInvocation i1 = idmapInvocations.get(1);
+ assertTrue(i1.enforceOverlayable);
+ assertEquals("product", i1.policy);
+ assertEquals(1, i1.overlayPaths.size());
+ assertTrue(i1.overlayPaths.get(0).endsWith("/product/overlay/four.apk"));
+ }
+ }
+
+ @Test
+ public void testIdmapInvocationsDifferentTargetSdk() throws IOException {
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" mutable=\"false\" enabled=\"true\" />"
+ + " <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+ + " <overlay package=\"three\" mutable=\"false\" enabled=\"true\" />"
+ + " <overlay package=\"four\" mutable=\"false\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one", "android");
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android");
+ mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 28);
+ mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four", "android");
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+
+ if (mScannerRule.getIteration() == OverlayConfigIterationRule.Iteration.ZYGOTE) {
+ final ArrayList<IdmapInvocation> idmapInvocations =
+ overlayConfig.getImmutableFrameworkOverlayIdmapInvocations();
+ assertEquals(3, idmapInvocations.size());
+
+ final IdmapInvocation i0 = idmapInvocations.get(0);
+ assertTrue(i0.enforceOverlayable);
+ assertEquals(2, i0.overlayPaths.size());
+ assertTrue(i0.overlayPaths.get(0).endsWith("/product/overlay/one.apk"));
+ assertTrue(i0.overlayPaths.get(1).endsWith("/product/overlay/two.apk"));
+
+ final IdmapInvocation i1 = idmapInvocations.get(1);
+ assertFalse(i1.enforceOverlayable);
+ assertEquals(1, i1.overlayPaths.size());
+ assertTrue(i1.overlayPaths.get(0).endsWith("/product/overlay/three.apk"));
+
+ final IdmapInvocation i2 = idmapInvocations.get(2);
+ assertTrue(i2.enforceOverlayable);
+ assertEquals(1, i2.overlayPaths.size());
+ assertTrue(i2.overlayPaths.get(0).endsWith("/product/overlay/four.apk"));
+ }
+ }
+
+ @Test
+ public void testNoConfigIsStatic() throws IOException {
+ mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one", "android", 28, true,
+ 1);
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 28, false,
+ 0);
+ mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 28,
+ true, 0);
+ mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four", "android", 28,
+ false, 2);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", false, true, 1);
+ assertConfig(overlayConfig, "three", false, true, 0);
+
+ }
+
+ @Test
+ public void testVendorStaticPrecedesProductImmutable() throws IOException {
+ createFile("/product/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+ 1);
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+ 0);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", false, true, 0);
+ assertConfig(overlayConfig, "two", false, true, 1);
+ }
+
+ @Test
+ public void testVendorImmutablePrecededProductStatic() throws IOException {
+ createFile("/vendor/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"one\" mutable=\"false\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+ 1);
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+ 0);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", false, true, 0);
+ assertConfig(overlayConfig, "two", false, true, 1);
+ }
+
+ @Test
+ public void testNoConfigsAllowPartitionReordering() throws IOException {
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+ 1);
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+ 0);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", false, true, 1);
+ assertConfig(overlayConfig, "two", false, true, 0);
+ }
+
+ @Test
+ public void testConfigDisablesPartitionReordering() throws IOException {
+ createFile("/odm/overlay/config/config.xml",
+ "<config>"
+ + " <overlay package=\"two\" enabled=\"true\" />"
+ + "</config>");
+
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+ 1);
+ mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two");
+ mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0,
+ true, 0);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", false, true, 0);
+ assertConfig(overlayConfig, "two", true, true, 1);
+ assertConfig(overlayConfig, "three", false, true, 2);
+ }
+
+ @Test
+ public void testStaticOverlayOutsideOverlayDir() throws IOException {
+ mScannerRule.addOverlay(createFile("/product/app/one.apk"), "one", "android", 0, true, 0);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ if (mScannerRule.getIteration() == OverlayConfigIterationRule.Iteration.SYSTEM_SERVER) {
+ assertConfig(overlayConfig, "one", false, true, 0);
+ }
+ }
+
+ @Test
+ public void testSortStaticOverlaysDifferentTargets() throws IOException {
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "other", 0, true, 0);
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+ 0);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", false, true, 1);
+ assertConfig(overlayConfig, "two", false, true, 0);
+ }
+
+ @Test
+ public void testSortStaticOverlaysSamePriority() throws IOException {
+ mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+ 0);
+ mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+ 0);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertConfig(overlayConfig, "one", false, true, 1);
+ assertConfig(overlayConfig, "two", false, true, 0);
+ }
+
+ @Test
+ public void testNonSystemOverlayCannotBeStatic() throws IOException {
+ mScannerRule.addOverlay(createFile("/data/overlay/one.apk"), "one", "android", 0, true,
+ 0);
+
+ final OverlayConfig overlayConfig = createConfigImpl();
+ assertTrue(overlayConfig.isMutable("one"));
+ assertFalse(overlayConfig.isEnabled("one"));
+ assertEquals(Integer.MAX_VALUE, overlayConfig.getPriority("one"));
+ }
+
+ @Test
+ public void testGetOverlayInfo() throws IOException {
+ if (mScannerRule.getIteration() != OverlayConfigIterationRule.Iteration.ZYGOTE) {
+ // Run only one iteration of the test.
+ return;
+ }
+
+ final InputStream is = InstrumentationRegistry.getContext().getResources()
+ .openRawResource(R.raw.overlay_config);
+ final File partitionDir = mTestFolder.newFolder("product", "overlay");
+ final File testApk = new File(partitionDir, "test.apk");
+ FileUtils.copy(is, new FileOutputStream(testApk));
+
+ final OverlayScanner scanner = new OverlayScanner();
+ scanner.scanDir(partitionDir);
+
+ final OverlayScanner.ParsedOverlayInfo info = scanner.getParsedInfo(TEST_APK_PACKAGE_NAME);
+ assertNotNull(info);
+ assertEquals(TEST_APK_PACKAGE_NAME, info.packageName);
+ assertEquals("android", info.targetPackageName);
+ assertEquals(testApk.getPath(), info.path.getPath());
+ assertEquals(21, info.targetSdkVersion);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 85c7f4b41b61..6a922c4abc3d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -307,6 +307,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
+import com.android.internal.content.om.OverlayConfig;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.Zygote;
@@ -799,6 +800,8 @@ public class PackageManagerService extends IPackageManager.Stub
private final List<ScanPartition> mDirsToScanAsSystem;
+ private final OverlayConfig mOverlayConfig;
+
/**
* Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
*
@@ -2896,6 +2899,9 @@ public class PackageManagerService extends IPackageManager.Stub
packageParser, executorService);
}
+ // Parse overlay configuration files to set default enable state, mutability, and
+ // priority of system overlays.
+ mOverlayConfig = OverlayConfig.initializeSystemInstance(mPmInternal::forEachPackage);
// Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -11531,50 +11537,17 @@ public class PackageManagerService extends IPackageManager.Stub
// We are scanning a system overlay. This can be the first scan of the
// system/vendor/oem partition, or an update to the system overlay.
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
- // This must be an update to a system overlay.
- final PackageSetting previousPkg = assertNotNull(
- mSettings.getPackageLPr(pkg.getPackageName()),
- "previous package state not present");
-
- // previousPkg.pkg may be null: the package will be not be scanned if the
- // package manager knows there is a newer version on /data.
- // TODO[b/79435695]: Find a better way to keep track of the "static"
- // property for RROs instead of having to parse packages on /system
- AndroidPackage ppkg = previousPkg.pkg;
- if (ppkg == null) {
- try {
- final PackageParser pp = new PackageParser();
- // TODO(b/135203078): Do we really need to parse here? Maybe use
- // a shortened path?
- ppkg = pp.parseParsedPackage(previousPkg.codePath,
- parseFlags | PackageParser.PARSE_IS_SYSTEM_DIR,
- false)
- .hideAsFinal();
- } catch (PackageParserException e) {
- Slog.w(TAG, "failed to parse " + previousPkg.codePath, e);
- }
- }
-
- // Static overlays cannot be updated.
- if (ppkg != null && ppkg.isOverlayIsStatic()) {
+ // This must be an update to a system overlay. Immutable overlays cannot be
+ // upgraded.
+ Objects.requireNonNull(mOverlayConfig,
+ "Parsing non-system dir before overlay configs are initialized");
+ if (!mOverlayConfig.isMutable(pkg.getPackageName())) {
throw new PackageManagerException("Overlay "
+ pkg.getPackageName()
+ " is static and cannot be upgraded.");
- // Non-static overlays cannot be converted to static overlays.
- } else if (pkg.isOverlayIsStatic()) {
- throw new PackageManagerException("Overlay "
- + pkg.getPackageName()
- + " cannot be upgraded into a static overlay.");
}
}
} else {
- // The overlay is a non-system overlay. Non-system overlays cannot be static.
- if (pkg.isOverlayIsStatic()) {
- throw new PackageManagerException("Overlay "
- + pkg.getPackageName()
- + " is static but not pre-installed.");
- }
-
// A non-preloaded overlay packages must have targetSdkVersion >= Q, or be
// signed with the platform certificate. Check this in increasing order of
// computational cost.