Fix an IllegalStateException in the OverlayConfigParser

The issue could be reproduced when the device defined an
overlay configuration file and an overlay package which
was in the configuration file also declared the required
system property in its manifest. The device works fine when
the required system property matches the corresponding
value on the device. But an IllegalStateException was thrown
from the system if it didn't match.

The root cause is that the package parser fails to parse the
overlay package if the required system property in the manifest
does not match the corresponding value on the device. It resulted
in the OverlayConfigParser could not find the package present
in the partition and throws the exception.

To resolve this issue, this CL skips the overlay configuration
if the package not found was caused by the system property
conditionon on the device.

Bug: 193422327
Test: atest OverlayConfigTest
Change-Id: I5650d796d92e3c4825b0d035e8e3b18f36d4cb47
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 49d4137..259cbf9 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -108,6 +108,16 @@
     private final boolean mOverlayIsStatic;
     /** Indicate the priority of this overlay package */
     private final int mOverlayPriority;
+    /**
+     * A comma separated list of system property names to control whether the overlay should be
+     * excluded based on the system property condition.
+     */
+    private final @Nullable String mRequiredSystemPropertyName;
+    /**
+     * A comma separated list of system property values to control whether the overlay should be
+     * excluded based on the system property condition.
+     */
+    private final @Nullable String mRequiredSystemPropertyValue;
 
     /**
      * Indicate the policy to deal with user data when rollback is committed
@@ -125,6 +135,7 @@
             boolean debuggable, boolean profileableByShell, boolean multiArch, boolean use32bitAbi,
             boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits,
             String targetPackageName, boolean overlayIsStatic, int overlayPriority,
+            String requiredSystemPropertyName, String requiredSystemPropertyValue,
             int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
             Set<String> requiredSplitTypes, Set<String> splitTypes) {
         mPath = path;
@@ -153,6 +164,8 @@
         mTargetPackageName = targetPackageName;
         mOverlayIsStatic = overlayIsStatic;
         mOverlayPriority = overlayPriority;
+        mRequiredSystemPropertyName = requiredSystemPropertyName;
+        mRequiredSystemPropertyValue = requiredSystemPropertyValue;
         mMinSdkVersion = minSdkVersion;
         mTargetSdkVersion = targetSdkVersion;
         mRollbackDataPolicy = rollbackDataPolicy;
@@ -419,6 +432,24 @@
     }
 
     /**
+     * A comma separated list of system property names to control whether the overlay should be
+     * excluded based on the system property condition.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getRequiredSystemPropertyName() {
+        return mRequiredSystemPropertyName;
+    }
+
+    /**
+     * A comma separated list of system property values to control whether the overlay should be
+     * excluded based on the system property condition.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getRequiredSystemPropertyValue() {
+        return mRequiredSystemPropertyValue;
+    }
+
+    /**
      * Indicate the policy to deal with user data when rollback is committed
      *
      * @see {@link PackageManager#ROLLBACK_DATA_POLICY_RESTORE}
@@ -431,10 +462,10 @@
     }
 
     @DataClass.Generated(
-            time = 1628562554893L,
+            time = 1631763761543L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final  int mRollbackDataPolicy\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index d3b6f2b..82637aa 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+import static android.content.pm.parsing.ParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY;
 import static android.content.pm.parsing.ParsingPackageUtils.checkRequiredSystemProperties;
 import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey;
 import static android.content.pm.parsing.ParsingPackageUtils.validateName;
@@ -332,7 +333,7 @@
                 signingDetails = SigningDetails.UNKNOWN;
             }
 
-            return parseApkLite(input, apkPath, parser, signingDetails);
+            return parseApkLite(input, apkPath, parser, signingDetails, flags);
         } catch (XmlPullParserException | IOException | RuntimeException e) {
             Slog.w(TAG, "Failed to parse " + apkPath, e);
             return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
@@ -350,7 +351,7 @@
     }
 
     private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
-            XmlResourceParser parser, SigningDetails signingDetails)
+            XmlResourceParser parser, SigningDetails signingDetails, int flags)
             throws IOException, XmlPullParserException {
         ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser);
         if (result.isError()) {
@@ -522,14 +523,14 @@
         }
 
         // Check to see if overlay should be excluded based on system property condition
-        if (!checkRequiredSystemProperties(requiredSystemPropertyName,
-                requiredSystemPropertyValue)) {
-            Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+        if ((flags & PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY) == 0
+                && !checkRequiredSystemProperties(
+                        requiredSystemPropertyName, requiredSystemPropertyValue)) {
+            String message = "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;
+                    + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue;
+            Slog.i(TAG, message);
+            return input.skip(message);
         }
 
         return input.success(
@@ -538,7 +539,8 @@
                         versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
                         coreApp, debuggable, profilableByShell, multiArch, use32bitAbi,
                         useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage,
-                        overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion,
+                        overlayIsStatic, overlayPriority, requiredSystemPropertyName,
+                        requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second));
     }
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 69cf32f..97af1d24 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -217,6 +217,11 @@
     public static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
     public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
     public static final int PARSE_ENFORCE_CODE = 1 << 6;
+    /**
+     * This flag is applied in the ApkLiteParser. Used by OverlayConfigParser to ignore the
+     * checks of required system property within the overlay tag.
+     */
+    public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
     public static final int PARSE_CHATTY = 1 << 31;
 
     @IntDef(flag = true, prefix = { "PARSE_" }, value = {
@@ -227,6 +232,7 @@
             PARSE_IGNORE_PROCESSES,
             PARSE_IS_SYSTEM_DIR,
             PARSE_MUST_BE_APK,
+            PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ParseFlags {}
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index 3f3c9bd..02dfc0e 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -35,6 +35,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.Supplier;
 
@@ -116,14 +118,16 @@
         }
 
         boolean foundConfigFile = false;
-        ArrayList<ParsedOverlayInfo> packageManagerOverlayInfos = null;
+        final Map<String, ParsedOverlayInfo> packageManagerOverlayInfos =
+                packageProvider == null ? null : getOverlayPackageInfos(packageProvider);
 
         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);
+                    OverlayConfigParser.getConfigurations(partition, scanner,
+                            packageManagerOverlayInfos);
             if (partitionOverlays != null) {
                 foundConfigFile = true;
                 overlays.addAll(partitionOverlays);
@@ -138,12 +142,8 @@
             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);
+                partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos.values());
                 for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) {
                     if (!partition.containsFile(partitionOverlayInfos.get(j).path)) {
                         partitionOverlayInfos.remove(j);
@@ -289,14 +289,14 @@
     }
 
     @NonNull
-    private static ArrayList<ParsedOverlayInfo> getOverlayPackageInfos(
+    private static Map<String, ParsedOverlayInfo> getOverlayPackageInfos(
             @NonNull PackageProvider packageManager) {
-        final ArrayList<ParsedOverlayInfo> overlays = new ArrayList<>();
+        final HashMap<String, ParsedOverlayInfo> overlays = new HashMap<>();
         packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> {
             if (p.getOverlayTarget() != null && isSystem) {
-                overlays.add(new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(),
-                        p.getTargetSdkVersion(), p.isOverlayIsStatic(), p.getOverlayPriority(),
-                        new File(p.getBaseApkPath())));
+                overlays.put(p.getPackageName(), new ParsedOverlayInfo(p.getPackageName(),
+                        p.getOverlayTarget(), p.getTargetSdkVersion(), p.isOverlayIsStatic(),
+                        p.getOverlayPriority(), new File(p.getBaseApkPath())));
             }
         });
         return overlays;
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index a86e595..053a341 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -28,6 +28,7 @@
 import android.util.Xml;
 
 import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 
 import libcore.io.IoUtils;
@@ -40,6 +41,7 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Map;
 
 /**
  * Responsible for parsing configurations of Runtime Resource Overlays that control mutability,
@@ -192,7 +194,8 @@
      */
     @Nullable
     static ArrayList<ParsedConfiguration> getConfigurations(
-            @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner) {
+            @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner,
+            @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos) {
         if (partition.getOverlayFolder() == null) {
             return null;
         }
@@ -207,11 +210,12 @@
         }
 
         final ParsingContext parsingContext = new ParsingContext(partition);
-        readConfigFile(configFile, scanner, parsingContext);
+        readConfigFile(configFile, scanner, packageManagerOverlayInfos, parsingContext);
         return parsingContext.mOrderedConfigurations;
     }
 
     private static void readConfigFile(@NonNull File configFile, @Nullable OverlayScanner scanner,
+            @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
             @NonNull ParsingContext parsingContext) {
         FileReader configReader;
         try {
@@ -231,10 +235,12 @@
                 final String name = parser.getName();
                 switch (name) {
                     case "merge":
-                        parseMerge(configFile, parser, scanner, parsingContext);
+                        parseMerge(configFile, parser, scanner, packageManagerOverlayInfos,
+                                parsingContext);
                         break;
                     case "overlay":
-                        parseOverlay(configFile, parser, scanner, parsingContext);
+                        parseOverlay(configFile, parser, scanner, packageManagerOverlayInfos,
+                                parsingContext);
                         break;
                     default:
                         Log.w(TAG, String.format("Tag %s is unknown in %s at %s",
@@ -258,7 +264,9 @@
      * configuration files.
      */
     private static void parseMerge(@NonNull File configFile, @NonNull XmlPullParser parser,
-            @Nullable OverlayScanner scanner, @NonNull ParsingContext parsingContext) {
+            @Nullable OverlayScanner scanner,
+            @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
+            @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"
@@ -304,7 +312,7 @@
                             parser.getPositionDescription()));
         }
 
-        readConfigFile(includedConfigFile, scanner, parsingContext);
+        readConfigFile(includedConfigFile, scanner, packageManagerOverlayInfos, parsingContext);
         parsingContext.mMergeDepth--;
     }
 
@@ -330,7 +338,12 @@
      * 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) {
+            @Nullable OverlayScanner scanner,
+            @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
+            @NonNull ParsingContext parsingContext) {
+        Preconditions.checkArgument((scanner == null) != (packageManagerOverlayInfos == null),
+                "scanner and packageManagerOverlayInfos cannot be both null or both non-null");
+
         final String packageName = parser.getAttributeValue(null, "package");
         if (packageName == null) {
             throw new IllegalStateException(String.format("\"<overlay> without package in %s at %s",
@@ -338,16 +351,30 @@
         }
 
         // Ensure the overlay being configured is present in the partition during zygote
-        // initialization.
+        // initialization, unless the package is an excluded overlay package.
         ParsedOverlayInfo info = null;
         if (scanner != null) {
             info = scanner.getParsedInfo(packageName);
-            if (info == null|| !parsingContext.mPartition.containsOverlay(info.path)) {
+            if (info == null
+                    && scanner.isExcludedOverlayPackage(packageName, parsingContext.mPartition)) {
+                Log.d(TAG, "overlay " + packageName + " in partition "
+                        + parsingContext.mPartition.getOverlayFolder() + " is ignored.");
+                return;
+            } else 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()));
             }
+        } else {
+            // Zygote shall have crashed itself, if there's an overlay apk not present in the
+            // partition. For the overlay package not found in the package manager, we can assume
+            // that it's an excluded overlay package.
+            if (packageManagerOverlayInfos.get(packageName) == null) {
+                Log.d(TAG, "overlay " + packageName + " in partition "
+                        + parsingContext.mPartition.getOverlayFolder() + " is ignored.");
+                return;
+            }
         }
 
         if (parsingContext.mConfiguredOverlays.contains(packageName)) {
diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java
index 6b5cb8d..4387d1b 100644
--- a/core/java/com/android/internal/content/om/OverlayScanner.java
+++ b/core/java/com/android/internal/content/om/OverlayScanner.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.content.om;
 
+import static android.content.pm.parsing.ParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY;
+import static android.content.pm.parsing.ParsingPackageUtils.checkRequiredSystemProperties;
+
 import static com.android.internal.content.om.OverlayConfig.TAG;
 
 import android.annotation.NonNull;
@@ -24,13 +27,17 @@
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * This class scans a directory containing overlay APKs and extracts information from the overlay
@@ -73,6 +80,14 @@
      */
     private final ArrayMap<String, ParsedOverlayInfo> mParsedOverlayInfos = new ArrayMap<>();
 
+    /**
+     * A list of pair<packageName, apkFile> which is excluded from the system based on the
+     * system property condition.
+     *
+     * @see #isExcludedOverlayPackage(String, OverlayConfigParser.OverlayPartition)
+     */
+    private final List<Pair<String, File>> mExcludedOverlayPackages = new ArrayList<>();
+
     /** Retrieves information parsed from the overlay with the package name. */
     @Nullable
     public final ParsedOverlayInfo getParsedInfo(String packageName) {
@@ -86,6 +101,25 @@
     }
 
     /**
+     * Returns {@code true} if the given package name on the given overlay partition is an
+     * excluded overlay package.
+     * <p>
+     * An excluded overlay package declares overlay attributes of required system property in its
+     * manifest that do not match the corresponding values on the device.
+     */
+    final boolean isExcludedOverlayPackage(@NonNull String packageName,
+            @NonNull OverlayConfigParser.OverlayPartition overlayPartition) {
+        for (int i = 0; i < mExcludedOverlayPackages.size(); i++) {
+            final Pair<String, File> pair = mExcludedOverlayPackages.get(i);
+            if (pair.first.equals(packageName)
+                    && overlayPartition.containsOverlay(pair.second)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * 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.
@@ -115,7 +149,7 @@
                 continue;
             }
 
-            final ParsedOverlayInfo info = parseOverlayManifest(f);
+            final ParsedOverlayInfo info = parseOverlayManifest(f, mExcludedOverlayPackages);
             if (info == null) {
                 continue;
             }
@@ -124,20 +158,37 @@
         }
     }
 
-    /** Extracts information about the overlay from its manifest. */
+    /**
+     * Extracts information about the overlay from its manifest. Adds the package name and apk file
+     * into the {@code outExcludedOverlayPackages} if the apk is excluded from the system based
+     * on the system property condition.
+     */
     @VisibleForTesting
-    public ParsedOverlayInfo parseOverlayManifest(File overlayApk) {
+    public ParsedOverlayInfo parseOverlayManifest(File overlayApk,
+            List<Pair<String, File>> outExcludedOverlayPackages) {
         final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
         final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(),
-                overlayApk, /* flags */ 0);
+                overlayApk, PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY);
         if (ret.isError()) {
             Log.w(TAG, "Got exception loading overlay.", ret.getException());
             return null;
         }
         final ApkLite apkLite = ret.getResult();
-        return apkLite.getTargetPackageName() == null ? null :
-                new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
-                        apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
-                        apkLite.getOverlayPriority(), new File(apkLite.getPath()));
+        if (apkLite.getTargetPackageName() == null) {
+            // Not an overlay package
+            return null;
+        }
+        final String propName = apkLite.getRequiredSystemPropertyName();
+        final String propValue = apkLite.getRequiredSystemPropertyValue();
+        if ((!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue))
+                && !checkRequiredSystemProperties(propName, propValue)) {
+            // The overlay package should be excluded. Adds it into the outExcludedOverlayPackages
+            // for overlay configuration parser to ignore it.
+            outExcludedOverlayPackages.add(Pair.create(apkLite.getPackageName(), overlayApk));
+            return null;
+        }
+        return new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
+                apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
+                apkLite.getOverlayPriority(), new File(apkLite.getPath()));
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
index c50c818..3c093d8 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java
@@ -18,12 +18,14 @@
 
 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.ParsingPackageRead;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Build;
+import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.Pair;
 
 import com.android.internal.content.om.OverlayConfig.PackageProvider;
 import com.android.internal.content.om.OverlayScanner;
@@ -38,6 +40,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.Supplier;
@@ -58,22 +61,42 @@
         SYSTEM_SERVER,
     }
 
-    private final ArrayMap<File, ParsedOverlayInfo> mOverlayStubResults = new ArrayMap<>();
+    private final ArrayMap<File, TestOverlayInfo> mTestOverlayInfos = new ArrayMap<>();
     private Supplier<OverlayScanner> mOverlayScanner;
     private PackageProvider mPkgProvider;
     private Iteration mIteration;
 
+    /** Represents information parsed from the manifest of an overlay for test. */
+    private static class TestOverlayInfo extends ParsedOverlayInfo {
+        public final String requiredSystemPropertyName;
+        public final String requiredSystemPropertyValue;
+
+        TestOverlayInfo(String packageName, String targetPackageName,
+                int targetSdkVersion, boolean isStatic, int priority, File path,
+                String requiredSystemPropertyName, String requiredSystemPropertyValue) {
+            super(packageName, targetPackageName, targetSdkVersion, isStatic, priority, path);
+            this.requiredSystemPropertyName = requiredSystemPropertyName;
+            this.requiredSystemPropertyValue = requiredSystemPropertyValue;
+        }
+
+        public boolean isMatchRequiredSystemProperty() {
+            return ParsingPackageUtils.checkRequiredSystemProperties(
+                    requiredSystemPropertyName, requiredSystemPropertyValue);
+        }
+    }
+
     /**
      * 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) {
+            boolean isStatic, int priority, String requiredSystemPropertyName,
+            String requiredSystemPropertyValue) {
         try {
             final File canonicalPath = new File(path.getCanonicalPath());
-            mOverlayStubResults.put(canonicalPath, new ParsedOverlayInfo(
+            mTestOverlayInfos.put(canonicalPath, new TestOverlayInfo(
                     packageName, targetPackage, targetSdkVersion, isStatic, priority,
-                    canonicalPath));
+                    canonicalPath, requiredSystemPropertyName, requiredSystemPropertyValue));
         } catch (IOException e) {
             Assert.fail("Failed to add overlay " + e);
         }
@@ -91,6 +114,12 @@
         addOverlay(path, packageName, targetPackage, targetSdkVersion, false, 0);
     }
 
+    void addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion,
+            boolean isStatic, int priority) {
+        addOverlay(path, packageName, targetPackage, targetSdkVersion, isStatic, priority,
+                null /* requiredSystemPropertyName */, null /* requiredSystemPropertyValue */);
+    }
+
     /** Retrieves the {@link OverlayScanner} for the current run of the test. */
     Supplier<OverlayScanner> getScannerFactory() {
         return mOverlayScanner;
@@ -116,11 +145,21 @@
                 // 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());
-                    }
+                    doAnswer((InvocationOnMock invocation) -> {
+                        final Object[] args = invocation.getArguments();
+                        final File overlayApk = (File) args[0];
+                        final List<Pair<String, File>> outExcludedOverlayPackages =
+                                (List<Pair<String, File>>) args[1];
+                        final TestOverlayInfo overlayInfo = mTestOverlayInfos.get(overlayApk);
+                        if ((!TextUtils.isEmpty(overlayInfo.requiredSystemPropertyName)
+                                || !TextUtils.isEmpty(overlayInfo.requiredSystemPropertyValue))
+                                && !overlayInfo.isMatchRequiredSystemProperty()) {
+                            outExcludedOverlayPackages.add(
+                                    Pair.create(overlayInfo.packageName, overlayApk));
+                            return null;
+                        }
+                        return overlayInfo;
+                    }).when(scanner).parseOverlayManifest(any(), any());
                     return scanner;
                 };
                 mPkgProvider = null;
@@ -137,10 +176,15 @@
                     final Object[] args = invocation.getArguments();
                     final BiConsumer<ParsingPackageRead, Boolean> f =
                             (BiConsumer<ParsingPackageRead, Boolean>) args[0];
-                    for (Map.Entry<File, ParsedOverlayInfo> overlay :
-                            mOverlayStubResults.entrySet()) {
+                    for (Map.Entry<File, TestOverlayInfo> overlay :
+                            mTestOverlayInfos.entrySet()) {
                         final ParsingPackageRead a = Mockito.mock(ParsingPackageRead.class);
-                        final ParsedOverlayInfo info = overlay.getValue();
+                        final TestOverlayInfo info = overlay.getValue();
+                        if ((!TextUtils.isEmpty(info.requiredSystemPropertyName)
+                                || !TextUtils.isEmpty(info.requiredSystemPropertyValue))
+                                && !info.isMatchRequiredSystemProperty()) {
+                            continue;
+                        }
                         when(a.getPackageName()).thenReturn(info.packageName);
                         when(a.getOverlayTarget()).thenReturn(info.targetPackageName);
                         when(a.getTargetSdkVersion()).thenReturn(info.targetSdkVersion);
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index 178c2dd..aea453e 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -19,9 +19,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.os.FileUtils;
+import android.os.SystemProperties;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -600,4 +602,65 @@
         assertEquals(testApk.getPath(), info.path.getPath());
         assertEquals(21, info.targetSdkVersion);
     }
+
+    @Test
+    public void testOverlayManifest_withRequiredSystemPropertyAndValueNotMatched()
+            throws IOException {
+        final String systemPropertyName = "foo.name";
+        final String systemPropertyValue = "foo.value";
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" />"
+                        + "  <overlay package=\"two\" />"
+                        + "  <overlay package=\"three\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one", "android", 0,
+                true, 1, systemPropertyName, systemPropertyValue);
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0,
+                true, 1, systemPropertyName, systemPropertyValue);
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        OverlayConfig.Configuration o1 = overlayConfig.getConfiguration("one");
+        assertNull(o1);
+
+        OverlayConfig.Configuration o2 = overlayConfig.getConfiguration("two");
+        assertNull(o2);
+
+        OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three");
+        assertNotNull(o3);
+    }
+
+    @Test
+    public void testOverlayManifest_withRequiredSystemPropertyAndValueMatched()
+            throws IOException {
+        final String systemPropertyName = "ro.build.version.sdk";
+        final String systemPropertyValue = SystemProperties.get(systemPropertyName, null);
+        assertNotNull(systemPropertyValue);
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" />"
+                        + "  <overlay package=\"two\" />"
+                        + "  <overlay package=\"three\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one", "android", 0,
+                true, 1, systemPropertyName, systemPropertyValue);
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0,
+                true, 1, systemPropertyName, systemPropertyValue);
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        OverlayConfig.Configuration o1 = overlayConfig.getConfiguration("one");
+        assertNotNull(o1);
+
+        OverlayConfig.Configuration o2 = overlayConfig.getConfiguration("two");
+        assertNotNull(o2);
+
+        OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three");
+        assertNotNull(o3);
+    }
 }