From 65f9099ad505984578c97c317a578f46abeb45b9 Mon Sep 17 00:00:00 2001 From: Sergey Nikolaienkov Date: Wed, 30 Sep 2020 08:17:09 +0000 Subject: [PATCH 1/2] Teach appt2 to handle 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. Bug: 168079571 Test: make aapt2 Test: make aapt2_tests Test: out/host/linux-x86/nativetest64/aapt2_tests/aapt2_tests Test: create AndroidManifest.xml with Test: aapt2 link -o out.apk -I $ANDROID_SDK/platforms/android-30/android.jar --manifest AndroidManifest.xml Test: aapt2 dump badging out.apk Test: appt2 dump permissions out.apk Change-Id: I67ba0731daa6d31cd976b922217853f159cf7c3a --- tools/aapt2/dump/DumpManifest.cpp | 199 ++++++++++++++++++----------- tools/aapt2/link/ManifestFixer.cpp | 2 + 2 files changed, 123 insertions(+), 78 deletions(-) diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 74978696fdd5a..8862405189c09 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -1063,17 +1063,23 @@ class UsesPermission : public ManifestExtractor::Element { public: UsesPermission() = default; std::string name; - std::string requiredFeature; - std::string requiredNotFeature; + std::vector requiredFeatures; + std::vector requiredNotFeatures; int32_t required = true; int32_t maxSdkVersion = -1; void Extract(xml::Element* element) override { name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - requiredFeature = GetAttributeStringDefault( - FindAttribute(element, REQUIRED_FEATURE_ATTR), ""); - requiredNotFeature = GetAttributeStringDefault( - FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), ""); + std::string feature = + GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), ""); + if (!feature.empty()) { + requiredFeatures.push_back(feature); + } + feature = GetAttributeStringDefault(FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), ""); + if (!feature.empty()) { + requiredNotFeatures.push_back(feature); + } + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); maxSdkVersion = GetAttributeIntegerDefault( FindAttribute(element, MAX_SDK_VERSION_ATTR), -1); @@ -1090,13 +1096,13 @@ class UsesPermission : public ManifestExtractor::Element { if (maxSdkVersion >= 0) { printer->Print(StringPrintf(" maxSdkVersion='%d'", maxSdkVersion)); } - if (!requiredFeature.empty()) { - printer->Print(StringPrintf(" requiredFeature='%s'", requiredFeature.data())); - } - if (!requiredNotFeature.empty()) { - printer->Print(StringPrintf(" requiredNotFeature='%s'", requiredNotFeature.data())); - } printer->Print("\n"); + for (const std::string& requiredFeature : requiredFeatures) { + printer->Print(StringPrintf(" required-feature='%s'\n", requiredFeature.data())); + } + for (const std::string& requiredNotFeature : requiredNotFeatures) { + printer->Print(StringPrintf(" required-not-feature='%s'\n", requiredNotFeature.data())); + } if (required == 0) { printer->Print(StringPrintf("optional-permission: name='%s'", name.data())); if (maxSdkVersion >= 0) { @@ -1116,6 +1122,38 @@ class UsesPermission : public ManifestExtractor::Element { } }; +/** Represents elements. **/ +class RequiredFeature : public ManifestExtractor::Element { + public: + RequiredFeature() = default; + std::string name; + + void Extract(xml::Element* element) override { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + auto parent_stack = extractor()->parent_stack(); + if (!name.empty() && ElementCast(parent_stack[0])) { + UsesPermission* uses_permission = ElementCast(parent_stack[0]); + uses_permission->requiredFeatures.push_back(name); + } + } +}; + +/** Represents elements. **/ +class RequiredNotFeature : public ManifestExtractor::Element { + public: + RequiredNotFeature() = default; + std::string name; + + void Extract(xml::Element* element) override { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + auto parent_stack = extractor()->parent_stack(); + if (!name.empty() && ElementCast(parent_stack[0])) { + UsesPermission* uses_permission = ElementCast(parent_stack[0]); + uses_permission->requiredNotFeatures.push_back(name); + } + } +}; + /** Represents elements. **/ class UsesPermissionSdk23 : public ManifestExtractor::Element { public: @@ -1845,7 +1883,8 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { for (xml::Element* child : element->GetChildElements()) { if (child->name == "uses-permission" || child->name == "uses-permission-sdk-23" || child->name == "permission") { - auto permission_element = ManifestExtractor::Element::Inflate(this, child); + // Inflate the element and its descendants + auto permission_element = Visit(child); manifest->AddChild(permission_element); } } @@ -2237,38 +2276,40 @@ T* ElementCast(ManifestExtractor::Element* element) { } const std::unordered_map kTagCheck = { - {"action", std::is_base_of::value}, - {"activity", std::is_base_of::value}, - {"application", std::is_base_of::value}, - {"category", std::is_base_of::value}, - {"compatible-screens", std::is_base_of::value}, - {"feature-group", std::is_base_of::value}, - {"input-type", std::is_base_of::value}, - {"intent-filter", std::is_base_of::value}, - {"meta-data", std::is_base_of::value}, - {"manifest", std::is_base_of::value}, - {"original-package", std::is_base_of::value}, - {"overlay", std::is_base_of::value}, - {"package-verifier", std::is_base_of::value}, - {"permission", std::is_base_of::value}, - {"provider", std::is_base_of::value}, - {"receiver", std::is_base_of::value}, - {"screen", std::is_base_of::value}, - {"service", std::is_base_of::value}, - {"supports-gl-texture", std::is_base_of::value}, - {"supports-input", std::is_base_of::value}, - {"supports-screens", std::is_base_of::value}, - {"uses-configuration", std::is_base_of::value}, - {"uses-feature", std::is_base_of::value}, - {"uses-permission", std::is_base_of::value}, - {"uses-permission-sdk-23", std::is_base_of::value}, - {"uses-library", std::is_base_of::value}, - {"uses-package", std::is_base_of::value}, - {"static-library", std::is_base_of::value}, - {"uses-static-library", std::is_base_of::value}, - {"additional-certificate", std::is_base_of::value}, - {"uses-sdk", std::is_base_of::value}, - {"uses-native-library", std::is_base_of::value}, + {"action", std::is_base_of::value}, + {"activity", std::is_base_of::value}, + {"additional-certificate", std::is_base_of::value}, + {"application", std::is_base_of::value}, + {"category", std::is_base_of::value}, + {"compatible-screens", std::is_base_of::value}, + {"feature-group", std::is_base_of::value}, + {"input-type", std::is_base_of::value}, + {"intent-filter", std::is_base_of::value}, + {"meta-data", std::is_base_of::value}, + {"manifest", std::is_base_of::value}, + {"original-package", std::is_base_of::value}, + {"overlay", std::is_base_of::value}, + {"package-verifier", std::is_base_of::value}, + {"permission", std::is_base_of::value}, + {"provider", std::is_base_of::value}, + {"receiver", std::is_base_of::value}, + {"required-feature", std::is_base_of::value}, + {"required-not-feature", std::is_base_of::value}, + {"screen", std::is_base_of::value}, + {"service", std::is_base_of::value}, + {"static-library", std::is_base_of::value}, + {"supports-gl-texture", std::is_base_of::value}, + {"supports-input", std::is_base_of::value}, + {"supports-screens", std::is_base_of::value}, + {"uses-configuration", std::is_base_of::value}, + {"uses-feature", std::is_base_of::value}, + {"uses-library", std::is_base_of::value}, + {"uses-native-library", std::is_base_of::value}, + {"uses-package", std::is_base_of::value}, + {"uses-permission", std::is_base_of::value}, + {"uses-permission-sdk-23", std::is_base_of::value}, + {"uses-sdk", std::is_base_of::value}, + {"uses-static-library", std::is_base_of::value}, }; auto check = kTagCheck.find(element->tag()); @@ -2288,39 +2329,41 @@ std::unique_ptr ManifestExtractor::Element::Inflate( const std::unordered_map()>> kTagCheck = { - {"action", &CreateType}, - {"activity", &CreateType}, - {"application", &CreateType}, - {"category", &CreateType}, - {"compatible-screens", &CreateType}, - {"feature-group", &CreateType}, - {"input-type", &CreateType}, - {"intent-filter",&CreateType}, - {"manifest", &CreateType}, - {"meta-data", &CreateType}, - {"original-package", &CreateType}, - {"overlay", &CreateType}, - {"package-verifier", &CreateType}, - {"permission", &CreateType}, - {"provider", &CreateType}, - {"receiver", &CreateType}, - {"screen", &CreateType}, - {"service", &CreateType}, - {"supports-gl-texture", &CreateType}, - {"supports-input", &CreateType}, - {"supports-screens", &CreateType}, - {"uses-configuration", &CreateType}, - {"uses-feature", &CreateType}, - {"uses-permission", &CreateType}, - {"uses-permission-sdk-23", &CreateType}, - {"uses-library", &CreateType}, - {"static-library", &CreateType}, - {"uses-static-library", &CreateType}, - {"uses-package", &CreateType}, - {"additional-certificate", &CreateType}, - {"uses-sdk", &CreateType}, - {"uses-native-library", &CreateType}, - }; + {"action", &CreateType}, + {"activity", &CreateType}, + {"additional-certificate", &CreateType}, + {"application", &CreateType}, + {"category", &CreateType}, + {"compatible-screens", &CreateType}, + {"feature-group", &CreateType}, + {"input-type", &CreateType}, + {"intent-filter", &CreateType}, + {"manifest", &CreateType}, + {"meta-data", &CreateType}, + {"original-package", &CreateType}, + {"overlay", &CreateType}, + {"package-verifier", &CreateType}, + {"permission", &CreateType}, + {"provider", &CreateType}, + {"receiver", &CreateType}, + {"required-feature", &CreateType}, + {"required-not-feature", &CreateType}, + {"screen", &CreateType}, + {"service", &CreateType}, + {"static-library", &CreateType}, + {"supports-gl-texture", &CreateType}, + {"supports-input", &CreateType}, + {"supports-screens", &CreateType}, + {"uses-configuration", &CreateType}, + {"uses-feature", &CreateType}, + {"uses-library", &CreateType}, + {"uses-native-library", &CreateType}, + {"uses-package", &CreateType}, + {"uses-permission", &CreateType}, + {"uses-permission-sdk-23", &CreateType}, + {"uses-sdk", &CreateType}, + {"uses-static-library", &CreateType}, + }; // Attempt to map the xml tag to a element inflater std::unique_ptr element; diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index dac21d7e92005..3d8c25ebcbddc 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -393,6 +393,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["protected-broadcast"]; manifest_action["adopt-permissions"]; manifest_action["uses-permission"]; + manifest_action["uses-permission"]["required-feature"].Action(RequiredNameIsNotEmpty); + manifest_action["uses-permission"]["required-not-feature"].Action(RequiredNameIsNotEmpty); manifest_action["uses-permission-sdk-23"]; manifest_action["permission"]; manifest_action["permission"]["meta-data"] = meta_data_action; From 48e47d1a2caff3c45ad8f0ec1559072052d7282d Mon Sep 17 00:00:00 2001 From: Sergey Nikolaienkov Date: Fri, 25 Sep 2020 10:33:14 +0000 Subject: [PATCH 2/2] Teach PackageManager to handle 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 --- .../pm/parsing/ParsingPackageUtils.java | 110 +++++++++++++++--- core/res/res/values/attrs_manifest.xml | 16 +++ 2 files changed, 110 insertions(+), 16 deletions(-) diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 9197020e1fce7..b936c6323a80b 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -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 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 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("", pkg, parser, input); + break; + } + + if (result.isError()) { + return input.error(result); + } + } // Can only succeed from here on out ParseResult 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 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 tag.") + : input.success(featureName); + } finally { + sa.recycle(); + } + } + + private ParseResult 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 tag.") + : input.success(featureName); + } finally { + sa.recycle(); + } + } + private static ParseResult parseUsesConfiguration(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) { ConfigurationInfo cPref = new ConfigurationInfo(); diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 96ebc127e9baa..25c64a9f87811 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2016,6 +2016,22 @@ + + + + + + + + + +