diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/app/AppOpsManager.java | 173 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageParser.java | 1 | ||||
| -rw-r--r-- | core/java/android/content/pm/parsing/AndroidPackage.java | 4 | ||||
| -rw-r--r-- | core/java/android/content/pm/parsing/ApkParseUtils.java | 36 | ||||
| -rw-r--r-- | core/java/android/content/pm/parsing/ComponentParseUtils.java | 265 | ||||
| -rw-r--r-- | core/java/android/content/pm/parsing/PackageImpl.java | 18 | ||||
| -rw-r--r-- | core/java/android/content/pm/parsing/ParsingPackage.java | 3 | ||||
| -rw-r--r-- | core/res/res/values/attrs_manifest.xml | 26 | ||||
| -rw-r--r-- | core/res/res/values/public.xml | 6 | ||||
| -rw-r--r-- | services/core/java/com/android/server/appop/AppOpsService.java | 597 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 6 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java | 12 | ||||
| -rw-r--r-- | tools/aapt2/link/ManifestFixer.cpp | 2 |
14 files changed, 962 insertions, 188 deletions
diff --git a/api/current.txt b/api/current.txt index c734db5196a9..f6aa0a316b70 100644 --- a/api/current.txt +++ b/api/current.txt @@ -610,6 +610,7 @@ package android { 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/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 4840963f2633..c94e61a9a041 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -51,6 +51,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongSparseArray; import android.util.LongSparseLongArray; +import android.util.Pools; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -2341,15 +2342,31 @@ public class AppOpsManager { */ @TestApi @SystemApi - @Immutable - @DataClass(genHiddenConstructor = true) + // @DataClass(genHiddenConstructor = true, genHiddenCopyConstructor = true) + // genHiddenCopyConstructor does not work for @hide @SystemApi classes public static final class OpEventProxyInfo implements Parcelable { /** UID of the proxy app that noted the op */ - private final @IntRange(from = 0) int mUid; + private @IntRange(from = 0) int mUid; /** Package of the proxy that noted the op */ - private final @Nullable String mPackageName; + private @Nullable String mPackageName; /** ID of the feature of the proxy that noted the op */ - private final @Nullable String mFeatureId; + private @Nullable String mFeatureId; + + /** + * Reinit existing object with new state. + * + * @param uid UID of the proxy app that noted the op + * @param packageName Package of the proxy that noted the op + * @param featureId ID of the feature of the proxy that noted the op + * + * @hide + */ + public void reinit(@IntRange(from = 0) int uid, @Nullable String packageName, + @Nullable String featureId) { + mUid = Preconditions.checkArgumentNonnegative(uid); + mPackageName = packageName; + mFeatureId = featureId; + } @@ -2393,6 +2410,18 @@ public class AppOpsManager { } /** + * Copy constructor + * + * @hide + */ + @DataClass.Generated.Member + public OpEventProxyInfo(@NonNull OpEventProxyInfo orig) { + mUid = orig.mUid; + mPackageName = orig.mPackageName; + mFeatureId = orig.mFeatureId; + } + + /** * UID of the proxy app that noted the op */ @DataClass.Generated.Member @@ -2471,14 +2500,15 @@ public class AppOpsManager { } }; + /* @DataClass.Generated( - time = 1576194071700L, + time = 1576814974615L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java", - inputSignatures = "private final @android.annotation.IntRange(from=0L) int mUid\nprivate final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nclass OpEventProxyInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)") + inputSignatures = "private @android.annotation.IntRange(from=0L) int mUid\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.Nullable java.lang.String mFeatureId\npublic void reinit(int,java.lang.String,java.lang.String)\nclass OpEventProxyInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genHiddenCopyConstructor=true)") @Deprecated private void __metadata() {} - + */ //@formatter:on // End of generated code @@ -2490,15 +2520,48 @@ public class AppOpsManager { * * @hide */ - @Immutable //@DataClass codegen verifier is broken public static final class NoteOpEvent implements Parcelable { /** Time of noteOp event */ - public final @IntRange(from = 0) long noteTime; + private @IntRange(from = 0) long mNoteTime; /** The duration of this event (in case this is a startOp event, -1 otherwise). */ - public final @IntRange(from = -1) long duration; + private @IntRange(from = -1) long mDuration; /** Proxy information of the noteOp event */ - public final @Nullable OpEventProxyInfo proxy; + private @Nullable OpEventProxyInfo mProxy; + + /** + * Reinit existing object with new state. + * + * @param noteTime Time of noteOp event + * @param duration The duration of this event (in case this is a startOp event, + * -1 otherwise). + * @param proxy Proxy information of the noteOp event + * @param proxyPool The pool to release previous {@link OpEventProxyInfo} to + */ + public void reinit(@IntRange(from = 0) long noteTime, + @IntRange(from = -1) long duration, + @Nullable OpEventProxyInfo proxy, + @NonNull Pools.Pool<OpEventProxyInfo> proxyPool) { + mNoteTime = Preconditions.checkArgumentNonnegative(noteTime); + mDuration = Preconditions.checkArgumentInRange(duration, -1L, Long.MAX_VALUE, + "duration"); + + if (mProxy != null) { + proxyPool.release(mProxy); + } + mProxy = proxy; + } + + /** + * Copy constructor + * + * @hide + */ + public NoteOpEvent(@NonNull NoteOpEvent original) { + this(original.mNoteTime, original.mDuration, + original.mProxy != null ? new OpEventProxyInfo(original.mProxy) : null); + } + // Code below generated by codegen v1.0.14. @@ -2529,19 +2592,43 @@ public class AppOpsManager { @IntRange(from = 0) long noteTime, @IntRange(from = -1) long duration, @Nullable OpEventProxyInfo proxy) { - this.noteTime = noteTime; + this.mNoteTime = noteTime; com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, noteTime, + IntRange.class, null, mNoteTime, "from", 0); - this.duration = duration; + this.mDuration = duration; com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, duration, + IntRange.class, null, mDuration, "from", -1); - this.proxy = proxy; + this.mProxy = proxy; // onConstructed(); // You can define this method to get a callback } + /** + * Time of noteOp event + */ + @DataClass.Generated.Member + public @IntRange(from = 0) long getNoteTime() { + return mNoteTime; + } + + /** + * The duration of this event (in case this is a startOp event, -1 otherwise). + */ + @DataClass.Generated.Member + public @IntRange(from = -1) long getDuration() { + return mDuration; + } + + /** + * Proxy information of the noteOp event + */ + @DataClass.Generated.Member + public @Nullable OpEventProxyInfo getProxy() { + return mProxy; + } + @Override @DataClass.Generated.Member public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -2549,11 +2636,11 @@ public class AppOpsManager { // void parcelFieldName(Parcel dest, int flags) { ... } byte flg = 0; - if (proxy != null) flg |= 0x4; + if (mProxy != null) flg |= 0x4; dest.writeByte(flg); - dest.writeLong(noteTime); - dest.writeLong(duration); - if (proxy != null) dest.writeTypedObject(proxy, flags); + dest.writeLong(mNoteTime); + dest.writeLong(mDuration); + if (mProxy != null) dest.writeTypedObject(mProxy, flags); } @Override @@ -2568,20 +2655,19 @@ public class AppOpsManager { // static FieldType unparcelFieldName(Parcel in) { ... } byte flg = in.readByte(); - long _noteTime = in.readLong(); - long _duration = in.readLong(); - OpEventProxyInfo _proxy = (flg & 0x4) == 0 ? null : (OpEventProxyInfo) in.readTypedObject( - OpEventProxyInfo.CREATOR); + long noteTime = in.readLong(); + long duration = in.readLong(); + OpEventProxyInfo proxy = (flg & 0x4) == 0 ? null : (OpEventProxyInfo) in.readTypedObject(OpEventProxyInfo.CREATOR); - this.noteTime = _noteTime; + this.mNoteTime = noteTime; com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, noteTime, + IntRange.class, null, mNoteTime, "from", 0); - this.duration = _duration; + this.mDuration = duration; com.android.internal.util.AnnotationValidations.validate( - IntRange.class, null, duration, + IntRange.class, null, mDuration, "from", -1); - this.proxy = _proxy; + this.mProxy = proxy; // onConstructed(); // You can define this method to get a callback } @@ -2602,10 +2688,10 @@ public class AppOpsManager { /* @DataClass.Generated( - time = 1574809856220L, + time = 1576811792274L, codegenVersion = "1.0.14", sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java", - inputSignatures = "public final @android.annotation.IntRange(from=0L) long noteTime\npublic final @android.annotation.IntRange(from=-1) long duration\npublic final @android.annotation.Nullable android.app.NoteOpEventProxyInfo proxy\nclass NoteOpEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass") + inputSignatures = "private @android.annotation.IntRange(from=0L) long mNoteTime\nprivate @android.annotation.IntRange(from=-1) long mDuration\nprivate @android.annotation.Nullable android.app.OpEventProxyInfo mProxy\npublic void reinit(long,long,android.app.OpEventProxyInfo,android.util.Pools.Pool<android.app.OpEventProxyInfo>)\npublic @java.lang.Override java.lang.Object clone()\nclass NoteOpEvent extends java.lang.Object implements [android.os.Parcelable, java.lang.Cloneable]\n@com.android.internal.util.DataClass") @Deprecated private void __metadata() {} */ @@ -2751,7 +2837,7 @@ public class AppOpsManager { return -1; } - return lastEvent.noteTime; + return lastEvent.getNoteTime(); } /** @@ -2847,7 +2933,7 @@ public class AppOpsManager { return -1; } - return lastEvent.noteTime; + return lastEvent.getNoteTime(); } /** @@ -2922,7 +3008,7 @@ public class AppOpsManager { return -1; } - return lastEvent.duration; + return lastEvent.getDuration(); } /** @@ -3000,7 +3086,7 @@ public class AppOpsManager { return null; } - return lastEvent.proxy; + return lastEvent.getProxy(); } private static class LongSparseArrayParceling implements @@ -3304,7 +3390,7 @@ public class AppOpsManager { toUidState, flags); if (lastAccessEvent == null || (lastFeatureAccessEvent != null - && lastFeatureAccessEvent.noteTime > lastAccessEvent.noteTime)) { + && lastFeatureAccessEvent.getNoteTime() > lastAccessEvent.getNoteTime())) { lastAccessEvent = lastFeatureAccessEvent; } } @@ -3335,7 +3421,7 @@ public class AppOpsManager { return -1; } - return lastEvent.noteTime; + return lastEvent.getNoteTime(); } /** @@ -3418,7 +3504,7 @@ public class AppOpsManager { toUidState, flags); if (lastAccessEvent == null || (lastFeatureAccessEvent != null - && lastFeatureAccessEvent.noteTime > lastAccessEvent.noteTime)) { + && lastFeatureAccessEvent.getNoteTime() > lastAccessEvent.getNoteTime())) { lastAccessEvent = lastFeatureAccessEvent; } } @@ -3449,7 +3535,7 @@ public class AppOpsManager { return -1; } - return lastEvent.noteTime; + return lastEvent.getNoteTime(); } /** @@ -3544,7 +3630,7 @@ public class AppOpsManager { return -1; } - return lastEvent.duration; + return lastEvent.getDuration(); } /** @@ -3674,7 +3760,7 @@ public class AppOpsManager { return null; } - return lastEvent.proxy; + return lastEvent.getProxy(); } @@ -7331,7 +7417,8 @@ public class AppOpsManager { final long key = makeKey(uidState, flag); NoteOpEvent event = events.get(key); - if (lastEvent == null || event != null && event.noteTime > lastEvent.noteTime) { + if (lastEvent == null + || event != null && event.getNoteTime() > lastEvent.getNoteTime()) { lastEvent = event; } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 47edf2e6fd3a..32803ab8f859 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -202,6 +202,7 @@ public class PackageParser { 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 0562dffc551e..990c8359e698 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.PackageUserState; 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 @@ public interface AndroidPackage extends Parcelable { 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 3f2296729891..a001ada8df4a 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -793,6 +793,10 @@ public class ApkParseUtils { 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 @@ public class ApkParseUtils { ); } + 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 @@ public class ApkParseUtils { 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 fc210b266040..7b24d3df92a2 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.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 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.Parcel; 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.util.TypedValue; 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 org.xmlpull.v1.XmlPullParserException; 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,186 @@ public class ComponentParseUtils { }; } + /** + * 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 { + /** Maximum length of featureId */ + public static final int MAX_FEATURE_ID_LEN = 50; + + /** Maximum amount of features per package */ + private static final int MAX_NUM_FEATURES = 1000; + + /** 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(); + if (numFeatures > MAX_NUM_FEATURES) { + return false; + } + + 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 +2752,85 @@ public class ComponentParseUtils { 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; + } + if (featureId.length() > ParsedFeature.MAX_FEATURE_ID_LEN) { + outError[0] = "<featureId> is too long. Max length is " + + ParsedFeature.MAX_FEATURE_ID_LEN; + 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 18dee23dc690..8677fced18fa 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.ServiceInfo; 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 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android private ArrayList<ComponentParseUtils.ParsedProvider> providers; @Nullable + private ArrayList<ComponentParseUtils.ParsedFeature> features; + + @Nullable private ArrayList<ComponentParseUtils.ParsedPermission> permissions; @Nullable @@ -580,6 +584,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android return permissions; } + @Nullable + @Override + public List<ParsedFeature> getFeatures() { + return features; + } + @Override public String getCpuAbiOverride() { return cpuAbiOverride; @@ -792,6 +802,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @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 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android 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 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android 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 47dac5584c89..411c74991594 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.FeatureInfo; 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 @@ public interface ParsingPackage extends AndroidPackage { 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 85f7d617352e..6435cddebd1e 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 0b6351860a02..bea3920d8b71 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 7ef84bd80f69..1e136617bf24 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 @@ package com.android.server.appop; 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_FLAG_SELF; 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,9 +40,12 @@ import static android.app.AppOpsManager.makeKey; 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; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -69,6 +72,8 @@ import android.content.pm.PackageManager; 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; @@ -98,6 +103,7 @@ import android.util.AtomicFile; import android.util.KeyValueListParser; import android.util.LongSparseArray; import android.util.Pair; +import android.util.Pools.SimplePool; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -122,6 +128,7 @@ import com.android.internal.util.XmlUtils; 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; @@ -195,11 +202,21 @@ public class AppOpsService extends IAppOpsService.Stub { }; private static final int MAX_UNFORWARED_OPS = 10; + private static final int MAX_UNUSED_POOLED_OBJECTS = 3; Context mContext; final AtomicFile mFile; final Handler mHandler; + /** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */ + @GuardedBy("this") + private final OpEventProxyInfoPool mOpEventProxyInfoPool = new OpEventProxyInfoPool(); + + /** Pool for {@link InProgressStartOpEventPool} to avoid to constantly reallocate new objects */ + @GuardedBy("this") + private final InProgressStartOpEventPool mInProgressStartOpEventPool = + new InProgressStartOpEventPool(); + private final AppOpsManagerInternalImpl mAppOpsManagerInternal = new AppOpsManagerInternalImpl(); @@ -266,6 +283,46 @@ public class AppOpsService extends IAppOpsService.Stub { private SparseArray<List<Integer>> mSwitchOpToOps; /** + * An unsynchronized pool of {@link OpEventProxyInfo} objects. + */ + private class OpEventProxyInfoPool extends SimplePool<OpEventProxyInfo> { + OpEventProxyInfoPool() { + super(MAX_UNUSED_POOLED_OBJECTS); + } + + OpEventProxyInfo acquire(@IntRange(from = 0) int uid, @Nullable String packageName, + @Nullable String featureId) { + OpEventProxyInfo recycled = acquire(); + if (recycled != null) { + recycled.reinit(uid, packageName, featureId); + return recycled; + } + + return new OpEventProxyInfo(uid, packageName, featureId); + } + } + + /** + * An unsynchronized pool of {@link InProgressStartOpEvent} objects. + */ + private class InProgressStartOpEventPool extends SimplePool<InProgressStartOpEvent> { + InProgressStartOpEventPool() { + super(MAX_UNUSED_POOLED_OBJECTS); + } + + InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, + @NonNull Runnable onDeath, int uidState) throws RemoteException { + InProgressStartOpEvent recycled = acquire(); + if (recycled != null) { + recycled.reinit(startTime, elapsedTime, clientId, onDeath, uidState); + return recycled; + } + + return new InProgressStartOpEvent(startTime, elapsedTime, clientId, onDeath, uidState); + } + } + + /** * All times are in milliseconds. These constants are kept synchronized with the system * global Settings. Any access to this class or its fields should be done while * holding the AppOpsService lock. @@ -468,6 +525,9 @@ public class AppOpsService extends IAppOpsService.Stub { 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; @@ -478,43 +538,96 @@ public class AppOpsService extends IAppOpsService.Stub { /** A in progress startOp->finishOp event */ private static final class InProgressStartOpEvent implements IBinder.DeathRecipient { /** Wall clock time of startOp event (not monotonic) */ - final long startTime; + private long mStartTime; /** Elapsed time since boot of startOp event */ - final long startElapsedTime; + private long mStartElapsedTime; /** Id of the client that started the event */ - final @NonNull IBinder clientId; + private @NonNull IBinder mClientId; /** To call when client dies */ - final @NonNull Runnable onDeath; + private @NonNull Runnable mOnDeath; /** uidstate used when calling startOp */ - final @AppOpsManager.UidState int uidState; + private @AppOpsManager.UidState int mUidState; /** How many times the op was started but not finished yet */ int numUnfinishedStarts; + /** + * Create a new {@link InProgressStartOpEvent}. + * + * @param startTime The time {@link #startOperation} was called + * @param startElapsedTime The elapsed time when {@link #startOperation} was called + * @param clientId The client id of the caller of {@link #startOperation} + * @param onDeath The code to execute on client death + * @param uidState The uidstate of the app {@link #startOperation} was called for + * + * @throws RemoteException If the client is dying + */ private InProgressStartOpEvent(long startTime, long startElapsedTime, @NonNull IBinder clientId, @NonNull Runnable onDeath, int uidState) throws RemoteException { - this.startTime = startTime; - this.startElapsedTime = startElapsedTime; - this.clientId = clientId; - this.onDeath = onDeath; - this.uidState = uidState; + mStartTime = startTime; + mStartElapsedTime = startElapsedTime; + mClientId = clientId; + mOnDeath = onDeath; + mUidState = uidState; clientId.linkToDeath(this, 0); } /** Clean up event */ public void finish() { - clientId.unlinkToDeath(this, 0); + mClientId.unlinkToDeath(this, 0); } @Override public void binderDied() { - onDeath.run(); + mOnDeath.run(); + } + + /** + * Reinit existing object with new state. + * + * @param startTime The time {@link #startOperation} was called + * @param startElapsedTime The elapsed time when {@link #startOperation} was called + * @param clientId The client id of the caller of {@link #startOperation} + * @param onDeath The code to execute on client death + * @param uidState The uidstate of the app {@link #startOperation} was called for + * + * @throws RemoteException If the client is dying + */ + public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId, + @NonNull Runnable onDeath, int uidState) throws RemoteException { + mStartTime = startTime; + mStartElapsedTime = startElapsedTime; + mClientId = clientId; + mOnDeath = onDeath; + mUidState = uidState; + + clientId.linkToDeath(this, 0); + } + + /** @return Wall clock time of startOp event */ + public long getStartTime() { + return mStartTime; + } + + /** @return Elapsed time since boot of startOp event */ + public long getStartElapsedTime() { + return mStartElapsedTime; + } + + /** @return Id of the client that started the event */ + public @NonNull IBinder getClientId() { + return mClientId; + } + + /** @return uidstate used when calling startOp */ + public int getUidState() { + return mUidState; } } @@ -527,7 +640,7 @@ public class AppOpsService extends IAppOpsService.Stub { * <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 +648,7 @@ public class AppOpsService extends IAppOpsService.Stub { * <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 @@ -587,9 +700,16 @@ public class AppOpsService extends IAppOpsService.Stub { OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { - proxyInfo = new OpEventProxyInfo(proxyUid, proxyPackageName, proxyFeatureId); + proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, + proxyFeatureId); + } + + NoteOpEvent existingEvent = mAccessEvents.get(key); + if (existingEvent != null) { + existingEvent.reinit(noteTime, duration, proxyInfo, mOpEventProxyInfoPool); + } else { + mAccessEvents.put(key, new NoteOpEvent(noteTime, duration, proxyInfo)); } - mAccessEvents.put(key, new NoteOpEvent(noteTime, duration, proxyInfo)); } /** @@ -618,7 +738,12 @@ public class AppOpsService extends IAppOpsService.Stub { } // We do not collect proxy information for rejections yet - mRejectEvents.put(key, new NoteOpEvent(noteTime, -1, null)); + NoteOpEvent existingEvent = mRejectEvents.get(key); + if (existingEvent != null) { + existingEvent.reinit(noteTime, -1, null, mOpEventProxyInfoPool); + } else { + mRejectEvents.put(key, new NoteOpEvent(noteTime, -1, null)); + } } /** @@ -638,28 +763,15 @@ public class AppOpsService extends IAppOpsService.Stub { mInProgressEvents = new ArrayMap<>(1); } - InProgressStartOpEvent event = mInProgressEvents.get(clientId); if (event == null) { - event = new InProgressStartOpEvent(System.currentTimeMillis(), - SystemClock.elapsedRealtime(), clientId, () -> { - // In the case the client dies without calling finish first - synchronized (AppOpsService.this) { - if (mInProgressEvents == null) { - return; - } - - InProgressStartOpEvent deadEvent = mInProgressEvents.get(clientId); - if (deadEvent != null) { - deadEvent.numUnfinishedStarts = 1; - } - - finished(clientId); - } - }, uidState); + event = mInProgressStartOpEventPool.acquire(System.currentTimeMillis(), + SystemClock.elapsedRealtime(), clientId, + PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), + uidState); mInProgressEvents.put(clientId, event); } else { - if (uidState != event.uidState) { + if (uidState != event.mUidState) { onUidStateChanged(uidState); } } @@ -703,13 +815,15 @@ public class AppOpsService extends IAppOpsService.Stub { } // startOp events don't support proxy, hence use flags==SELF - NoteOpEvent finishedEvent = new NoteOpEvent(event.startTime, - SystemClock.elapsedRealtime() - event.startElapsedTime, null); - mAccessEvents.put(makeKey(event.uidState, OP_FLAG_SELF), finishedEvent); + NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(), + SystemClock.elapsedRealtime() - event.getStartElapsedTime(), null); + mAccessEvents.put(makeKey(event.getUidState(), OP_FLAG_SELF), finishedEvent); mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, - parent.packageName, event.uidState, AppOpsManager.OP_FLAG_SELF, - finishedEvent.duration); + parent.packageName, event.getUidState(), AppOpsManager.OP_FLAG_SELF, + finishedEvent.getDuration()); + + mInProgressStartOpEventPool.release(event); if (mInProgressEvents.isEmpty()) { mInProgressEvents = null; @@ -724,6 +838,26 @@ public class AppOpsService extends IAppOpsService.Stub { } /** + * Called in the case the client dies without calling finish first + * + * @param clientId The client that died + */ + void onClientDeath(@NonNull IBinder clientId) { + synchronized (AppOpsService.this) { + if (mInProgressEvents == null) { + return; + } + + InProgressStartOpEvent deadEvent = mInProgressEvents.get(clientId); + if (deadEvent != null) { + deadEvent.numUnfinishedStarts = 1; + } + + finished(clientId); + } + } + + /** * Notify that the state of the uid changed * * @param newState The new state @@ -737,10 +871,10 @@ public class AppOpsService extends IAppOpsService.Stub { for (int i = 0; i < numInProgressEvents; i++) { InProgressStartOpEvent event = mInProgressEvents.valueAt(i); - if (event.uidState != newState) { + if (event.getUidState() != newState) { try { - finished(event.clientId, false); - started(event.clientId, newState); + finished(event.getClientId(), false); + started(event.getClientId(), newState); } catch (RemoteException e) { if (DEBUG) Slog.e(TAG, "Cannot switch to new uidState " + newState); } @@ -748,6 +882,59 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * 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.getNoteTime() > aEvent.getNoteTime()) { + 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++) { + InProgressStartOpEvent event = opToAdd.mInProgressEvents.valueAt(i); + + event.finish(); + mInProgressStartOpEventPool.release(event); + } + } + + mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents); + mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents); + } + public boolean isRunning() { return mInProgressEvents != null; } @@ -757,12 +944,27 @@ public class AppOpsService extends IAppOpsService.Stub { || (mRejectEvents != null && mRejectEvents.size() > 0); } - @NonNull OpFeatureEntry createFeatureEntryLocked() { - LongSparseArray<NoteOpEvent> accessEvents = null; - if (mAccessEvents != null) { - accessEvents = mAccessEvents.clone(); + /** + * Clone a {@link LongSparseArray} and clone all values. + */ + private @Nullable LongSparseArray<NoteOpEvent> deepClone( + @Nullable LongSparseArray<NoteOpEvent> original) { + if (original == null) { + return original; } + int size = original.size(); + LongSparseArray<NoteOpEvent> clone = new LongSparseArray<>(size); + for (int i = 0; i < size; i++) { + clone.put(original.keyAt(i), new NoteOpEvent(original.valueAt(i))); + } + + return clone; + } + + @NonNull OpFeatureEntry createFeatureEntryLocked() { + LongSparseArray<NoteOpEvent> accessEvents = deepClone(mAccessEvents); + // Add in progress events as access events if (mInProgressEvents != null) { long now = SystemClock.elapsedRealtime(); @@ -776,15 +978,13 @@ public class AppOpsService extends IAppOpsService.Stub { InProgressStartOpEvent event = mInProgressEvents.valueAt(i); // startOp events don't support proxy - accessEvents.append(makeKey(event.uidState, OP_FLAG_SELF), - new NoteOpEvent(event.startTime, now - event.startElapsedTime, null)); + accessEvents.append(makeKey(event.getUidState(), OP_FLAG_SELF), + new NoteOpEvent(event.getStartTime(), now - event.getStartElapsedTime(), + null)); } } - LongSparseArray<NoteOpEvent> rejectEvents = null; - if (mRejectEvents != null) { - rejectEvents = mRejectEvents.clone(); - } + LongSparseArray<NoteOpEvent> rejectEvents = deepClone(mRejectEvents); return new OpFeatureEntry(parent.op, isRunning(), accessEvents, rejectEvents); } @@ -1024,6 +1224,13 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * Call {@link FeatureOp#onClientDeath featureOp.onClientDeath(clientId)}. + */ + private static void onClientDeath(@NonNull FeatureOp featureOp, @NonNull IBinder clientId) { + featureOp.onClientDeath(clientId); + } + public AppOpsService(File storagePath, Handler handler) { LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); mFile = new AtomicFile(storagePath, "appops"); @@ -1038,20 +1245,110 @@ public class AppOpsService extends IAppOpsService.Stub { 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()); + IntentFilter packageUpdateFilter = new IntentFilter(); + packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + packageUpdateFilter.addDataScheme("package"); + + mContext.registerReceiver(mOnPackageUpdatedReceiver, packageUpdateFilter); + synchronized (this) { - boolean changed = false; - for (int i = mUidStates.size() - 1; i >= 0; i--) { - UidState uidState = mUidStates.valueAt(i); + for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) { + int uid = mUidStates.keyAt(uidNum); + UidState uidState = mUidStates.valueAt(uidNum); - String[] packageNames = getPackagesForUid(uidState.uid); - if (ArrayUtils.isEmpty(packageNames)) { + String[] pkgsInUid = getPackagesForUid(uidState.uid); + if (ArrayUtils.isEmpty(pkgsInUid)) { uidState.clear(); - mUidStates.removeAt(i); - changed = true; + mUidStates.removeAt(uidNum); + scheduleFastWriteLocked(); continue; } @@ -1060,31 +1357,24 @@ public class AppOpsService extends IAppOpsService.Stub { 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); + + String action; + if (!ArrayUtils.contains(pkgsInUid, pkg)) { + action = Intent.ACTION_PACKAGE_REMOVED; + } else { + action = Intent.ACTION_PACKAGE_REPLACED; } - } - if (uidState.isDefault()) { - mUidStates.removeAt(i); + 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(); @@ -1198,7 +1488,7 @@ public class AppOpsService extends IAppOpsService.Stub { FeatureOp featureOp = op.mFeatures.valueAt(featureNum); while (featureOp.mInProgressEvents != null) { - featureOp.mInProgressEvents.valueAt(0).onDeath.run(); + featureOp.finished(featureOp.mInProgressEvents.keyAt(0)); } } } @@ -1388,7 +1678,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +1778,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2042,7 @@ public class AppOpsService extends IAppOpsService.Stub { boolean isPrivileged; try { - isPrivileged = verifyAndGetIsPrivileged(uid, packageName); + isPrivileged = verifyAndGetIsPrivileged(uid, packageName, null); } catch (SecurityException e) { Slog.e(TAG, "Cannot setMode", e); return; @@ -1767,7 +2057,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2427,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2437,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2447,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2510,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2580,17 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2598,9 @@ public class AppOpsService extends IAppOpsService.Stub { + " 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; @@ -2320,7 +2609,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (featureOp.isRunning()) { Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code " + code + " startTime of in progress event=" - + featureOp.mInProgressEvents.valueAt(0).startTime); + + featureOp.mInProgressEvents.valueAt(0).getStartTime()); } final int switchCode = AppOpsManager.opToSwitch(code); @@ -2339,7 +2628,8 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2750,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2759,7 @@ public class AppOpsService extends IAppOpsService.Stub { long now = System.currentTimeMillis(); if (callingPackageName != null) { - verifyAndGetIsPrivileged(callingUid, callingPackageName); + verifyAndGetIsPrivileged(callingUid, callingPackageName, featureId); } long token = Binder.clearCallingIdentity(); @@ -2534,7 +2824,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2854,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2873,7 @@ public class AppOpsService extends IAppOpsService.Stub { int uid = Binder.getCallingUid(); - verifyAndGetIsPrivileged(uid, packageName); + verifyAndGetIsPrivileged(uid, packageName, null); synchronized (this) { return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid)); @@ -2602,22 +2892,22 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2929,8 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +2968,14 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +3206,24 @@ public class AppOpsService extends IAppOpsService.Stub { * * @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 +3233,31 @@ public class AppOpsService extends IAppOpsService.Stub { final long ident = Binder.clearCallingIdentity(); try { int pkgUid; + AndroidPackage pkg = LocalServices.getService(PackageManagerInternal.class).getPackage( + packageName); + boolean isFeatureIdValid = false; + + 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; + } + } + } + } - 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; + 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 +3267,12 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +3285,14 @@ public class AppOpsService extends IAppOpsService.Stub { * * @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 +3313,9 @@ public class AppOpsService extends IAppOpsService.Stub { ops = new Ops(packageName, uidState, isPrivileged); uidState.pkgOps.put(packageName, ops); } + if (edit && featureId != null) { + ops.knownFeatureIds.add(featureId); + } return ops; } @@ -3010,13 +3326,14 @@ public class AppOpsService extends IAppOpsService.Stub { * * @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 +3354,11 @@ public class AppOpsService extends IAppOpsService.Stub { ops = new Ops(packageName, uidState, isPrivileged); uidState.pkgOps.put(packageName, ops); } + + if (edit && featureId != null) { + ops.knownFeatureIds.add(featureId); + } + return ops; } @@ -3062,6 +3384,7 @@ public class AppOpsService extends IAppOpsService.Stub { * @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 +3392,21 @@ public class AppOpsService extends IAppOpsService.Stub { * @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 +3424,7 @@ public class AppOpsService extends IAppOpsService.Stub { } 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 +3436,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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 +3813,7 @@ public class AppOpsService extends IAppOpsService.Stub { 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. @@ -4200,7 +4523,7 @@ public class AppOpsService extends IAppOpsService.Stub { for (int i = 0; i < numInProgressEvents; i++) { InProgressStartOpEvent event = featureOp.mInProgressEvents.valueAt(i); - earliestElapsedTime = Math.min(earliestElapsedTime, event.startElapsedTime); + earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime()); maxNumStarts = Math.max(maxNumStarts, event.numUnfinishedStarts); } @@ -4807,7 +5130,7 @@ public class AppOpsService extends IAppOpsService.Stub { } // 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 b793d6de2e40..04e7372a92a7 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 @@ public class PackageManagerService extends IPackageManager.Stub "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 529339e48546..f553a4818fa2 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.app.AppOpsManager.PackageOps; 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.junit.runner.RunWith; 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 @@ public class AppOpsServiceTest { .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 b725920ba014..27960c8ed4db 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -365,6 +365,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, }); 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"]; |