Teach PackageManager to handle <required-(not-)feature>

Introducing 'required-feature' and 'requred-not-feature' tags that could
be used "inside" 'uses-permission' elements.
Elements of both types should have non-empty 'android:name' attribute to
specify the name of the feature.
If the 'uses-permission' element contains 'required-feature' and/or
'required-not-feature' elements, the permission will only be "requested"
if the system supports all the 'required-feature's and does not support
any of the 'required-not-feature's.
The new elemets are "replacement" of the existing
'android:required(Not)Feature' attributes that can be optionally added
to 'uses-permission' elements, but allow to specify only 1 feature each.
However the 'android:required(Not)Feature' attributes remain supported
for compatibility and will be treated similarly to the
'require-(not-)feature' elements.

Bug: 168079571
Test: make && flash
Test: atest CtsContentTestCases:PermissionFeatureTest
Change-Id: Id3043530ebac6bd11805a611cebbf08b45e12f68
This commit is contained in:
Sergey Nikolaienkov
2020-09-25 10:33:14 +00:00
parent 65f9099ad5
commit 48e47d1a2c
2 changed files with 110 additions and 16 deletions

View File

@@ -1080,14 +1080,57 @@ public class ParsingPackageUtils {
}
}
final String requiredFeature = sa.getNonConfigurationString(
R.styleable.AndroidManifestUsesPermission_requiredFeature, 0);
final String requiredNotfeature = sa.getNonConfigurationString(
R.styleable.AndroidManifestUsesPermission_requiredNotFeature,
final ArraySet<String> requiredFeatures = new ArraySet<>();
String feature = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature,
0);
if (feature != null) {
requiredFeatures.add(feature);
}
XmlUtils.skipCurrentTag(parser);
final ArraySet<String> requiredNotFeatures = new ArraySet<>();
feature = sa.getNonConfigurationString(
com.android.internal.R.styleable
.AndroidManifestUsesPermission_requiredNotFeature,
0);
if (feature != null) {
requiredNotFeatures.add(feature);
}
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
final ParseResult<?> result;
switch (parser.getName()) {
case "required-feature":
result = parseRequiredFeature(input, res, parser);
if (result.isSuccess()) {
requiredFeatures.add((String) result.getResult());
}
break;
case "required-not-feature":
result = parseRequiredNotFeature(input, res, parser);
if (result.isSuccess()) {
requiredNotFeatures.add((String) result.getResult());
}
break;
default:
result = ParsingUtils.unknownTag("<uses-permission>", pkg, parser, input);
break;
}
if (result.isError()) {
return input.error(result);
}
}
// Can only succeed from here on out
ParseResult<ParsingPackage> success = input.success(pkg);
@@ -1100,17 +1143,22 @@ public class ParsingPackageUtils {
return success;
}
// Only allow requesting this permission if the platform supports the given feature.
if (requiredFeature != null && mCallback != null && !mCallback.hasFeature(
requiredFeature)) {
return success;
}
if (mCallback != null) {
// Only allow requesting this permission if the platform supports all of the
// "required-feature"s.
for (int i = requiredFeatures.size() - 1; i >= 0; i--) {
if (!mCallback.hasFeature(requiredFeatures.valueAt(i))) {
return success;
}
}
// Only allow requesting this permission if the platform doesn't support the given
// feature.
if (requiredNotfeature != null && mCallback != null
&& mCallback.hasFeature(requiredNotfeature)) {
return success;
// Only allow requesting this permission if the platform does not supports any of
// the "required-not-feature"s.
for (int i = requiredNotFeatures.size() - 1; i >= 0; i--) {
if (mCallback.hasFeature(requiredNotFeatures.valueAt(i))) {
return success;
}
}
}
if (!pkg.getRequestedPermissions().contains(name)) {
@@ -1127,6 +1175,36 @@ public class ParsingPackageUtils {
}
}
private ParseResult<String> parseRequiredFeature(ParseInput input, Resources res,
AttributeSet attrs) {
final TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestRequiredFeature);
try {
final String featureName = sa.getString(
R.styleable.AndroidManifestRequiredFeature_name);
return TextUtils.isEmpty(featureName)
? input.error("Feature name is missing from <required-feature> tag.")
: input.success(featureName);
} finally {
sa.recycle();
}
}
private ParseResult<String> parseRequiredNotFeature(ParseInput input, Resources res,
AttributeSet attrs) {
final TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestRequiredNotFeature);
try {
final String featureName = sa.getString(
R.styleable.AndroidManifestRequiredNotFeature_name);
return TextUtils.isEmpty(featureName)
? input.error("Feature name is missing from <required-not-feature> tag.")
: input.success(featureName);
} finally {
sa.recycle();
}
}
private static ParseResult<ParsingPackage> parseUsesConfiguration(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
ConfigurationInfo cPref = new ConfigurationInfo();

View File

@@ -2016,6 +2016,22 @@
<attr name="requiredNotFeature" format="string" />
</declare-styleable>
<!-- <code>required-feature</code> and <code>required-not-feature</code> elements inside
<code>uses-permission<code/> can be used to request the permission based on the fact
whether the system supports or does not support certain features.
If multiple <code>required-feature</code> and/or <code>required-not-feature</code> elements
are present, the permission will be “requested” only if the system supports all of the
listed "required-features" and does not support any of the "required-not-features".
-->
<declare-styleable name="AndroidManifestRequiredFeature">
<!-- The name of the feature. -->
<attr name="name" />
</declare-styleable>
<declare-styleable name="AndroidManifestRequiredNotFeature">
<!-- The name of the feature. -->
<attr name="name" />
</declare-styleable>
<!-- The <code>uses-configuration</code> tag specifies
a specific hardware configuration value used by the application.
For example an application might specify that it requires