Loading core/java/android/content/pm/PermissionInfo.java +21 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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; } /** Loading Loading @@ -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 */ Loading Loading @@ -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); } } core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java +2 −0 Original line number Diff line number Diff line Loading @@ -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; Loading core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +49 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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). Loading Loading @@ -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); Loading Loading @@ -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(); Loading core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java +37 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -38,6 +41,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.Set; /** * @hide Loading @@ -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 Loading @@ -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); } Loading @@ -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) { Loading core/java/com/android/internal/pm/pkg/component/ParsedPermission.java +5 −0 Original line number Diff line number Diff line Loading @@ -41,5 +41,10 @@ public interface ParsedPermission extends ParsedComponent { int getRequestRes(); boolean isPurposeRequired(); @NonNull Set<String> getValidPurposes(); boolean isTree(); } Loading
core/java/android/content/pm/PermissionInfo.java +21 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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; } /** Loading Loading @@ -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 */ Loading Loading @@ -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); } }
core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java +2 −0 Original line number Diff line number Diff line Loading @@ -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; Loading
core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +49 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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 Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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). Loading Loading @@ -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); Loading Loading @@ -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(); Loading
core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java +37 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -38,6 +41,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.Set; /** * @hide Loading @@ -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 Loading @@ -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); } Loading @@ -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) { Loading
core/java/com/android/internal/pm/pkg/component/ParsedPermission.java +5 −0 Original line number Diff line number Diff line Loading @@ -41,5 +41,10 @@ public interface ParsedPermission extends ParsedComponent { int getRequestRes(); boolean isPurposeRequired(); @NonNull Set<String> getValidPurposes(); boolean isTree(); }