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

Commit 48e47d1a authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov
Browse files

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
parent 65f9099a
Loading
Loading
Loading
Loading
+93 −15
Original line number Diff line number Diff line
@@ -1080,14 +1080,57 @@ public class ParsingPackageUtils {
                }
            }

            final String requiredFeature = sa.getNonConfigurationString(
                    R.styleable.AndroidManifestUsesPermission_requiredFeature, 0);
            final ArraySet<String> requiredFeatures = new ArraySet<>();
            String feature = sa.getNonConfigurationString(
                    com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature,
                    0);
            if (feature != null) {
                requiredFeatures.add(feature);
            }

            final String requiredNotfeature = sa.getNonConfigurationString(
                    R.styleable.AndroidManifestUsesPermission_requiredNotFeature,
            final ArraySet<String> requiredNotFeatures = new ArraySet<>();
            feature = sa.getNonConfigurationString(
                    com.android.internal.R.styleable
                            .AndroidManifestUsesPermission_requiredNotFeature,
                    0);
            if (feature != null) {
                requiredNotFeatures.add(feature);
            }

            XmlUtils.skipCurrentTag(parser);
            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,18 +1143,23 @@ 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)) {
            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)) {
                // 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)) {
                pkg.addRequestedPermission(name.intern());
@@ -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();
+16 −0
Original line number Diff line number Diff line
@@ -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