Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1c5e38ca authored by Mark Punzalan's avatar Mark Punzalan
Browse files

Include feature flag state in cached package

We write the values of all feature flags encountered in the manifest in
the cached package. We then read these values in PackageCacher and
compare them to the current feature flag values. A cached package is
invalid if any of the flag values changed, and therefore the package
needs to be re-parsed.

Flag: android.content.pm.include_feature_flags_in_package_cacher
Bug: 364771256
Test: atest PackageManagerServiceUnitTests PackageManagerServiceServerTests
Test: Manually with include_feature_flags_in_package_cacher enabled.
Flipped the value of `android.content.pm.quarantined_enabled` back and
forth and verified the permission `android.permission.QUARANTINE_APPS`
is present (or absent) after a reboot. Inspected the logs to confirm
that PackageCacher invalidated the entry when the flag is flipped.

Change-Id: I6d78b1086374b80e66f921a53651b77867203eca
parent 3b544b85
Loading
Loading
Loading
Loading
+62 −0
Original line number Diff line number Diff line
@@ -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
+15 −8
Original line number Diff line number Diff line
@@ -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;
        }
            shouldSkip = true;
        } else if (flagValue == negated) {
            // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
        if (flagValue == negated) {
            Slog.i(LOG_TAG, "Skipping element " + parser.getName()
                    + " behind feature flag " + featureFlag + " = " + flagValue);
            return true;
            shouldSkip = true;
        }
        return false;
        if (android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) {
            pkg.addFeatureFlag(featureFlag, flagValue);
        }
        return shouldSkip;
    }

    /**
+1 −1
Original line number Diff line number Diff line
@@ -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;
            }

+3 −3
Original line number Diff line number Diff line
@@ -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)) {
+1 −1
Original line number Diff line number Diff line
@@ -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;
            }

Loading