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 <feature>} 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"];