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

Commit d3a000be authored by Akhil Gangu's avatar Akhil Gangu
Browse files

Add parsing support for new purpose declaration manifest elements.

This is an attempt to reland commit 089a8a61 after it has been reverted due to perf regressions (boot-time). The hypothesis is that the regression was caused due to the population of new usesPermissionMapping that has been added in AndroidPackage. Considering there could be a lot of uses-permission use cases, this population of the map might have increased the latency. This CL flag guards adding entries into the map so the change can be relanded without any impact. The regression is expected to show up again when progressing the flag, but is reasonable to seek an override considering the parsed results are cached for subsequent boots.

Bug: 417787288
Bug: 420985780
Test: atest PermissionInfoTest AndroidPackageTest ParsedPermissionTest ParsedUsesPermissionTest
Test: atest PurposeDeclarationPermissionsTest
Flag: android.permission.flags.purpose_declaration_enabled
Change-Id: Ibbef57b8eb3a3ba1281656b6a1f6a73ca3137c18
parent 824e843a
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -508,6 +508,21 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
    @SystemApi
    public @NonNull Set<String> knownCerts = Collections.emptySet();

    /**
     * A boolean to signify if purposes are required to be declared in order to use the permission.
     *
     * @hide
     */
    public boolean requiresPurpose;

    /**
     * A {@link Set} of valid purposes defined for using this permission. Apps that request this
     * permission will be required to declare at least one permission from this set.
     *
     * @hide
     */
    public @NonNull Set<String> validPurposes = Collections.emptySet();

    /** @hide */
    public static int fixProtectionLevel(int level) {
        if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
@@ -679,6 +694,8 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
        nonLocalizedDescription = orig.nonLocalizedDescription;
        // Note that knownCerts wasn't properly copied before Android U.
        knownCerts = orig.knownCerts;
        requiresPurpose = orig.requiresPurpose;
        validPurposes = orig.validPurposes;
    }

    /**
@@ -744,6 +761,8 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
        dest.writeInt(requestRes);
        TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
        sForStringSet.parcel(knownCerts, dest, parcelableFlags);
        dest.writeBoolean(requiresPurpose);
        sForStringSet.parcel(validPurposes, dest, parcelableFlags);
    }

    /** @hide */
@@ -805,5 +824,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
        requestRes = source.readInt();
        nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
        knownCerts = sForStringSet.unparcel(source);
        requiresPurpose = source.readBoolean();
        validPurposes = sForStringSet.unparcel(source);
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -509,6 +509,8 @@ public class PackageInfoCommonUtils {
        pi.descriptionRes = p.getDescriptionRes();
        pi.flags = p.getFlags();
        pi.knownCerts = p.getKnownCerts();
        pi.requiresPurpose = p.isPurposeRequired();
        pi.validPurposes = p.getValidPurposes();

        if ((flags & PackageManager.GET_META_DATA) == 0) {
            pi.metaData = null;
+49 −4
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.permission.flags.Flags;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -283,6 +284,8 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
    @NonNull
    private List<ParsedUsesPermission> usesPermissions = emptyList();
    @NonNull
    private Map<String, ParsedUsesPermission> usesPermissionMapping = emptyMap();
    @NonNull
    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
    private Set<String> implicitPermissions = emptySet();
    @NonNull
@@ -545,7 +548,9 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,

    @Override
    public PackageImpl addImplicitPermission(String permission) {
        addUsesPermission(new ParsedUsesPermissionImpl(permission, 0 /*usesPermissionFlags*/));
        addUsesPermission(
                new ParsedUsesPermissionImpl(
                        permission, /* usesPermissionFlags= */ 0, /* purposes= */ emptySet()));
        this.implicitPermissions = CollectionUtils.add(this.implicitPermissions,
                TextUtils.safeIntern(permission));
        return this;
@@ -722,14 +727,23 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
        return this;
    }

    // TODO(419394776) - Use single source of truth for storing uses permission metadata.
    @Override
    public PackageImpl addUsesPermission(ParsedUsesPermission permission) {
        this.usesPermissions = CollectionUtils.add(this.usesPermissions, permission);

        // Continue populating legacy data structures to avoid performance
        // issues until all that code can be migrated
        this.requestedPermissions = CollectionUtils.add(this.requestedPermissions,
                permission.getName());
        this.requestedPermissions =
                CollectionUtils.add(this.requestedPermissions, permission.getName());

        if (Flags.purposeDeclarationEnabled()) {
            // During manifest parsing, we ignore duplicate permission requests. Therefore, it's
            // safe to directly add to the mapping.
            this.usesPermissionMapping =
                    CollectionUtils.add(
                            this.usesPermissionMapping, permission.getName(), permission);
        }

        return this;
    }
@@ -1493,7 +1507,15 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,

    @NonNull
    @Override
    public List<String> getUsesSdkLibraries() { return usesSdkLibraries; }
    public Map<String, ParsedUsesPermission> getUsesPermissionMapping() {
        return usesPermissionMapping;
    }

    @NonNull
    @Override
    public List<String> getUsesSdkLibraries() {
        return usesSdkLibraries;
    }

    @NonNull
    @Override
@@ -2857,6 +2879,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
        reqFeatures = Collections.unmodifiableList(reqFeatures);
        featureGroups = Collections.unmodifiableList(featureGroups);
        usesPermissions = Collections.unmodifiableList(usesPermissions);
        usesPermissionMapping = Collections.unmodifiableMap(usesPermissionMapping);
        usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries);
        implicitPermissions = Collections.unmodifiableSet(implicitPermissions);
        upgradeKeySets = Collections.unmodifiableSet(upgradeKeySets);
@@ -3237,6 +3260,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
        sForInternedStringList.parcel(this.adoptPermissions, dest, flags);
        sForInternedStringSet.parcel(this.requestedPermissions, dest, flags);
        ParsingUtils.writeParcelableList(dest, this.usesPermissions);
        writeUsesPermissionMapping(dest);
        sForInternedStringSet.parcel(this.implicitPermissions, dest, flags);
        sForStringSet.parcel(this.upgradeKeySets, dest, flags);
        ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping);
@@ -3325,6 +3349,15 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
        dest.writeInt(this.mPageSizeAppCompatFlags);
    }

    private void writeUsesPermissionMapping(@NonNull Parcel dest) {
        // No need to deal with null case because the mapping is already instantiated to be empty.
        final Bundle bundle = new Bundle();
        for (Map.Entry<String, ParsedUsesPermission> entry : usesPermissionMapping.entrySet()) {
            bundle.putParcelable(entry.getKey(), (ParsedUsesPermissionImpl) entry.getValue());
        }
        dest.writeBundle(bundle);
    }

    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).
@@ -3426,6 +3459,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
        this.requestedPermissions = sForInternedStringSet.unparcel(in);
        this.usesPermissions = ParsingUtils.createTypedInterfaceList(in,
                ParsedUsesPermissionImpl.CREATOR);
        readUsesPermissionMapping(in);
        this.implicitPermissions = sForInternedStringSet.unparcel(in);
        this.upgradeKeySets = sForStringSet.unparcel(in);
        this.keySetMapping = ParsingPackageUtils.readKeySetMapping(in);
@@ -3526,6 +3560,17 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
        // to mutate this instance before it's finalized.
    }

    private void readUsesPermissionMapping(@NonNull Parcel in) {
        final Bundle bundle = in.readBundle(ParsedUsesPermissionImpl.class.getClassLoader());
        final Map<String, ParsedUsesPermission> mapping = new ArrayMap<>();
        if (bundle != null) {
            for (String key : bundle.keySet()) {
                mapping.put(key, bundle.getParcelable(key, ParsedUsesPermissionImpl.class));
            }
        }
        usesPermissionMapping = mapping;
    }

    private void readFeatureFlagState(@NonNull Parcel in) {
        // See comment in writeFeatureFlagState() for encoding of flag state.
        String[] featureFlagStateAsArray = in.createStringArray();
+37 −1
Original line number Diff line number Diff line
@@ -27,8 +27,11 @@ import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.permission.flags.Flags;
import android.text.TextUtils;
import android.util.ArraySet;

import com.android.internal.R;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
@@ -38,6 +41,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.Set;

/**
 * @hide
@@ -55,6 +59,14 @@ public class ComponentParseUtils {
    static <Component extends ParsedComponentImpl> ParseResult<Component> parseAllMetaData(
            ParsingPackage pkg, Resources res, XmlResourceParser parser, String tag,
            Component component, ParseInput input) throws XmlPullParserException, IOException {
        // Beginning in Android 17, permissions may specify valid usage purposes. Currently, valid
        // purposes are only processed for enforcement if the permission is defined within the
        // Android platform manifest. This limitation might be lifted in future versions.
        final boolean shouldParseValidPurposes =
                Flags.purposeDeclarationEnabled()
                        && component instanceof ParsedPermissionImpl
                        && "android".equals(pkg.getPackageName());
        final Set<String> validPurposes = new ArraySet<>();
        final int depth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -67,9 +79,16 @@ public class ComponentParseUtils {
                continue;
            }

            final ParseResult result;
            final ParseResult<?> result;
            if ("meta-data".equals(parser.getName())) {
                result = ParsedComponentUtils.addMetaData(component, pkg, res, parser, input);
            } else if (shouldParseValidPurposes && "valid-purpose".equals(parser.getName())) {
                final ParseResult<String> validPurposeResult =
                        parseValidPurpose(res, parser, input);
                result = validPurposeResult;
                if (validPurposeResult.isSuccess() && validPurposeResult.getResult() != null) {
                    validPurposes.add(validPurposeResult.getResult());
                }
            } else {
                result = ParsingUtils.unknownTag(tag, pkg, parser, input);
            }
@@ -79,9 +98,26 @@ public class ComponentParseUtils {
            }
        }

        if (!validPurposes.isEmpty()) {
            // There can only be valid purposes if component is an instance of ParsedPermissionImpl.
            final ParsedPermissionImpl permission = (ParsedPermissionImpl) component;
            permission.setValidPurposes(validPurposes);
        }

        return input.success(component);
    }

    private static ParseResult<String> parseValidPurpose(
            Resources res, XmlResourceParser parser, ParseInput input) {
        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestValidPurpose);
        try {
            final String validPurpose = sa.getString(R.styleable.AndroidManifestValidPurpose_name);
            return input.success(TextUtils.isEmpty(validPurpose) ? null : validPurpose);
        } finally {
            sa.recycle();
        }
    }

    @NonNull
    public static ParseResult<String> buildProcessName(@NonNull String pkg, String defProc,
            CharSequence procSeq, int flags, String[] separateProcesses, ParseInput input) {
+5 −0
Original line number Diff line number Diff line
@@ -41,5 +41,10 @@ public interface ParsedPermission extends ParsedComponent {

    int getRequestRes();

    boolean isPurposeRequired();

    @NonNull
    Set<String> getValidPurposes();

    boolean isTree();
}
Loading