From b62f02cb147d26aa6efd2d01560e4e1d0b3eb12b Mon Sep 17 00:00:00 2001 From: Rhed Jao Date: Thu, 16 Sep 2021 12:04:28 +0800 Subject: [PATCH] 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 --- .../android/content/pm/parsing/ApkLite.java | 35 +++++++++- .../content/pm/parsing/ApkLiteParseUtils.java | 22 +++--- .../pm/parsing/ParsingPackageUtils.java | 6 ++ .../internal/content/om/OverlayConfig.java | 24 +++---- .../content/om/OverlayConfigParser.java | 45 +++++++++--- .../internal/content/om/OverlayScanner.java | 67 +++++++++++++++--- .../res/OverlayConfigIterationRule.java | 70 +++++++++++++++---- .../content/res/OverlayConfigTest.java | 63 +++++++++++++++++ 8 files changed, 278 insertions(+), 54 deletions(-) diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java index 49d413702f9c7..259cbf9ca889d 100644 --- a/core/java/android/content/pm/parsing/ApkLite.java +++ b/core/java/android/content/pm/parsing/ApkLite.java @@ -108,6 +108,16 @@ public class ApkLite { 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 @@ public class ApkLite { 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 requiredSplitTypes, Set splitTypes) { mPath = path; @@ -153,6 +164,8 @@ public class ApkLite { mTargetPackageName = targetPackageName; mOverlayIsStatic = overlayIsStatic; mOverlayPriority = overlayPriority; + mRequiredSystemPropertyName = requiredSystemPropertyName; + mRequiredSystemPropertyValue = requiredSystemPropertyValue; mMinSdkVersion = minSdkVersion; mTargetSdkVersion = targetSdkVersion; mRollbackDataPolicy = rollbackDataPolicy; @@ -418,6 +431,24 @@ public class ApkLite { return mOverlayPriority; } + /** + * 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 * @@ -431,10 +462,10 @@ public class ApkLite { } @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 mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set 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 mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set 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 d3b6f2b83553e..82637aa9b2879 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -18,6 +18,7 @@ package android.content.pm.parsing; 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 @@ public class ApkLiteParseUtils { 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 @@ public class ApkLiteParseUtils { } private static ParseResult parseApkLite(ParseInput input, String codePath, - XmlResourceParser parser, SigningDetails signingDetails) + XmlResourceParser parser, SigningDetails signingDetails, int flags) throws IOException, XmlPullParserException { ParseResult> result = parsePackageSplitNames(input, parser); if (result.isError()) { @@ -522,14 +523,14 @@ public class ApkLiteParseUtils { } // 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 @@ public class ApkLiteParseUtils { 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 69cf32fe24846..97af1d24d5780 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -217,6 +217,11 @@ public class ParsingPackageUtils { 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 @@ public class ParsingPackageUtils { 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 3f3c9bdbe5f5e..02dfc0e9f45f7 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.io.File; 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 @@ public class OverlayConfig { } boolean foundConfigFile = false; - ArrayList packageManagerOverlayInfos = null; + final Map packageManagerOverlayInfos = + packageProvider == null ? null : getOverlayPackageInfos(packageProvider); final ArrayList 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 partitionOverlays = - OverlayConfigParser.getConfigurations(partition, scanner); + OverlayConfigParser.getConfigurations(partition, scanner, + packageManagerOverlayInfos); if (partitionOverlays != null) { foundConfigFile = true; overlays.addAll(partitionOverlays); @@ -138,12 +142,8 @@ public class OverlayConfig { 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 @@ public class OverlayConfig { } @NonNull - private static ArrayList getOverlayPackageInfos( + private static Map getOverlayPackageInfos( @NonNull PackageProvider packageManager) { - final ArrayList overlays = new ArrayList<>(); + final HashMap 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 a86e5950c2f62..053a341dc83ea 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.Log; 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.FileNotFoundException; 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 @@ final class OverlayConfigParser { */ @Nullable static ArrayList getConfigurations( - @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner) { + @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner, + @Nullable Map packageManagerOverlayInfos) { if (partition.getOverlayFolder() == null) { return null; } @@ -207,11 +210,12 @@ final class OverlayConfigParser { } 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 packageManagerOverlayInfos, @NonNull ParsingContext parsingContext) { FileReader configReader; try { @@ -231,10 +235,12 @@ final class OverlayConfigParser { 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 @@ final class OverlayConfigParser { * configuration files. */ private static void parseMerge(@NonNull File configFile, @NonNull XmlPullParser parser, - @Nullable OverlayScanner scanner, @NonNull ParsingContext parsingContext) { + @Nullable OverlayScanner scanner, + @Nullable Map packageManagerOverlayInfos, + @NonNull ParsingContext parsingContext) { final String path = parser.getAttributeValue(null, "path"); if (path == null) { throw new IllegalStateException(String.format(" without path in %s at %s" @@ -304,7 +312,7 @@ final class OverlayConfigParser { parser.getPositionDescription())); } - readConfigFile(includedConfigFile, scanner, parsingContext); + readConfigFile(includedConfigFile, scanner, packageManagerOverlayInfos, parsingContext); parsingContext.mMergeDepth--; } @@ -330,7 +338,12 @@ final class OverlayConfigParser { * 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 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("\" without package in %s at %s", @@ -338,16 +351,30 @@ final class OverlayConfigParser { } // 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 6b5cb8d9b850e..4387d1bd12009 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.ApkLite; 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 @@ public class OverlayScanner { */ private final ArrayMap mParsedOverlayInfos = new ArrayMap<>(); + /** + * A list of pair which is excluded from the system based on the + * system property condition. + * + * @see #isExcludedOverlayPackage(String, OverlayConfigParser.OverlayPartition) + */ + private final List> mExcludedOverlayPackages = new ArrayList<>(); + /** Retrieves information parsed from the overlay with the package name. */ @Nullable public final ParsedOverlayInfo getParsedInfo(String packageName) { @@ -85,6 +100,25 @@ public class OverlayScanner { return mParsedOverlayInfos.values(); } + /** + * Returns {@code true} if the given package name on the given overlay partition is an + * excluded overlay package. + *

+ * 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 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 @@ -115,7 +149,7 @@ public class OverlayScanner { continue; } - final ParsedOverlayInfo info = parseOverlayManifest(f); + final ParsedOverlayInfo info = parseOverlayManifest(f, mExcludedOverlayPackages); if (info == null) { continue; } @@ -124,20 +158,37 @@ public class OverlayScanner { } } - /** 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> outExcludedOverlayPackages) { final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); final ParseResult 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 c50c818be716a..3c093d89723d5 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 @@ package com.android.internal.content.res; 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 org.mockito.invocation.InvocationOnMock; 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 @@ public class OverlayConfigIterationRule implements TestRule { SYSTEM_SERVER, } - private final ArrayMap mOverlayStubResults = new ArrayMap<>(); + private final ArrayMap mTestOverlayInfos = new ArrayMap<>(); private Supplier 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 @@ public class OverlayConfigIterationRule implements TestRule { 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 getScannerFactory() { return mOverlayScanner; @@ -116,11 +145,21 @@ public class OverlayConfigIterationRule implements TestRule { // and parsing configuration files. mOverlayScanner = () -> { OverlayScanner scanner = Mockito.spy(new OverlayScanner()); - for (Map.Entry 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> outExcludedOverlayPackages = + (List>) 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 @@ public class OverlayConfigIterationRule implements TestRule { final Object[] args = invocation.getArguments(); final BiConsumer f = (BiConsumer) args[0]; - for (Map.Entry overlay : - mOverlayStubResults.entrySet()) { + for (Map.Entry 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 178c2dd5b0629..aea453ea4e573 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 @@ package com.android.internal.content.res; 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 @@ public class OverlayConfigTest { 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", + "" + + " " + + " " + + " " + + ""); + + 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", + "" + + " " + + " " + + " " + + ""); + + 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); + } }