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

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

Parsing logic for additional elements to support purpose declaration app

compatibility.

Updating PermissionInfo with a map where the keys contain the valid
purposes and the value is the metadata associated with the purpose
itself. Also updated PermissionInfo with the
requirePurposeTargetSdkVersion attribute.

Bug: 422817717
Test: atest PermissionInfoTest ParsedPermissionTest ParsedValidPurposeTest PackageStateTest
Flag: android.permission.flags.purpose_declaration_enabled
Change-Id: Ica86cc49d8455da7704ebfb02fa9464a653c4162
parent a7416534
Loading
Loading
Loading
Loading
+38 −5
Original line number Diff line number Diff line
@@ -24,16 +24,19 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;

import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Parcelling;
import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
@@ -516,12 +519,19 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
    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.
     * Specifies the minimum target SDK version for which purpose validation should be enforced.
     *
     * @hide
     */
    public @NonNull Set<String> validPurposes = Collections.emptySet();
    public int requiresPurposeTargetSdkVersion;

    /**
     * A {@link Map} of valid purposes where the key represents the name of the purpose and value
     * represents the {@link ValidPurposeInfo} metadata associated with the purpose.
     *
     * @hide
     */
    public @NonNull Map<String, ValidPurposeInfo> validPurposes = Collections.emptyMap();

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

@@ -762,7 +773,8 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
        TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
        sForStringSet.parcel(knownCerts, dest, parcelableFlags);
        dest.writeBoolean(requiresPurpose);
        sForStringSet.parcel(validPurposes, dest, parcelableFlags);
        dest.writeInt(requiresPurposeTargetSdkVersion);
        writeValidPurposes(dest);
    }

    /** @hide */
@@ -825,6 +837,27 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
        nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
        knownCerts = sForStringSet.unparcel(source);
        requiresPurpose = source.readBoolean();
        validPurposes = sForStringSet.unparcel(source);
        requiresPurposeTargetSdkVersion = source.readInt();
        readValidPurposes(source);
    }

    private void readValidPurposes(@NonNull Parcel in) {
        final Bundle bundle = in.readBundle(ValidPurposeInfo.class.getClassLoader());
        Map<String, ValidPurposeInfo> purposes = Collections.emptyMap();
        // Null case handling not required as null bundles are not written.
        for (String key : bundle.keySet()) {
            purposes =
                    CollectionUtils.add(
                            purposes, key, bundle.getParcelable(key, ValidPurposeInfo.class));
        }
        validPurposes = purposes;
    }

    private void writeValidPurposes(@NonNull Parcel dest) {
        final Bundle bundle = new Bundle();
        for (Map.Entry<String, ValidPurposeInfo> entry : validPurposes.entrySet()) {
            bundle.putParcelable(entry.getKey(), entry.getValue());
        }
        dest.writeBundle(bundle);
    }
}
+133 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content.pm;

import android.annotation.NonNull;
import android.os.Parcelable;

import com.android.internal.util.DataClass;

/**
 * Information about {@link android.R.styleable#AndroidManifestValidPurpose &lt;valid-purpose&gt;}
 * tag parsed from the manifest.
 *
 * @hide
 */
@DataClass(genGetters = true, genSetters = false, genParcelable = true, genAidl = false,
        genBuilder = false)
public class ValidPurposeInfo implements Parcelable {
    @NonNull
    private String name;

    private int maxTargetSdkVersion;



    // Code below generated by codegen v1.0.23.
    //
    // DO NOT MODIFY!
    // CHECKSTYLE:OFF Generated code
    //
    // To regenerate run:
    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ValidPurposeInfo.java
    //
    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
    //   Settings > Editor > Code Style > Formatter Control
    //@formatter:off


    @DataClass.Generated.Member
    public ValidPurposeInfo(
            @NonNull String name,
            int maxTargetSdkVersion) {
        this.name = name;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, name);
        this.maxTargetSdkVersion = maxTargetSdkVersion;

        // onConstructed(); // You can define this method to get a callback
    }

    @DataClass.Generated.Member
    public @NonNull String getName() {
        return name;
    }

    @DataClass.Generated.Member
    public int getMaxTargetSdkVersion() {
        return maxTargetSdkVersion;
    }

    @Override
    @DataClass.Generated.Member
    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
        // You can override field parcelling by defining methods like:
        // void parcelFieldName(Parcel dest, int flags) { ... }

        dest.writeString(name);
        dest.writeInt(maxTargetSdkVersion);
    }

    @Override
    @DataClass.Generated.Member
    public int describeContents() { return 0; }

    /** @hide */
    @SuppressWarnings({"unchecked", "RedundantCast"})
    @DataClass.Generated.Member
    protected ValidPurposeInfo(@NonNull android.os.Parcel in) {
        // You can override field unparcelling by defining methods like:
        // static FieldType unparcelFieldName(Parcel in) { ... }

        String _name = in.readString();
        int _maxTargetSdkVersion = in.readInt();

        this.name = _name;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, name);
        this.maxTargetSdkVersion = _maxTargetSdkVersion;

        // onConstructed(); // You can define this method to get a callback
    }

    @DataClass.Generated.Member
    public static final @NonNull Parcelable.Creator<ValidPurposeInfo> CREATOR
            = new Parcelable.Creator<ValidPurposeInfo>() {
        @Override
        public ValidPurposeInfo[] newArray(int size) {
            return new ValidPurposeInfo[size];
        }

        @Override
        public ValidPurposeInfo createFromParcel(@NonNull android.os.Parcel in) {
            return new ValidPurposeInfo(in);
        }
    };

    @DataClass.Generated(
            time = 1751306273710L,
            codegenVersion = "1.0.23",
            sourceFile = "frameworks/base/core/java/android/content/pm/ValidPurposeInfo.java",
            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate  int maxTargetSdkVersion\nclass ValidPurposeInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
    @Deprecated
    private void __metadata() {}


    //@formatter:on
    // End of generated code

}
+13 −1
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
import android.content.pm.ValidPurposeInfo;
import android.os.Debug;
import android.os.PatternMatcher;
import android.os.UserHandle;
@@ -58,10 +59,12 @@ import com.android.internal.pm.pkg.component.ParsedPermission;
import com.android.internal.pm.pkg.component.ParsedProvider;
import com.android.internal.pm.pkg.component.ParsedService;
import com.android.internal.pm.pkg.component.ParsedUsesPermission;
import com.android.internal.pm.pkg.component.ParsedValidPurpose;
import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.pm.pkg.AndroidPackage;

import java.util.List;
@@ -510,7 +513,16 @@ public class PackageInfoCommonUtils {
        pi.flags = p.getFlags();
        pi.knownCerts = p.getKnownCerts();
        pi.requiresPurpose = p.isPurposeRequired();
        pi.validPurposes = p.getValidPurposes();
        pi.requiresPurposeTargetSdkVersion = p.getRequiresPurposeTargetSdkVersion();
        for (ParsedValidPurpose validPurpose : p.getValidPurposes()) {
            if (validPurpose != null) {
                pi.validPurposes =
                        CollectionUtils.add(pi.validPurposes, validPurpose.getName(),
                                new ValidPurposeInfo(
                                        validPurpose.getName(),
                                        validPurpose.getMaxTargetSdkVersion()));
            }
        }

        if ((flags & PackageManager.GET_META_DATA) == 0) {
            pi.metaData = null;
+17 −8
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ 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;
@@ -41,7 +40,8 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

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

/**
 * @hide
@@ -66,7 +66,7 @@ public class ComponentParseUtils {
                Flags.purposeDeclarationEnabled()
                        && component instanceof ParsedPermissionImpl
                        && "android".equals(pkg.getPackageName());
        final Set<String> validPurposes = new ArraySet<>();
        final List<ParsedValidPurpose> validPurposes = new ArrayList<>();
        final int depth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -83,10 +83,10 @@ public class ComponentParseUtils {
            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 =
                final ParseResult<ParsedValidPurpose> validPurposeResult =
                        parseValidPurpose(res, parser, input);
                result = validPurposeResult;
                if (validPurposeResult.isSuccess() && validPurposeResult.getResult() != null) {
                if (validPurposeResult.isSuccess()) {
                    validPurposes.add(validPurposeResult.getResult());
                }
            } else {
@@ -111,12 +111,21 @@ public class ComponentParseUtils {
        return input.success(component);
    }

    private static ParseResult<String> parseValidPurpose(
    private static ParseResult<ParsedValidPurpose> 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);
            final String name = sa.getString(R.styleable.AndroidManifestValidPurpose_name);
            if (TextUtils.isEmpty(name)) {
                return input.error(
                        "The android:name attribute for <valid-purpose> cannot be null or empty!");
            }
            final int maxTargetSdkVersion =
                    ParsingPackageUtils.parseMinOrMaxSdkVersion(
                            sa,
                            R.styleable.AndroidManifestValidPurpose_maxTargetSdkVersion,
                            /* defaultValue= */ Integer.MAX_VALUE);
            return input.success(new ParsedValidPurposeImpl(name, maxTargetSdkVersion));
        } finally {
            sa.recycle();
        }
+4 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.internal.pm.pkg.component;
import android.annotation.NonNull;
import android.annotation.Nullable;

import java.util.List;
import java.util.Set;

/** @hide */
@@ -43,8 +44,10 @@ public interface ParsedPermission extends ParsedComponent {

    boolean isPurposeRequired();

    int getRequiresPurposeTargetSdkVersion();

    @NonNull
    Set<String> getValidPurposes();
    List<ParsedValidPurpose> getValidPurposes();

    boolean isTree();
}
Loading