Allow apps to define featureIds in the manifest

Test: CtsAppOpsTestCases (with newly added test for featureIds declared
      in manifest)
Bug: 144997947
Change-Id: I5563ae6861318e4cc1d196e3f6aea378a4dcf748
diff --git a/api/current.txt b/api/current.txt
index c734db5..f6aa0a3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -610,6 +610,7 @@
     field public static final int fastScrollTextColor = 16843609; // 0x1010359
     field public static final int fastScrollThumbDrawable = 16843574; // 0x1010336
     field public static final int fastScrollTrackDrawable = 16843577; // 0x1010339
+    field public static final int featureId = 16844301; // 0x101060d
     field public static final int fillAfter = 16843197; // 0x10101bd
     field public static final int fillAlpha = 16843980; // 0x10104cc
     field public static final int fillBefore = 16843196; // 0x10101bc
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 47edf2e..32803ab 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -202,6 +202,7 @@
     public static final String TAG_OVERLAY = "overlay";
     public static final String TAG_PACKAGE = "package";
     public static final String TAG_PACKAGE_VERIFIER = "package-verifier";
+    public static final String TAG_FEATURE = "feature";
     public static final String TAG_PERMISSION = "permission";
     public static final String TAG_PERMISSION_GROUP = "permission-group";
     public static final String TAG_PERMISSION_TREE = "permission-tree";
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index 0562dff..990c835 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -27,6 +27,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
+import android.content.pm.parsing.ComponentParseUtils.ParsedFeature;
 import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup;
@@ -245,6 +246,9 @@
     List<ParsedInstrumentation> getInstrumentations();
 
     @Nullable
+    List<ParsedFeature> getFeatures();
+
+    @Nullable
     List<ParsedPermissionGroup> getPermissionGroups();
 
     @Nullable
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index 3f22967..a001ada 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -793,6 +793,10 @@
                     parseResult = parseKeySets(parseInput, parsingPackage, res, parser);
                     success = parseResult.isSuccess();
                     break;
+                case PackageParser.TAG_FEATURE:
+                    parseResult = parseFeature(parseInput, parsingPackage, res, parser);
+                    success = parseResult.isSuccess();
+                    break;
                 case PackageParser.TAG_PERMISSION_GROUP:
                     parseResult = parsePermissionGroup(parseInput, parsingPackage, res,
                             parser);
@@ -880,6 +884,13 @@
             );
         }
 
+        if (!ComponentParseUtils.ParsedFeature.isCombinationValid(parsingPackage.getFeatures())) {
+            return parseInput.error(
+                    INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                    "Combination <feature> tags are not valid"
+            );
+        }
+
         convertNewPermissions(parsingPackage);
 
         convertSplitPermissions(parsingPackage);
@@ -1260,6 +1271,31 @@
         return parseInput.success(parsingPackage);
     }
 
+    private static ParseResult parseFeature(
+            ParseInput parseInput,
+            ParsingPackage parsingPackage,
+            Resources res,
+            XmlResourceParser parser
+    ) throws IOException, XmlPullParserException {
+        // TODO(b/135203078): Remove, replace with ParseResult
+        String[] outError = new String[1];
+
+        ComponentParseUtils.ParsedFeature parsedFeature =
+                ComponentParseUtils.parseFeature(res, parser, outError);
+
+        if (parsedFeature == null || outError[0] != null) {
+            return parseInput.error(
+                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                    outError[0]
+            );
+        }
+
+        parsingPackage.addFeature(parsedFeature);
+
+        return parseInput.success(parsingPackage);
+    }
+
+
     private static ParseResult parsePermissionGroup(
             ParseInput parseInput,
             ParsingPackage parsingPackage,
diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java
index fc210b2..88e98da 100644
--- a/core/java/android/content/pm/parsing/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java
@@ -24,6 +24,9 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
@@ -47,6 +50,7 @@
 import android.os.Parcelable;
 import android.os.PatternMatcher;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
@@ -54,6 +58,7 @@
 import android.view.Gravity;
 
 import com.android.internal.R;
+import com.android.internal.util.DataClass;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -62,6 +67,7 @@
 import java.io.IOException;
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
@@ -931,6 +937,176 @@
         };
     }
 
+    /**
+     * A {@link android.R.styleable#AndroidManifestFeature &lt;feature&gt;} tag parsed from the
+     * manifest.
+     */
+    // @DataClass verifier is broken, hence comment out for now
+    public static class ParsedFeature implements Parcelable {
+        /** Id of the feature */
+        public final @NonNull String id;
+
+        /** User visible label fo the feature */
+        public final @StringRes int label;
+
+        /** Ids of previously declared features this feature inherits from */
+        public final @NonNull List<String> inheritFrom;
+
+        /**
+         * @return Is this set of features a valid combination for a single package?
+         */
+        public static boolean isCombinationValid(@Nullable List<ParsedFeature> features) {
+            if (features == null) {
+                return true;
+            }
+
+            ArraySet<String> featureIds = new ArraySet<>(features.size());
+            ArraySet<String> inheritFromFeatureIds = new ArraySet<>();
+
+            int numFeatures = features.size();
+            for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+                boolean wasAdded = featureIds.add(features.get(featureNum).id);
+                if (!wasAdded) {
+                    // feature id is not unique
+                    return false;
+                }
+            }
+
+            for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+                ParsedFeature feature = features.get(featureNum);
+
+                int numInheritFrom = feature.inheritFrom.size();
+                for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; inheritFromNum++) {
+                    String inheritFrom = feature.inheritFrom.get(inheritFromNum);
+
+                    if (featureIds.contains(inheritFrom)) {
+                        // Cannot inherit from a feature that is still defined
+                        return false;
+                    }
+
+                    boolean wasAdded = inheritFromFeatureIds.add(inheritFrom);
+                    if (!wasAdded) {
+                        // inheritFrom is not unique
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+
+
+        // Code below generated by codegen v1.0.14.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/ComponentParseUtils.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new ParsedFeature.
+         *
+         * @param id
+         *   Id of the feature
+         * @param label
+         *   User visible label fo the feature (if defined as resource)
+         * @param inheritFrom
+         *   Ids of previously declared features this feature inherits from
+         */
+        @DataClass.Generated.Member
+        public ParsedFeature(
+                @NonNull String id,
+                @StringRes int label,
+                @NonNull List<String> inheritFrom) {
+            this.id = id;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, id);
+            this.label = label;
+            com.android.internal.util.AnnotationValidations.validate(
+                    StringRes.class, null, label);
+            this.inheritFrom = inheritFrom;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, inheritFrom);
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            dest.writeString(id);
+            dest.writeInt(label);
+            dest.writeStringList(inheritFrom);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        protected ParsedFeature(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            String _id = in.readString();
+            int _label = in.readInt();
+            List<String> _inheritFrom = new ArrayList<>();
+            in.readStringList(_inheritFrom);
+
+            this.id = _id;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, id);
+            this.label = _label;
+            com.android.internal.util.AnnotationValidations.validate(
+                    StringRes.class, null, label);
+            this.inheritFrom = _inheritFrom;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, inheritFrom);
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<ParsedFeature> CREATOR
+                = new Parcelable.Creator<ParsedFeature>() {
+            @Override
+            public ParsedFeature[] newArray(int size) {
+                return new ParsedFeature[size];
+            }
+
+            @Override
+            public ParsedFeature createFromParcel(@NonNull Parcel in) {
+                return new ParsedFeature(in);
+            }
+        };
+
+        /*@DataClass.Generated(
+                time = 1576783172965L,
+                codegenVersion = "1.0.14",
+                sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ComponentParseUtils.java",
+                inputSignatures = "public final @android.annotation.NonNull java.lang.String id\npublic final @android.annotation.StringRes int label\npublic final @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\npublic static  boolean isCombinationValid(java.util.List<android.content.pm.parsing.ParsedFeature>)\nclass ParsedFeature extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass")
+         */
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
     public static class ParsedPermission extends ParsedComponent<ParsedIntentInfo> {
 
         public String backgroundPermission;
@@ -2566,6 +2742,80 @@
         return result;
     }
 
+    public static ParsedFeature parseFeature(
+            Resources res,
+            XmlResourceParser parser,
+            String[] outError
+    ) throws IOException, XmlPullParserException {
+        String featureId;
+        int label;
+        List<String> inheritFrom = null;
+
+        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestFeature);
+        if (sa == null) {
+            outError[0] = "<feature> could not be parsed";
+            return null;
+        }
+
+        try {
+            featureId = sa.getNonConfigurationString(R.styleable.AndroidManifestFeature_featureId,
+                    0);
+            if (featureId == null) {
+                outError[0] = "<featureId> does not specify android:featureId";
+                return null;
+            }
+
+            label = sa.getResourceId(R.styleable.AndroidManifestFeature_label, 0);
+            if (label == Resources.ID_NULL) {
+                outError[0] = "<featureId> does not specify android:label";
+                return null;
+            }
+        } finally {
+            sa.recycle();
+        }
+
+        int type;
+        final int innerDepth = parser.getDepth();
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("inherit-from")) {
+                sa = res.obtainAttributes(parser, R.styleable.AndroidManifestFeatureInheritFrom);
+                if (sa == null) {
+                    outError[0] = "<inherit-from> could not be parsed";
+                    return null;
+                }
+
+                try {
+                    String inheritFromId = sa.getNonConfigurationString(
+                            R.styleable.AndroidManifestFeatureInheritFrom_featureId,0);
+
+                    if (inheritFrom == null) {
+                        inheritFrom = new ArrayList<>();
+                    }
+                    inheritFrom.add(inheritFromId);
+                } finally {
+                    sa.recycle();
+                }
+            } else {
+                outError[0] = "Bad element under <feature>: " + tagName;
+                return null;
+            }
+        }
+
+        if (inheritFrom == null) {
+            inheritFrom = Collections.emptyList();
+        } else {
+            ((ArrayList) inheritFrom).trimToSize();
+        }
+
+        return new ParsedFeature(featureId, label, inheritFrom);
+    }
+
     public static ParsedPermission parsePermission(
             ParsingPackage parsingPackage,
             Resources res,
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index 18dee23..8677fce 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -36,6 +36,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
+import android.content.pm.parsing.ComponentParseUtils.ParsedFeature;
 import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
 import android.content.pm.parsing.ComponentParseUtils.ParsedIntentInfo;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
@@ -175,6 +176,9 @@
     private ArrayList<ComponentParseUtils.ParsedProvider> providers;
 
     @Nullable
+    private ArrayList<ComponentParseUtils.ParsedFeature> features;
+
+    @Nullable
     private ArrayList<ComponentParseUtils.ParsedPermission> permissions;
 
     @Nullable
@@ -580,6 +584,12 @@
         return permissions;
     }
 
+    @Nullable
+    @Override
+    public List<ParsedFeature> getFeatures() {
+        return features;
+    }
+
     @Override
     public String getCpuAbiOverride() {
         return cpuAbiOverride;
@@ -792,6 +802,12 @@
     }
 
     @Override
+    public PackageImpl addFeature(ParsedFeature feature) {
+        this.features = ArrayUtils.add(this.features, feature);
+        return this;
+    }
+
+    @Override
     public PackageImpl addPermission(ParsedPermission permission) {
         this.permissions = ArrayUtils.add(this.permissions, permission);
         return this;
@@ -3021,6 +3037,7 @@
         dest.writeTypedList(this.receivers);
         dest.writeTypedList(this.services);
         dest.writeTypedList(this.providers);
+        dest.writeTypedList(this.features);
         dest.writeTypedList(this.permissions);
         dest.writeTypedList(this.permissionGroups);
         dest.writeTypedList(this.instrumentations);
@@ -3173,6 +3190,7 @@
         this.receivers = in.createTypedArrayList(ParsedActivity.CREATOR);
         this.services = in.createTypedArrayList(ParsedService.CREATOR);
         this.providers = in.createTypedArrayList(ParsedProvider.CREATOR);
+        this.features = in.createTypedArrayList(ParsedFeature.CREATOR);
         this.permissions = in.createTypedArrayList(ParsedPermission.CREATOR);
         this.permissionGroups = in.createTypedArrayList(ParsedPermissionGroup.CREATOR);
         this.instrumentations = in.createTypedArrayList(ParsedInstrumentation.CREATOR);
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 47dac55..411c749 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageParser;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
+import android.content.pm.parsing.ComponentParseUtils.ParsedFeature;
 import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup;
@@ -64,6 +65,8 @@
 
     ParsingPackage addOverlayable(String overlayableName, String actorName);
 
+    ParsingPackage addFeature(ParsedFeature permission);
+
     ParsingPackage addPermission(ParsedPermission permission);
 
     ParsingPackage addPermissionGroup(ParsedPermissionGroup permissionGroup);
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 85f7d61..6435cdd 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1533,7 +1533,8 @@
          <code>com.google.app.<em>appname</em></code>
 
          <p>Inside of the manifest tag, may appear the following tags
-         in any order: {@link #AndroidManifestPermission permission},
+         in any order: {@link #AndroidManifestFeature feature},
+         {@link #AndroidManifestPermission permission},
          {@link #AndroidManifestPermissionGroup permission-group},
          {@link #AndroidManifestPermissionTree permission-tree},
          {@link #AndroidManifestUsesSdk uses-sdk},
@@ -1763,6 +1764,29 @@
              The default value is {@code false}. -->
         <attr name="crossProfile" format="boolean" />
     </declare-styleable>
+
+    <!-- The <code>feature</code> tag declares a feature. A feature is a part of an app. E.g.
+    photo sharing app might include a direct messaging component.
+
+    <p>This appears as a child tag of the root {@link #AndroidManifest manifest} tag.
+
+    <p>In case this feature inherits from another feature, this tag can contain one or multiple
+    {@link #AndroidManifestFeatureInheritFrom inherit-from} tags. -->
+    <declare-styleable name="AndroidManifestFeature" parent="AndroidManifest">
+        <!-- Required identifier for a feature. Can be passed to
+        {@link android.content.Context#createFeatureContext} to create a context for this feature
+        -->
+        <attr name="featureId" format="string" />
+        <!-- Required user visible label for a feature. -->
+        <attr name="label" format="string" />
+    </declare-styleable>
+
+    <!-- Declares previously declared features this feature inherits from. -->
+    <declare-styleable name="AndroidManifestFeatureInheritFrom" parent="AndroidManifestFeature">
+        <!-- Identifier of the feature this feature inherits from -->
+        <attr name="featureId" format="string" />
+    </declare-styleable>
+
     <!-- The <code>permission</code> tag declares a security permission that can be
          used to control access from other packages to specific components or
          features in your package (or other packages).  See the
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 0b63518..bea3920 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3004,6 +3004,8 @@
       <public name="resourcesMap" />
       <public name="animatedImageDrawable"/>
       <public name="htmlDescription"/>
+      <public name="preferMinimalPostProcessing"/>
+      <public name="featureId" />
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b5">
@@ -3049,10 +3051,6 @@
       <public name="accessibilitySystemActionLockScreen" />
       <public name="accessibilitySystemActionTakeScreenshot" />
     </public-group>
-
-    <public-group type="attr" first-id="0x0101060c">
-      <public name="preferMinimalPostProcessing"/>
-    </public-group>
   <!-- ===============================================================
        DO NOT ADD UN-GROUPED ITEMS HERE
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7ef84bd..367f0ef 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,7 +18,6 @@
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
 import static android.app.AppOpsManager.NoteOpEvent;
-import static android.app.AppOpsManager.OpEventProxyInfo;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_COARSE_LOCATION;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -26,6 +25,7 @@
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OpEventProxyInfo;
 import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
 import static android.app.AppOpsManager.UID_STATE_CACHED;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
@@ -40,6 +40,8 @@
 import static android.app.AppOpsManager.modeToName;
 import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 
 import android.Manifest;
@@ -69,6 +71,8 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.parsing.AndroidPackage;
+import android.content.pm.parsing.ComponentParseUtils.ParsedFeature;
 import android.database.ContentObserver;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
 import android.net.Uri;
@@ -122,6 +126,7 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+import com.android.server.SystemServerInitThreadPool;
 
 import libcore.util.EmptyArray;
 
@@ -468,6 +473,9 @@
         final UidState uidState;
         final boolean isPrivileged;
 
+        /** Lazily populated cache of featureIds of this package */
+        final @NonNull ArraySet<String> knownFeatureIds = new ArraySet<>();
+
         Ops(String _packageName, UidState _uidState, boolean _isPrivileged) {
             packageName = _packageName;
             uidState = _uidState;
@@ -527,7 +535,7 @@
          * <p>Key is {@link AppOpsManager#makeKey}
          */
         @GuardedBy("AppOpsService.this")
-        private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mAccessEvents;
+        private @Nullable LongSparseArray<NoteOpEvent> mAccessEvents;
 
         /**
          * Last rejected accesses for each uidState/opFlag combination
@@ -535,7 +543,7 @@
          * <p>Key is {@link AppOpsManager#makeKey}
          */
         @GuardedBy("AppOpsService.this")
-        private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mRejectEvents;
+        private @Nullable LongSparseArray<NoteOpEvent> mRejectEvents;
 
         /**
          * Currently in progress startOp events
@@ -748,6 +756,56 @@
             }
         }
 
+        /**
+         * Combine {@code a} and {@code b} and return the result. The result might be {@code a}
+         * or {@code b}. If there is an event for the same key in both the later event is retained.
+         */
+        private @Nullable LongSparseArray<NoteOpEvent> add(@Nullable LongSparseArray<NoteOpEvent> a,
+                @Nullable LongSparseArray<NoteOpEvent> b) {
+            if (a == null) {
+                return b;
+            }
+
+            if (b == null) {
+                return a;
+            }
+
+            int numEventsToAdd = b.size();
+            for (int i = 0; i < numEventsToAdd; i++) {
+                long keyOfEventToAdd = b.keyAt(i);
+                NoteOpEvent bEvent = b.valueAt(i);
+                NoteOpEvent aEvent = a.get(keyOfEventToAdd);
+
+                if (aEvent == null || bEvent.noteTime > aEvent.noteTime) {
+                    a.put(keyOfEventToAdd, bEvent);
+                }
+            }
+
+            return a;
+        }
+
+        /**
+         * Add all data from the {@code featureToAdd} to this op.
+         *
+         * <p>If there is an event for the same key in both the later event is retained.
+         * <p>{@code opToAdd} should not be used after this method is called.
+         *
+         * @param opToAdd The op to add
+         */
+        public void add(@NonNull FeatureOp opToAdd) {
+            if (opToAdd.mInProgressEvents != null) {
+                Slog.w(TAG, "Ignoring " + opToAdd.mInProgressEvents.size() + " running app-ops");
+
+                int numInProgressEvents = opToAdd.mInProgressEvents.size();
+                for (int i = 0; i < numInProgressEvents; i++) {
+                    opToAdd.mInProgressEvents.valueAt(i).finish();
+                }
+            }
+
+            mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents);
+            mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents);
+        }
+
         public boolean isRunning() {
             return mInProgressEvents != null;
         }
@@ -1038,20 +1096,110 @@
         LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
     }
 
+    /** Handler for work when packages are removed or updated */
+    private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+
+            if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+                synchronized (AppOpsService.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+
+                    Ops removedOps = uidState.pkgOps.remove(pkgName);
+                    if (removedOps != null) {
+                        scheduleFastWriteLocked();
+                    }
+                }
+            } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+                AndroidPackage pkg = LocalServices.getService(
+                        PackageManagerInternal.class).getPackage(pkgName);
+                if (pkg == null) {
+                    return;
+                }
+
+                ArrayMap<String, String> dstFeatureIds = new ArrayMap<>();
+                ArraySet<String> featureIds = new ArraySet<>();
+                if (pkg.getFeatures() != null) {
+                    int numFeatures = pkg.getFeatures().size();
+                    for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+                        ParsedFeature feature = pkg.getFeatures().get(featureNum);
+                        featureIds.add(feature.id);
+
+                        int numInheritFrom = feature.inheritFrom.size();
+                        for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
+                                inheritFromNum++) {
+                            dstFeatureIds.put(feature.inheritFrom.get(inheritFromNum),
+                                    feature.id);
+                        }
+                    }
+                }
+
+                synchronized (AppOpsService.this) {
+                    UidState uidState = mUidStates.get(uid);
+                    if (uidState == null || uidState.pkgOps == null) {
+                        return;
+                    }
+
+                    Ops ops = uidState.pkgOps.get(pkgName);
+                    if (ops == null) {
+                        return;
+                    }
+
+                    ops.knownFeatureIds.clear();
+                    int numOps = ops.size();
+                    for (int opNum = 0; opNum < numOps; opNum++) {
+                        Op op = ops.valueAt(opNum);
+
+                        int numFeatures = op.mFeatures.size();
+                        for (int featureNum = numFeatures - 1; featureNum >= 0; featureNum--) {
+                            String featureId = op.mFeatures.keyAt(featureNum);
+
+                            if (featureIds.contains(featureId)) {
+                                // feature still exist after upgrade
+                                continue;
+                            }
+
+                            String newFeatureId = dstFeatureIds.get(featureId);
+
+                            FeatureOp newFeatureOp = op.getOrCreateFeature(op, newFeatureId);
+                            newFeatureOp.add(op.mFeatures.valueAt(featureNum));
+                            op.mFeatures.removeAt(featureNum);
+
+                            scheduleFastWriteLocked();
+                        }
+                    }
+                }
+            }
+        }
+    };
+
     public void systemReady() {
         mConstants.startMonitoring(mContext.getContentResolver());
         mHistoricalRegistry.systemReady(mContext.getContentResolver());
 
-        synchronized (this) {
-            boolean changed = false;
-            for (int i = mUidStates.size() - 1; i >= 0; i--) {
-                UidState uidState = mUidStates.valueAt(i);
+        IntentFilter packageUpdateFilter = new IntentFilter();
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageUpdateFilter.addDataScheme("package");
 
-                String[] packageNames = getPackagesForUid(uidState.uid);
-                if (ArrayUtils.isEmpty(packageNames)) {
+        mContext.registerReceiver(mOnPackageUpdatedReceiver, packageUpdateFilter);
+
+        synchronized (this) {
+            for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
+                int uid = mUidStates.keyAt(uidNum);
+                UidState uidState = mUidStates.valueAt(uidNum);
+
+                String[] pkgsInUid = getPackagesForUid(uidState.uid);
+                if (ArrayUtils.isEmpty(pkgsInUid)) {
                     uidState.clear();
-                    mUidStates.removeAt(i);
-                    changed = true;
+                    mUidStates.removeAt(uidNum);
+                    scheduleFastWriteLocked();
                     continue;
                 }
 
@@ -1060,31 +1208,24 @@
                     continue;
                 }
 
-                Iterator<Ops> it = pkgs.values().iterator();
-                while (it.hasNext()) {
-                    Ops ops = it.next();
-                    int curUid = -1;
-                    try {
-                        curUid = AppGlobals.getPackageManager().getPackageUid(ops.packageName,
-                                PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                                UserHandle.getUserId(ops.uidState.uid));
-                    } catch (RemoteException ignored) {
-                    }
-                    if (curUid != ops.uidState.uid) {
-                        Slog.i(TAG, "Pruning old package " + ops.packageName
-                                + "/" + ops.uidState + ": new uid=" + curUid);
-                        it.remove();
-                        changed = true;
-                    }
-                }
+                int numPkgs = pkgs.size();
+                for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                    String pkg = pkgs.keyAt(pkgNum);
 
-                if (uidState.isDefault()) {
-                    mUidStates.removeAt(i);
+                    String action;
+                    if (!ArrayUtils.contains(pkgsInUid, pkg)) {
+                        action = Intent.ACTION_PACKAGE_REMOVED;
+                    } else {
+                        action = Intent.ACTION_PACKAGE_REPLACED;
+                    }
+
+                    SystemServerInitThreadPool.submit(
+                            () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
+                                    .setData(Uri.fromParts("package", pkg, null))
+                                    .putExtra(Intent.EXTRA_UID, uid)),
+                            "Update app-ops uidState in case package " + pkg + " changed");
                 }
             }
-            if (changed) {
-                scheduleFastWriteLocked();
-            }
         }
 
         final IntentFilter packageSuspendFilter = new IntentFilter();
@@ -1388,7 +1529,7 @@
             return Collections.emptyList();
         }
         synchronized (this) {
-            Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* isPrivileged */,
+            Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, null, false /* isPrivileged */,
                     false /* edit */);
             if (pkgOps == null) {
                 return null;
@@ -1488,7 +1629,7 @@
         op.removeFeaturesWithNoTime();
 
         if (op.mFeatures.size() == 0) {
-            Ops ops = getOpsRawLocked(uid, packageName, false /* isPrivileged */,
+            Ops ops = getOpsRawLocked(uid, packageName, null, false /* isPrivileged */,
                     false /* edit */);
             if (ops != null) {
                 ops.remove(op.op);
@@ -1752,7 +1893,7 @@
 
         boolean isPrivileged;
         try {
-            isPrivileged = verifyAndGetIsPrivileged(uid, packageName);
+            isPrivileged = verifyAndGetIsPrivileged(uid, packageName, null);
         } catch (SecurityException e) {
             Slog.e(TAG, "Cannot setMode", e);
             return;
@@ -1767,7 +1908,7 @@
 
         synchronized (this) {
             UidState uidState = getUidStateLocked(uid, false);
-            Op op = getOpLocked(code, uid, packageName, isPrivileged, true);
+            Op op = getOpLocked(code, uid, packageName, null, isPrivileged, true);
             if (op != null) {
                 if (op.mode != mode) {
                     op.mode = mode;
@@ -2137,7 +2278,7 @@
         boolean isPrivileged;
 
         try {
-            isPrivileged = verifyAndGetIsPrivileged(uid, packageName);
+            isPrivileged = verifyAndGetIsPrivileged(uid, packageName, null);
         } catch (SecurityException e) {
             Slog.e(TAG, "checkOperation", e);
             return AppOpsManager.opToDefaultMode(code);
@@ -2147,7 +2288,7 @@
             return AppOpsManager.MODE_IGNORED;
         }
         synchronized (this) {
-            if (isOpRestrictedLocked(uid, code, packageName, isPrivileged)) {
+            if (isOpRestrictedLocked(uid, code, packageName, null, isPrivileged)) {
                 return AppOpsManager.MODE_IGNORED;
             }
             code = AppOpsManager.opToSwitch(code);
@@ -2157,7 +2298,7 @@
                 final int rawMode = uidState.opModes.get(code);
                 return raw ? rawMode : uidState.evalMode(code, rawMode);
             }
-            Op op = getOpLocked(code, uid, packageName, false, false);
+            Op op = getOpLocked(code, uid, packageName, null, false, false);
             if (op == null) {
                 return AppOpsManager.opToDefaultMode(code);
             }
@@ -2220,7 +2361,7 @@
     public int checkPackage(int uid, String packageName) {
         Objects.requireNonNull(packageName);
         try {
-            verifyAndGetIsPrivileged(uid, packageName);
+            verifyAndGetIsPrivileged(uid, packageName, null);
 
             return AppOpsManager.MODE_ALLOWED;
         } catch (SecurityException ignored) {
@@ -2290,18 +2431,17 @@
     private int noteOperationUnchecked(int code, int uid, String packageName, String featureId,
             int proxyUid, String proxyPackageName, @Nullable String proxyFeatureId,
             @OpFlags int flags) {
-        // TODO moltmann: Verify that feature is declared in package
-
         boolean isPrivileged;
         try {
-            isPrivileged = verifyAndGetIsPrivileged(uid, packageName);
+            isPrivileged = verifyAndGetIsPrivileged(uid, packageName, featureId);
         } catch (SecurityException e) {
             Slog.e(TAG, "noteOperation", e);
             return AppOpsManager.MODE_ERRORED;
         }
 
         synchronized (this) {
-            final Ops ops = getOpsRawLocked(uid, packageName, isPrivileged, true /* edit */);
+            final Ops ops = getOpsRawLocked(uid, packageName, featureId, isPrivileged,
+                    true /* edit */);
             if (ops == null) {
                 scheduleOpNotedIfNeededLocked(code, uid, packageName,
                         AppOpsManager.MODE_IGNORED);
@@ -2309,9 +2449,9 @@
                         + " package " + packageName);
                 return AppOpsManager.MODE_ERRORED;
             }
-            final Op op = getOpLocked(ops, code, true);
+            final Op op = getOpLocked(ops, code, uid, true);
             final FeatureOp featureOp = op.getOrCreateFeature(op, featureId);
-            if (isOpRestrictedLocked(uid, code, packageName, isPrivileged)) {
+            if (isOpRestrictedLocked(uid, code, packageName, featureId, isPrivileged)) {
                 scheduleOpNotedIfNeededLocked(code, uid, packageName,
                         AppOpsManager.MODE_IGNORED);
                 return AppOpsManager.MODE_IGNORED;
@@ -2339,7 +2479,8 @@
                     return uidMode;
                 }
             } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
                 final int mode = switchOp.evalMode();
                 if (mode != AppOpsManager.MODE_ALLOWED) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
@@ -2460,7 +2601,7 @@
     public void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
             String featureId, String message) {
         Objects.requireNonNull(message);
-        verifyAndGetIsPrivileged(uid, packageName);
+        verifyAndGetIsPrivileged(uid, packageName, featureId);
 
         verifyIncomingUid(uid);
         verifyIncomingOp(opCode);
@@ -2469,7 +2610,7 @@
         long now = System.currentTimeMillis();
 
         if (callingPackageName != null) {
-            verifyAndGetIsPrivileged(callingUid, callingPackageName);
+            verifyAndGetIsPrivileged(callingUid, callingPackageName, featureId);
         }
 
         long token = Binder.clearCallingIdentity();
@@ -2534,7 +2675,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        verifyAndGetIsPrivileged(uid, packageName);
+        verifyAndGetIsPrivileged(uid, packageName, null);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2564,7 +2705,7 @@
         int uid = Binder.getCallingUid();
         Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
 
-        verifyAndGetIsPrivileged(uid, packageName);
+        verifyAndGetIsPrivileged(uid, packageName, null);
 
         synchronized (this) {
             RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2583,7 +2724,7 @@
 
         int uid = Binder.getCallingUid();
 
-        verifyAndGetIsPrivileged(uid, packageName);
+        verifyAndGetIsPrivileged(uid, packageName, null);
 
         synchronized (this) {
             return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
@@ -2602,22 +2743,22 @@
 
         boolean isPrivileged;
         try {
-            isPrivileged = verifyAndGetIsPrivileged(uid, packageName);
+            isPrivileged = verifyAndGetIsPrivileged(uid, packageName, featureId);
         } catch (SecurityException e) {
             Slog.e(TAG, "startOperation", e);
             return AppOpsManager.MODE_ERRORED;
         }
 
         synchronized (this) {
-            final Ops ops = getOpsRawLocked(uid, resolvedPackageName, isPrivileged,
+            final Ops ops = getOpsRawLocked(uid, resolvedPackageName, featureId, isPrivileged,
                     true /* edit */);
             if (ops == null) {
                 if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
                         + " package " + resolvedPackageName);
                 return AppOpsManager.MODE_ERRORED;
             }
-            final Op op = getOpLocked(ops, code, true);
-            if (isOpRestrictedLocked(uid, code, resolvedPackageName, isPrivileged)) {
+            final Op op = getOpLocked(ops, code, uid, true);
+            if (isOpRestrictedLocked(uid, code, resolvedPackageName, featureId, isPrivileged)) {
                 return AppOpsManager.MODE_IGNORED;
             }
             final FeatureOp featureOp = op.getOrCreateFeature(op, featureId);
@@ -2639,7 +2780,8 @@
                     return uidMode;
                 }
             } else {
-                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
+                final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+                        : op;
                 final int mode = switchOp.evalMode();
                 if (mode != AppOpsManager.MODE_ALLOWED
                         && (!startIfModeDefault || mode != AppOpsManager.MODE_DEFAULT)) {
@@ -2677,14 +2819,14 @@
 
         boolean isPrivileged;
         try {
-            isPrivileged = verifyAndGetIsPrivileged(uid, packageName);
+            isPrivileged = verifyAndGetIsPrivileged(uid, packageName, featureId);
         } catch (SecurityException e) {
             Slog.e(TAG, "Cannot finishOperation", e);
             return;
         }
 
         synchronized (this) {
-            Op op = getOpLocked(code, uid, resolvedPackageName, isPrivileged, true);
+            Op op = getOpLocked(code, uid, resolvedPackageName, featureId, isPrivileged, true);
             if (op == null) {
                 return;
             }
@@ -2915,22 +3057,24 @@
      *
      * @param uid The uid the package belongs to
      * @param packageName The package the might belong to the uid
+     * @param featureId The feature in the package or {@code null} if no need to verify
      *
      * @return {@code true} iff the package is privileged
      */
-    private boolean verifyAndGetIsPrivileged(int uid, String packageName) {
+    private boolean verifyAndGetIsPrivileged(int uid, String packageName,
+            @Nullable String featureId) {
         if (uid == Process.ROOT_UID) {
             // For backwards compatibility, don't check package name for root UID.
             return false;
         }
 
-        // Do not check if uid/packageName is already known
+        // Do not check if uid/packageName/featureId is already known
         synchronized (this) {
             UidState uidState = mUidStates.get(uid);
             if (uidState != null && uidState.pkgOps != null) {
                 Ops ops = uidState.pkgOps.get(packageName);
 
-                if (ops != null) {
+                if (ops != null && (featureId == null || ops.knownFeatureIds.contains(featureId))) {
                     return ops.isPrivileged;
                 }
             }
@@ -2940,19 +3084,31 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             int pkgUid;
+            AndroidPackage pkg = LocalServices.getService(PackageManagerInternal.class).getPackage(
+                    packageName);
+            boolean isFeatureIdValid = false;
 
-            ApplicationInfo appInfo = LocalServices.getService(PackageManagerInternal.class)
-                    .getApplicationInfo(packageName, PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                    | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
-                                    | PackageManager.MATCH_UNINSTALLED_PACKAGES
-                                    | PackageManager.MATCH_INSTANT,
-                            Process.SYSTEM_UID, UserHandle.getUserId(uid));
-            if (appInfo != null) {
-                pkgUid = appInfo.uid;
-                isPrivileged = (appInfo.privateFlags
-                        & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
+            if (pkg != null) {
+                if (featureId == null) {
+                    isFeatureIdValid = true;
+                } else {
+                    if (pkg.getFeatures() != null) {
+                        int numFeatures = pkg.getFeatures().size();
+                        for (int i = 0; i < numFeatures; i++) {
+                            if (pkg.getFeatures().get(i).id.equals(featureId)) {
+                                isFeatureIdValid = true;
+                            }
+                        }
+                    }
+                }
+
+                pkgUid = UserHandle.getUid(
+                        UserHandle.getUserId(uid), UserHandle.getAppId(pkg.getUid()));
+                isPrivileged = pkg.isPrivileged();
             } else {
+                // Allow any feature id for resolvable uids
+                isFeatureIdValid = true;
+
                 pkgUid = resolveUid(packageName);
                 if (pkgUid >= 0) {
                     isPrivileged = false;
@@ -2962,6 +3118,12 @@
                 throw new SecurityException("Specified package " + packageName + " under uid " + uid
                         + " but it is really " + pkgUid);
             }
+
+            if (!isFeatureIdValid) {
+                // TODO moltmann: Switch from logging to enforcement
+                Slog.e(TAG, "featureId " + featureId + " not declared in manifest of "
+                        + packageName);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -2974,12 +3136,14 @@
      *
      * @param uid The uid the package belongs to
      * @param packageName The name of the package
+     * @param featureId The feature in the package
      * @param isPrivileged If the package is privilidged (ignored if {@code edit} is false)
      * @param edit If an ops does not exist, create the ops?
 
      * @return
      */
-    private Ops getOpsRawLocked(int uid, String packageName, boolean isPrivileged, boolean edit) {
+    private Ops getOpsRawLocked(int uid, String packageName, @Nullable String featureId,
+            boolean isPrivileged, boolean edit) {
         UidState uidState = getUidStateLocked(uid, edit);
         if (uidState == null) {
             return null;
@@ -3000,6 +3164,9 @@
             ops = new Ops(packageName, uidState, isPrivileged);
             uidState.pkgOps.put(packageName, ops);
         }
+        if (edit && featureId != null) {
+            ops.knownFeatureIds.add(featureId);
+        }
         return ops;
     }
 
@@ -3010,13 +3177,14 @@
      *
      * @param uid The uid the of the package
      * @param packageName The package name for which to get the state for
+     * @param featureId The feature in the package
      * @param edit Iff {@code true} create the {@link Ops} object if not yet created
      * @param isPrivileged Whether the package is privileged or not
      *
      * @return The {@link Ops state} of all ops for the package
      */
     private @Nullable Ops getOpsRawNoVerifyLocked(int uid, @NonNull String packageName,
-            boolean edit, boolean isPrivileged) {
+            @Nullable String featureId, boolean edit, boolean isPrivileged) {
         UidState uidState = getUidStateLocked(uid, edit);
         if (uidState == null) {
             return null;
@@ -3037,6 +3205,11 @@
             ops = new Ops(packageName, uidState, isPrivileged);
             uidState.pkgOps.put(packageName, ops);
         }
+
+        if (edit && featureId != null) {
+            ops.knownFeatureIds.add(featureId);
+        }
+
         return ops;
     }
 
@@ -3062,6 +3235,7 @@
      * @param code The code of the op
      * @param uid The uid the of the package
      * @param packageName The package name for which to get the state for
+     * @param featureId The feature in the package
      * @param isPrivileged Whether the package is privileged or not (only used if {@code edit
      *                     == true})
      * @param edit Iff {@code true} create the {@link Op} object if not yet created
@@ -3069,21 +3243,21 @@
      * @return The {@link Op state} of the op
      */
     private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
-            boolean isPrivileged, boolean edit) {
-        Ops ops = getOpsRawNoVerifyLocked(uid, packageName, edit, isPrivileged);
+            @Nullable String featureId, boolean isPrivileged, boolean edit) {
+        Ops ops = getOpsRawNoVerifyLocked(uid, packageName, featureId, edit, isPrivileged);
         if (ops == null) {
             return null;
         }
-        return getOpLocked(ops, code, edit);
+        return getOpLocked(ops, code, uid, edit);
     }
 
-    private Op getOpLocked(Ops ops, int code, boolean edit) {
+    private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
         Op op = ops.get(code);
         if (op == null) {
             if (!edit) {
                 return null;
             }
-            op = new Op(ops.uidState, ops.packageName, code, ops.uidState.uid);
+            op = new Op(ops.uidState, ops.packageName, code, uid);
             ops.put(code, op);
         }
         if (edit) {
@@ -3101,7 +3275,7 @@
     }
 
     private boolean isOpRestrictedLocked(int uid, int code, String packageName,
-            boolean isPrivileged) {
+            @Nullable String featureId, boolean isPrivileged) {
         int userHandle = UserHandle.getUserId(uid);
         final int restrictionSetCount = mOpUserRestrictions.size();
 
@@ -3113,7 +3287,7 @@
                 if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
                     // If we are the system, bypass user restrictions for certain codes
                     synchronized (this) {
-                        Ops ops = getOpsRawLocked(uid, packageName, isPrivileged,
+                        Ops ops = getOpsRawLocked(uid, packageName, featureId, isPrivileged,
                                 true /* edit */);
                         if ((ops != null) && ops.isPrivileged) {
                             return false;
@@ -3490,7 +3664,7 @@
                         out.startTag(null, "uid");
                         out.attribute(null, "n", Integer.toString(pkg.getUid()));
                         synchronized (this) {
-                            Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(),
+                            Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(), null,
                                     false /* isPrivileged */, false /* edit */);
                             // Should always be present as the list of PackageOps is generated
                             // from Ops.
@@ -4807,7 +4981,7 @@
         }
         // TODO moltmann: Allow to check for feature op activeness
         synchronized (AppOpsService.this) {
-            Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false, false);
+            Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, null, false, false);
             if (pkgOps == null) {
                 return false;
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b793d6d..04e7372 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11319,6 +11319,12 @@
                             "Static shared libs cannot declare permission groups");
                 }
 
+                // Static shared libs cannot declare features
+                if (pkg.getFeatures() != null && !pkg.getFeatures().isEmpty()) {
+                    throw new PackageManagerException(
+                            "Static shared libs cannot declare features");
+                }
+
                 // Static shared libs cannot declare permissions
                 if (pkg.getPermissions() != null && !pkg.getPermissions().isEmpty()) {
                     throw new PackageManagerException(
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 529339e..f553a48 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -48,6 +48,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.parsing.AndroidPackage;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
@@ -68,6 +69,7 @@
 import org.mockito.quality.Strictness;
 
 import java.io.File;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -138,11 +140,15 @@
                 .spyStatic(Settings.Global.class)
                 .startMocking();
 
-        // Mock LocalServices.getService(PackageManagerInternal.class).getApplicationInfo dependency
+        // Mock LocalServices.getService(PackageManagerInternal.class).getPackage dependency
         // needed by AppOpsService
         PackageManagerInternal mockPackageManagerInternal = mock(PackageManagerInternal.class);
-        when(mockPackageManagerInternal.getApplicationInfo(eq(sMyPackageName), anyInt(), anyInt(),
-                anyInt())).thenReturn(sContext.getApplicationInfo());
+        AndroidPackage mockMyPkg = mock(AndroidPackage.class);
+        when(mockMyPkg.isPrivileged()).thenReturn(false);
+        when(mockMyPkg.getUid()).thenReturn(mMyUid);
+        when(mockMyPkg.getFeatures()).thenReturn(Collections.emptyList());
+
+        when(mockPackageManagerInternal.getPackage(sMyPackageName)).thenReturn(mockMyPkg);
         doReturn(mockPackageManagerInternal).when(
                 () -> LocalServices.getService(PackageManagerInternal.class));
 
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index b725920..27960c8 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -365,6 +365,8 @@
   });
   manifest_action["instrumentation"]["meta-data"] = meta_data_action;
 
+  manifest_action["feature"];
+  manifest_action["feature"]["inherit-from"];
   manifest_action["original-package"];
   manifest_action["overlay"];
   manifest_action["protected-broadcast"];