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

Commit fa739289 authored by Akhil Gangu's avatar Akhil Gangu Committed by Android (Google) Code Review
Browse files

Merge changes from topic "pd-compat" into main

* changes:
  Re-evaluate permissions requiring purpose upon package removal.
  Update install permissions policy to handle app compat purpose declarations.
  Parsing logic for additional elements to support purpose declaration app compatibility.
parents 33b7e032 d35ec85f
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