diff options
12 files changed, 150 insertions, 30 deletions
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java index 032ac4283712..c182b4aeb264 100644 --- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java @@ -421,6 +421,8 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @NonNull private String[] mUsesStaticLibrariesSorted; + private Map<String, Boolean> mFeatureFlagState = new ArrayMap<>(); + @NonNull public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath, @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp, @@ -2819,6 +2821,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, queriesProviders = Collections.unmodifiableSet(queriesProviders); mimeGroups = Collections.unmodifiableSet(mimeGroups); mKnownActivityEmbeddingCerts = Collections.unmodifiableSet(mKnownActivityEmbeddingCerts); + mFeatureFlagState = Collections.unmodifiableMap(mFeatureFlagState); } @Override @@ -3118,6 +3121,8 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Override public void writeToParcel(Parcel dest, int flags) { + writeFeatureFlagState(dest); + sForBoolean.parcel(this.supportsSmallScreens, dest, flags); sForBoolean.parcel(this.supportsNormalScreens, dest, flags); sForBoolean.parcel(this.supportsLargeScreens, dest, flags); @@ -3267,6 +3272,27 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow); } + private void writeFeatureFlagState(@NonNull Parcel dest) { + // Use a string array to encode flag state. One string per flag in the form `<flag>=<value>` + // where value is 0 (disabled), 1 (enabled) or ? (unknown flag or value). + int featureFlagCount = this.mFeatureFlagState.size(); + String[] featureFlagStateAsArray = new String[featureFlagCount]; + var entryIterator = this.mFeatureFlagState.entrySet().iterator(); + for (int i = 0; i < featureFlagCount; i++) { + var entry = entryIterator.next(); + Boolean flagValue = entry.getValue(); + if (flagValue == null) { + featureFlagStateAsArray[i] = entry.getKey() + "=?"; + } else if (flagValue.booleanValue()) { + featureFlagStateAsArray[i] = entry.getKey() + "=1"; + } else { + featureFlagStateAsArray[i] = entry.getKey() + "=0"; + } + + } + dest.writeStringArray(featureFlagStateAsArray); + } + public PackageImpl(Parcel in) { this(in, /* callback */ null); } @@ -3275,6 +3301,9 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, mCallback = callback; // We use the boot classloader for all classes that we load. final ClassLoader boot = Object.class.getClassLoader(); + + readFeatureFlagState(in); + this.supportsSmallScreens = sForBoolean.unparcel(in); this.supportsNormalScreens = sForBoolean.unparcel(in); this.supportsLargeScreens = sForBoolean.unparcel(in); @@ -3440,6 +3469,27 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, // to mutate this instance before it's finalized. } + private void readFeatureFlagState(@NonNull Parcel in) { + // See comment in writeFeatureFlagState() for encoding of flag state. + String[] featureFlagStateAsArray = in.createStringArray(); + for (String s : featureFlagStateAsArray) { + int sepIndex = s.lastIndexOf('='); + if (sepIndex >= 0 && sepIndex == s.length() - 2) { + String flagPackageAndName = s.substring(0, sepIndex); + char c = s.charAt(sepIndex + 1); + Boolean flagValue = null; + if (c == '1') { + flagValue = Boolean.TRUE; + } else if (c == '0') { + flagValue = Boolean.FALSE; + } else if (c != '?') { + continue; + } + this.mFeatureFlagState.put(flagPackageAndName, flagValue); + } + } + } + @NonNull public static final Creator<PackageImpl> CREATOR = new Creator<PackageImpl>() { @Override @@ -3660,6 +3710,18 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, return mBaseAppDataDeviceProtectedDirForSystemUser; } + @Override + public PackageImpl addFeatureFlag( + @NonNull String flagPackageAndName, + @Nullable Boolean flagValue) { + mFeatureFlagState.put(flagPackageAndName, flagValue); + return this; + } + + public Map<String, Boolean> getFeatureFlagState() { + return mFeatureFlagState; + } + /** * Flags used for a internal bitset. These flags should never be persisted or exposed outside * of this class. It is expected that PackageCacher explicitly clears itself whenever the diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java index 8faaf9584e54..70b7953ed364 100644 --- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java +++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java @@ -33,6 +33,7 @@ import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.modules.utils.TypedXmlPullParser; import org.xmlpull.v1.XmlPullParser; @@ -199,7 +200,7 @@ public class AconfigFlags { * @return the current value of the given Aconfig flag, or null if there is no such flag */ @Nullable - private Boolean getFlagValue(@NonNull String flagPackageAndName) { + public Boolean getFlagValue(@NonNull String flagPackageAndName) { Boolean value = mFlagValues.get(flagPackageAndName); if (DEBUG) { Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value); @@ -209,10 +210,13 @@ public class AconfigFlags { /** * Check if the element in {@code parser} should be skipped because of the feature flag. + * @param pkg The package being parsed * @param parser XML parser object currently parsing an element * @return true if the element is disabled because of its feature flag */ - public boolean skipCurrentElement(@NonNull XmlResourceParser parser) { + public boolean skipCurrentElement( + @NonNull ParsingPackage pkg, + @NonNull XmlResourceParser parser) { if (!Flags.manifestFlagging()) { return false; } @@ -227,18 +231,21 @@ public class AconfigFlags { featureFlag = featureFlag.substring(1).strip(); } final Boolean flagValue = getFlagValue(featureFlag); + boolean shouldSkip = false; if (flagValue == null) { Slog.w(LOG_TAG, "Skipping element " + parser.getName() + " due to unknown feature flag " + featureFlag); - return true; - } - // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated) - if (flagValue == negated) { + shouldSkip = true; + } else if (flagValue == negated) { + // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated) Slog.i(LOG_TAG, "Skipping element " + parser.getName() + " behind feature flag " + featureFlag + " = " + flagValue); - return true; + shouldSkip = true; + } + if (android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) { + pkg.addFeatureFlag(featureFlag, flagValue); } - return false; + return shouldSkip; } /** diff --git a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java index 8858f9492890..335dedd17f75 100644 --- a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java @@ -61,7 +61,7 @@ public class ComponentParseUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) { continue; } diff --git a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java index bb015812c225..5f48d16a7b87 100644 --- a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java +++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java @@ -54,7 +54,7 @@ public class InstallConstraintsTagParser { return input.skip("install-constraints cannot be used by this package"); } - ParseResult<Set<String>> prefixes = parseFingerprintPrefixes(input, res, parser); + ParseResult<Set<String>> prefixes = parseFingerprintPrefixes(input, pkg, res, parser); if (prefixes.isSuccess()) { if (validateFingerprintPrefixes(prefixes.getResult())) { return input.success(pkg); @@ -68,7 +68,7 @@ public class InstallConstraintsTagParser { } private static ParseResult<Set<String>> parseFingerprintPrefixes( - ParseInput input, Resources res, XmlResourceParser parser) + ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) throws XmlPullParserException, IOException { Set<String> prefixes = new ArraySet<>(); int type; @@ -81,7 +81,7 @@ public class InstallConstraintsTagParser { } return input.success(prefixes); } else if (type == XmlPullParser.START_TAG) { - if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) { continue; } if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) { diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java index 55baa532b434..a35150b30846 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java @@ -393,7 +393,7 @@ public class ParsedActivityUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) { continue; } diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java index da48b23a2b81..39d7af6c4c4a 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java @@ -99,7 +99,7 @@ public class ParsedIntentInfoUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) { continue; } @@ -200,7 +200,7 @@ public class ParsedIntentInfoUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) { continue; } diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java index 6af2a29822a2..3726b42e3e8e 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java @@ -174,7 +174,7 @@ public class ParsedProviderUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) { continue; } diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java index c68ea2dc8516..9601813ce496 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java @@ -138,7 +138,7 @@ public class ParsedServiceUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) { continue; } diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index 5d185af17d48..341beed14137 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -123,6 +123,9 @@ public interface ParsingPackage { ParsingPackage addQueriesProvider(String authority); + /** Adds a feature flag (`android:featureFlag` attribute) encountered in the manifest. */ + ParsingPackage addFeatureFlag(@NonNull String flagPackageAndName, @Nullable Boolean flagValue); + /** Sets a process name -> {@link ParsedProcess} map coming from the <processes> tag. */ ParsingPackage setProcesses(@NonNull Map<String, ParsedProcess> processes); diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index 787006eb214c..bb733f220068 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -763,7 +763,7 @@ public class ParsingPackageUtils { if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) { continue; } - if (sAconfigFlags.skipCurrentElement(parser)) { + if (sAconfigFlags.skipCurrentElement(pkg, parser)) { continue; } @@ -842,7 +842,7 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (sAconfigFlags.skipCurrentElement(parser)) { + if (sAconfigFlags.skipCurrentElement(pkg, parser)) { continue; } @@ -988,7 +988,7 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (sAconfigFlags.skipCurrentElement(parser)) { + if (sAconfigFlags.skipCurrentElement(pkg, parser)) { continue; } @@ -1610,7 +1610,7 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (sAconfigFlags.skipCurrentElement(parser)) { + if (sAconfigFlags.skipCurrentElement(pkg, parser)) { continue; } @@ -1853,7 +1853,7 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (sAconfigFlags.skipCurrentElement(parser)) { + if (sAconfigFlags.skipCurrentElement(pkg, parser)) { continue; } if (parser.getName().equals("intent")) { @@ -2202,7 +2202,7 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (sAconfigFlags.skipCurrentElement(parser)) { + if (sAconfigFlags.skipCurrentElement(pkg, parser)) { continue; } @@ -2620,7 +2620,7 @@ public class ParsingPackageUtils { } } - ParseResult<String[]> certResult = parseAdditionalCertificates(input, res, parser); + ParseResult<String[]> certResult = parseAdditionalCertificates(input, pkg, res, parser); if (certResult.isError()) { return input.error(certResult); } @@ -2674,7 +2674,8 @@ public class ParsingPackageUtils { // Fot apps targeting O-MR1 we require explicit enumeration of all certs. String[] additionalCertSha256Digests = EmptyArray.STRING; if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O_MR1) { - ParseResult<String[]> certResult = parseAdditionalCertificates(input, res, parser); + ParseResult<String[]> certResult = + parseAdditionalCertificates(input, pkg, res, parser); if (certResult.isError()) { return input.error(certResult); } @@ -2782,7 +2783,7 @@ public class ParsingPackageUtils { } private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input, - Resources resources, XmlResourceParser parser) + ParsingPackage pkg, Resources resources, XmlResourceParser parser) throws XmlPullParserException, IOException { String[] certSha256Digests = EmptyArray.STRING; final int depth = parser.getDepth(); @@ -2793,7 +2794,7 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } - if (sAconfigFlags.skipCurrentElement(parser)) { + if (sAconfigFlags.skipCurrentElement(pkg, parser)) { continue; } diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java index 2db454aa4c41..db65bf059319 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java +++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java @@ -33,6 +33,7 @@ import com.android.internal.pm.parsing.IPackageCacher; import com.android.internal.pm.parsing.PackageParser2; import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.component.AconfigFlags; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.ApexManager; @@ -41,6 +42,8 @@ import libcore.io.IoUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; public class PackageCacher implements IPackageCacher { @@ -57,6 +60,8 @@ public class PackageCacher implements IPackageCacher { @Nullable private final PackageParser2.Callback mCallback; + private static final AconfigFlags sAconfigFlags = ParsingPackageUtils.getAconfigFlags(); + public PackageCacher(File cacheDir) { this(cacheDir, null); } @@ -136,7 +141,7 @@ public class PackageCacher implements IPackageCacher { * Given a {@code packageFile} and a {@code cacheFile} returns whether the * cache file is up to date based on the mod-time of both files. */ - private static boolean isCacheUpToDate(File packageFile, File cacheFile) { + private static boolean isCacheFileUpToDate(File packageFile, File cacheFile) { try { // In case packageFile is located on one of /apex mount points it's mtime will always be // 0. Instead, we can use mtime of the APEX file backing the corresponding mount point. @@ -185,16 +190,36 @@ public class PackageCacher implements IPackageCacher { try { // If the cache is not up to date, return null. - if (!isCacheUpToDate(packageFile, cacheFile)) { + if (!isCacheFileUpToDate(packageFile, cacheFile)) { return null; } final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath()); - ParsedPackage parsed = fromCacheEntry(bytes); + final ParsedPackage parsed = fromCacheEntry(bytes); if (!packageFile.getAbsolutePath().equals(parsed.getPath())) { // Don't use this cache if the path doesn't match return null; } + + if (!android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) { + return parsed; + } + + final Map<String, Boolean> featureFlagState = + ((PackageImpl) parsed).getFeatureFlagState(); + if (!featureFlagState.isEmpty()) { + Slog.d(TAG, "Feature flags for package " + packageFile + ": " + featureFlagState); + for (var entry : featureFlagState.entrySet()) { + final String flagPackageAndName = entry.getKey(); + if (!Objects.equals(sAconfigFlags.getFlagValue(flagPackageAndName), + entry.getValue())) { + Slog.i(TAG, "Feature flag " + flagPackageAndName + " changed for package " + + packageFile + "; cached result is invalid"); + return null; + } + } + } + return parsed; } catch (Throwable e) { Slog.w(TAG, "Error reading package cache: ", e); diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index d4b57f191ecd..f439770bd648 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -26,6 +26,8 @@ import android.content.pm.SigningDetails import android.net.Uri import android.os.Bundle import android.os.Parcelable +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.util.ArraySet import android.util.SparseArray import android.util.SparseIntArray @@ -47,14 +49,19 @@ import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl import com.android.server.pm.pkg.AndroidPackage import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever +import org.junit.Rule import java.security.KeyPairGenerator import java.security.PublicKey import java.util.UUID import kotlin.contracts.ExperimentalContracts @ExperimentalContracts +@EnableFlags(android.content.pm.Flags.FLAG_INCLUDE_FEATURE_FLAGS_IN_PACKAGE_CACHER) class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) { + @get:Rule + val setFlagsRule: SetFlagsRule = SetFlagsRule() + companion object { private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac") } @@ -93,6 +100,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag "getUsesOptionalLibrariesSorted", "getUsesSdkLibrariesSorted", "getUsesStaticLibrariesSorted", + "readFeatureFlagState", + "writeFeatureFlagState", // Tested through setting minor/major manually "setLongVersionCode", "getLongVersionCode", @@ -149,6 +158,10 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag "isSystem", "isSystemExt", "isVendor", + + // Tested through addFeatureFlag + "addFeatureFlag", + "getFeatureFlagState", ) override val baseParams = listOf( @@ -613,6 +626,9 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne") .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"), true) .addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2")) + .addFeatureFlag("testFlag1", null) + .addFeatureFlag("testFlag2", true) + .addFeatureFlag("testFlag3", false) override fun finalizeObject(parcelable: Parcelable) { (parcelable as PackageImpl).hideAsParsed().hideAsFinal() @@ -673,6 +689,12 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag .containsExactly("testCertDigest2") expect.that(after.storageUuid).isEqualTo(TEST_UUID) + + expect.that(after.featureFlagState).containsExactlyEntriesIn(mapOf( + "testFlag1" to null, + "testFlag2" to true, + "testFlag3" to false, + )) } private fun testKey() = KeyPairGenerator.getInstance("RSA") |