summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/app/AppOpsManager.java225
-rw-r--r--core/java/android/app/SyncNotedAppOp.java54
-rw-r--r--core/java/android/content/AttributionSource.java385
-rw-r--r--core/java/android/content/PermissionChecker.java502
-rw-r--r--core/java/android/os/BinderProxy.java5
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java55
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java420
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java2
10 files changed, 845 insertions, 809 deletions
diff --git a/Android.bp b/Android.bp
index acf86a0cfa2d..0ffafc6dcef2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -326,6 +326,7 @@ java_defaults {
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
"modules-utils-os",
+ "framework-permission-aidl-java",
],
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 196799345efd..d366e35dd11a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -219,6 +219,7 @@ package android.app {
public class AppOpsManager {
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory();
+ method public static void collectNotedOpSync(@NonNull android.app.SyncNotedAppOp);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static int getNumOps();
method public boolean isOperationActive(int, int, String);
@@ -360,6 +361,10 @@ package android.app {
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}
+ public final class SyncNotedAppOp implements android.os.Parcelable {
+ ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String);
+ }
+
public class TaskInfo {
method public boolean containsLaunchCookie(@NonNull android.os.IBinder);
method @NonNull public android.content.res.Configuration getConfiguration();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 75a38c2380b9..52b38a20e4a1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2783,14 +2783,24 @@ public class AppOpsManager {
private static final ThreadLocal<Integer> sBinderThreadCallingUid = new ThreadLocal<>();
/**
- * If a thread is currently executing a two-way binder transaction, this stores the op-codes of
- * the app-ops that were noted during this transaction.
+ * Optimization: we need to propagate to IPCs whether the current thread is collecting
+ * app ops but using only the thread local above is too slow as it requires a map lookup
+ * on every IPC. We add this static var that is lockless and stores an OR-ed mask of the
+ * thread id's currently collecting ops, thus reducing the map lookup to a simple bit
+ * operation except the extremely unlikely case when threads with overlapping id bits
+ * execute op collecting ops.
+ */
+ private static volatile long sThreadsListeningForOpNotedInBinderTransaction = 0L;
+
+ /**
+ * If a thread is currently executing a two-way binder transaction, this stores the
+ * ops that were noted blaming any app (the caller, the caller of the caller, etc).
*
* @see #getNotedOpCollectionMode
* @see #collectNotedOpSync
*/
- private static final ThreadLocal<ArrayMap<String, long[]>> sAppOpsNotedInThisBinderTransaction =
- new ThreadLocal<>();
+ private static final ThreadLocal<ArrayMap<String, ArrayMap<String, long[]>>>
+ sAppOpsNotedInThisBinderTransaction = new ThreadLocal<>();
/** Whether noting for an appop should be collected */
private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
@@ -8105,7 +8115,7 @@ public class AppOpsManager {
SyncNotedAppOp syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
- if (syncOp.getOpMode()== MODE_ALLOWED) {
+ if (syncOp.getOpMode() == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
collectNotedOpForSelf(syncOp);
} else if (collectionMode == COLLECT_SYNC) {
@@ -8872,70 +8882,11 @@ public class AppOpsManager {
* @hide
*/
public static void startNotedAppOpsCollection(int callingUid) {
+ sThreadsListeningForOpNotedInBinderTransaction |= Thread.currentThread().getId();
sBinderThreadCallingUid.set(callingUid);
}
/**
- * State of a temporarily paused noted app-ops collection.
- *
- * @see #pauseNotedAppOpsCollection()
- *
- * @hide
- */
- public static class PausedNotedAppOpsCollection {
- final int mUid;
- final @Nullable ArrayMap<String, long[]> mCollectedNotedAppOps;
-
- PausedNotedAppOpsCollection(int uid, @Nullable ArrayMap<String,
- long[]> collectedNotedAppOps) {
- mUid = uid;
- mCollectedNotedAppOps = collectedNotedAppOps;
- }
- }
-
- /**
- * Temporarily suspend collection of noted app-ops when binder-thread calls into the other
- * process. During such a call there might be call-backs coming back on the same thread which
- * should not be accounted to the current collection.
- *
- * @return a state needed to resume the collection
- *
- * @hide
- */
- public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
- Integer previousUid = sBinderThreadCallingUid.get();
- if (previousUid != null) {
- ArrayMap<String, long[]> previousCollectedNotedAppOps =
- sAppOpsNotedInThisBinderTransaction.get();
-
- sBinderThreadCallingUid.remove();
- sAppOpsNotedInThisBinderTransaction.remove();
-
- return new PausedNotedAppOpsCollection(previousUid, previousCollectedNotedAppOps);
- }
-
- return null;
- }
-
- /**
- * Resume a collection paused via {@link #pauseNotedAppOpsCollection}.
- *
- * @param prevCollection The state of the previous collection
- *
- * @hide
- */
- public static void resumeNotedAppOpsCollection(
- @Nullable PausedNotedAppOpsCollection prevCollection) {
- if (prevCollection != null) {
- sBinderThreadCallingUid.set(prevCollection.mUid);
-
- if (prevCollection.mCollectedNotedAppOps != null) {
- sAppOpsNotedInThisBinderTransaction.set(prevCollection.mCollectedNotedAppOps);
- }
- }
- }
-
- /**
* Finish collection of noted appops on this thread.
*
* <p>Called at the end of a two way binder transaction.
@@ -8946,6 +8897,7 @@ public class AppOpsManager {
*/
public static void finishNotedAppOpsCollection() {
sBinderThreadCallingUid.remove();
+ sThreadsListeningForOpNotedInBinderTransaction &= ~Thread.currentThread().getId();
sAppOpsNotedInThisBinderTransaction.remove();
}
@@ -8970,28 +8922,52 @@ public class AppOpsManager {
* <p> Delivered to caller via {@link #prefixParcelWithAppOpsIfNeeded}
*
* @param syncOp the op and attribution tag to note for
+ *
+ * @hide
*/
- private void collectNotedOpSync(@NonNull SyncNotedAppOp syncOp) {
+ @TestApi
+ public static void collectNotedOpSync(@NonNull SyncNotedAppOp syncOp) {
+ collectNotedOpSync(sOpStrToOp.get(syncOp.getOp()), syncOp.getAttributionTag(),
+ syncOp.getPackageName());
+ }
+
+ /**
+ * Collect a noted op when inside of a two-way binder call.
+ *
+ * <p> Delivered to caller via {@link #prefixParcelWithAppOpsIfNeeded}
+ *
+ * @param code the op code to note for
+ * @param attributionTag the attribution tag to note for
+ * @param packageName the package to note for
+ */
+ private static void collectNotedOpSync(int code, @Nullable String attributionTag,
+ @NonNull String packageName) {
// If this is inside of a two-way binder call:
// We are inside of a two-way binder call. Delivered to caller via
// {@link #prefixParcelWithAppOpsIfNeeded}
- int op = sOpStrToOp.get(syncOp.getOp());
- ArrayMap<String, long[]> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
+ ArrayMap<String, ArrayMap<String, long[]>> appOpsNoted =
+ sAppOpsNotedInThisBinderTransaction.get();
if (appOpsNoted == null) {
appOpsNoted = new ArrayMap<>(1);
sAppOpsNotedInThisBinderTransaction.set(appOpsNoted);
}
- long[] appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
+ ArrayMap<String, long[]> packageAppOpsNotedForAttribution = appOpsNoted.get(packageName);
+ if (packageAppOpsNotedForAttribution == null) {
+ packageAppOpsNotedForAttribution = new ArrayMap<>(1);
+ appOpsNoted.put(packageName, packageAppOpsNotedForAttribution);
+ }
+
+ long[] appOpsNotedForAttribution = packageAppOpsNotedForAttribution.get(attributionTag);
if (appOpsNotedForAttribution == null) {
appOpsNotedForAttribution = new long[2];
- appOpsNoted.put(syncOp.getAttributionTag(), appOpsNotedForAttribution);
+ packageAppOpsNotedForAttribution.put(attributionTag, appOpsNotedForAttribution);
}
- if (op < 64) {
- appOpsNotedForAttribution[0] |= 1L << op;
+ if (code < 64) {
+ appOpsNotedForAttribution[0] |= 1L << code;
} else {
- appOpsNotedForAttribution[1] |= 1L << (op - 64);
+ appOpsNotedForAttribution[1] |= 1L << (code - 64);
}
}
@@ -9045,9 +9021,7 @@ public class AppOpsManager {
}
}
- Integer binderUid = sBinderThreadCallingUid.get();
-
- if (binderUid != null && binderUid == uid) {
+ if (isListeningForOpNotedInBinderTransaction()) {
return COLLECT_SYNC;
} else {
return COLLECT_ASYNC;
@@ -9064,21 +9038,31 @@ public class AppOpsManager {
*
* @hide
*/
+ // TODO (b/186872903) Refactor how sync noted ops are propagaged.
public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
- ArrayMap<String, long[]> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+ final ArrayMap<String, ArrayMap<String, long[]>> notedAppOps =
+ sAppOpsNotedInThisBinderTransaction.get();
if (notedAppOps == null) {
return;
}
p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
- int numAttributionWithNotesAppOps = notedAppOps.size();
- p.writeInt(numAttributionWithNotesAppOps);
+ final int packageCount = notedAppOps.size();
+ p.writeInt(packageCount);
- for (int i = 0; i < numAttributionWithNotesAppOps; i++) {
+ for (int i = 0; i < packageCount; i++) {
p.writeString(notedAppOps.keyAt(i));
- p.writeLong(notedAppOps.valueAt(i)[0]);
- p.writeLong(notedAppOps.valueAt(i)[1]);
+
+ final ArrayMap<String, long[]> notedTagAppOps = notedAppOps.valueAt(i);
+ final int tagCount = notedTagAppOps.size();
+ p.writeInt(tagCount);
+
+ for (int j = 0; j < tagCount; j++) {
+ p.writeString(notedTagAppOps.keyAt(j));
+ p.writeLong(notedTagAppOps.valueAt(j)[0]);
+ p.writeLong(notedTagAppOps.valueAt(j)[1]);
+ }
}
}
@@ -9093,36 +9077,57 @@ public class AppOpsManager {
* @hide
*/
public static void readAndLogNotedAppops(@NonNull Parcel p) {
- int numAttributionsWithNotedAppOps = p.readInt();
+ final int packageCount = p.readInt();
+ if (packageCount <= 0) {
+ return;
+ }
+
+ final String myPackageName = ActivityThread.currentPackageName();
+ if (myPackageName == null) {
+ return;
+ }
- for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
- String attributionTag = p.readString();
- long[] rawNotedAppOps = new long[2];
- rawNotedAppOps[0] = p.readLong();
- rawNotedAppOps[1] = p.readLong();
+ synchronized (sLock) {
+ for (int i = 0; i < packageCount; i++) {
+ final String packageName = p.readString();
- if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
- BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+ final int tagCount = p.readInt();
+ for (int j = 0; j < tagCount; j++) {
+ final String attributionTag = p.readString();
+ final long[] rawNotedAppOps = new long[2];
+ rawNotedAppOps[0] = p.readLong();
+ rawNotedAppOps[1] = p.readLong();
- synchronized (sLock) {
+ if (rawNotedAppOps[0] == 0 && rawNotedAppOps[1] == 0) {
+ continue;
+ }
+
+ final BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
for (int code = notedAppOps.nextSetBit(0); code != -1;
code = notedAppOps.nextSetBit(code + 1)) {
- if (sOnOpNotedCallback != null) {
- sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code, attributionTag));
- } else {
- String message = getFormattedStackTrace();
- sUnforwardedOps.add(
- new AsyncNotedAppOp(code, Process.myUid(), attributionTag,
- message, System.currentTimeMillis()));
- if (sUnforwardedOps.size() > MAX_UNFORWARDED_OPS) {
- sUnforwardedOps.remove(0);
+ if (myPackageName.equals(packageName)) {
+ if (sOnOpNotedCallback != null) {
+ sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code,
+ attributionTag, packageName));
+ } else {
+ String message = getFormattedStackTrace();
+ sUnforwardedOps.add(new AsyncNotedAppOp(code, Process.myUid(),
+ attributionTag, message, System.currentTimeMillis()));
+ if (sUnforwardedOps.size() > MAX_UNFORWARDED_OPS) {
+ sUnforwardedOps.remove(0);
+ }
}
+ } else if (isListeningForOpNotedInBinderTransaction()) {
+ collectNotedOpSync(code, attributionTag, packageName);
+ }
+ }
+ for (int code = notedAppOps.nextSetBit(0); code != -1;
+ code = notedAppOps.nextSetBit(code + 1)) {
+ if (myPackageName.equals(packageName)) {
+ sMessageCollector.onNoted(new SyncNotedAppOp(code,
+ attributionTag, packageName));
}
}
- }
- for (int code = notedAppOps.nextSetBit(0); code != -1;
- code = notedAppOps.nextSetBit(code + 1)) {
- sMessageCollector.onNoted(new SyncNotedAppOp(code, attributionTag));
}
}
}
@@ -9229,7 +9234,17 @@ public class AppOpsManager {
* @hide
*/
public static boolean isListeningForOpNoted() {
- return sOnOpNotedCallback != null || isCollectingStackTraces();
+ return sOnOpNotedCallback != null || isListeningForOpNotedInBinderTransaction()
+ || isCollectingStackTraces();
+ }
+
+ /**
+ * @return whether we are in a binder transaction and collecting appops.
+ */
+ private static boolean isListeningForOpNotedInBinderTransaction() {
+ return (sThreadsListeningForOpNotedInBinderTransaction
+ & Thread.currentThread().getId()) != 0
+ && sBinderThreadCallingUid.get() != null;
}
/**
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index bc4e5436996d..32d889e81cb0 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -19,7 +19,9 @@ package android.app;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Parcelable;
+import android.os.Process;
import com.android.internal.annotations.Immutable;
import com.android.internal.util.DataClass;
@@ -48,13 +50,19 @@ public final class SyncNotedAppOp implements Parcelable {
private final @IntRange(from = 0L, to = AppOpsManager._NUM_OP - 1) int mOpCode;
/** attributionTag of synchronous appop noted */
private final @Nullable String mAttributionTag;
+ /**
+ * The package this op applies to
+ * @hide
+ */
+ private final @NonNull String mPackageName;
/**
* Native code relies on parcel ordering, do not change
* @hide
*/
+ @TestApi
public SyncNotedAppOp(int opMode, @IntRange(from = 0L) int opCode,
- @Nullable String attributionTag) {
+ @Nullable String attributionTag, @NonNull String packageName) {
this.mOpCode = opCode;
com.android.internal.util.AnnotationValidations.validate(
IntRange.class, null, mOpCode,
@@ -62,6 +70,7 @@ public final class SyncNotedAppOp implements Parcelable {
"to", AppOpsManager._NUM_OP - 1);
this.mAttributionTag = attributionTag;
this.mOpMode = opMode;
+ this.mPackageName = packageName;
}
/**
@@ -73,7 +82,25 @@ public final class SyncNotedAppOp implements Parcelable {
* attributionTag of synchronous appop noted
*/
public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag) {
- this(AppOpsManager.MODE_IGNORED, opCode, attributionTag);
+ this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, ActivityThread
+ .currentPackageName());
+ }
+
+ /**
+ * Creates a new SyncNotedAppOp.
+ *
+ * @param opCode
+ * op code of synchronous appop noted
+ * @param attributionTag
+ * attributionTag of synchronous appop noted
+ * @param packageName
+ * The package this op applies to
+ *
+ * @hide
+ */
+ public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag,
+ @NonNull String packageName) {
+ this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, packageName);
}
/**
@@ -113,6 +140,16 @@ public final class SyncNotedAppOp implements Parcelable {
return mAttributionTag;
}
+ /**
+ * The package this op applies to
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
@Override
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
@@ -128,7 +165,8 @@ public final class SyncNotedAppOp implements Parcelable {
return true
&& mOpMode == that.mOpMode
&& mOpCode == that.mOpCode
- && java.util.Objects.equals(mAttributionTag, that.mAttributionTag);
+ && java.util.Objects.equals(mAttributionTag, that.mAttributionTag)
+ && java.util.Objects.equals(mPackageName, that.mPackageName);
}
@Override
@@ -141,6 +179,7 @@ public final class SyncNotedAppOp implements Parcelable {
_hash = 31 * _hash + mOpMode;
_hash = 31 * _hash + mOpCode;
_hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
return _hash;
}
@@ -156,6 +195,7 @@ public final class SyncNotedAppOp implements Parcelable {
dest.writeInt(mOpMode);
dest.writeInt(mOpCode);
if (mAttributionTag != null) dest.writeString(mAttributionTag);
+ dest.writeString(mPackageName);
}
@Override
@@ -173,6 +213,7 @@ public final class SyncNotedAppOp implements Parcelable {
int opMode = in.readInt();
int opCode = in.readInt();
String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
+ String packageName = in.readString();
this.mOpMode = opMode;
this.mOpCode = opCode;
@@ -181,6 +222,9 @@ public final class SyncNotedAppOp implements Parcelable {
"from", 0L,
"to", AppOpsManager._NUM_OP - 1);
this.mAttributionTag = attributionTag;
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
// onConstructed(); // You can define this method to get a callback
}
@@ -200,10 +244,10 @@ public final class SyncNotedAppOp implements Parcelable {
};
@DataClass.Generated(
- time = 1617317997768L,
+ time = 1619711733947L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
- inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)")
+ inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 2c155d5884ac..7ab731f15ad2 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -31,10 +31,9 @@ import android.permission.PermissionManager;
import android.util.ArraySet;
import com.android.internal.annotations.Immutable;
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.Objects;
import java.util.Set;
@@ -70,10 +69,10 @@ import java.util.Set;
* This is supported to handle cases where you don't have access to the caller's attribution
* source and you can directly use the {@link AttributionSource.Builder} APIs. However,
* if the data flows through more than two apps (more than you access the data for the
- * caller - which you cannot know ahead of time) you need to have a handle to the {@link
- * AttributionSource} for the calling app's context in order to create an attribution context.
- * This means you either need to have an API for the other app to send you its attribution
- * source or use a platform API that pipes the callers attribution source.
+ * caller) you need to have a handle to the {@link AttributionSource} for the calling app's
+ * context in order to create an attribution context. This means you either need to have an
+ * API for the other app to send you its attribution source or use a platform API that pipes
+ * the callers attribution source.
* <p>
* You cannot forge an attribution chain without the participation of every app in the
* attribution chain (aside of the special case mentioned above). To create an attribution
@@ -85,80 +84,11 @@ import java.util.Set;
* permission protected APIs since some app in the chain may not have the permission.
*/
@Immutable
-// TODO: Codegen doesn't properly verify the class if the parcelling is inner class
-// TODO: Codegen doesn't allow overriding the constructor to change its visibility
-// TODO: Codegen applies method level annotations to argument vs the generated member (@SystemApi)
-// TODO: Codegen doesn't properly read/write IBinder members
-// TODO: Codegen doesn't properly handle Set arguments
-// TODO: Codegen requires @SystemApi annotations on fields which breaks
-// android.signature.cts.api.AnnotationTest (need to update the test)
-// @DataClass(genEqualsHashCode = true, genConstructor = false, genBuilder = true)
public final class AttributionSource implements Parcelable {
- /**
- * @hide
- */
- static class RenouncedPermissionsParcelling implements Parcelling<Set<String>> {
-
- @Override
- public void parcel(Set<String> item, Parcel dest, int parcelFlags) {
- if (item == null) {
- dest.writeInt(-1);
- } else {
- dest.writeInt(item.size());
- for (String permission : item) {
- dest.writeString8(permission);
- }
- }
- }
-
- @Override
- public Set<String> unparcel(Parcel source) {
- final int size = source.readInt();
- if (size < 0) {
- return null;
- }
- final ArraySet<String> result = new ArraySet<>(size);
- for (int i = 0; i < size; i++) {
- result.add(source.readString8());
- }
- return result;
- }
- }
-
- /**
- * The UID that is accessing the permission protected data.
- */
- private final int mUid;
-
- /**
- * The package that is accessing the permission protected data.
- */
- private @Nullable String mPackageName = null;
+ private final @NonNull AttributionSourceState mAttributionSourceState;
- /**
- * The attribution tag of the app accessing the permission protected data.
- */
- private @Nullable String mAttributionTag = null;
-
- /**
- * Unique token for that source.
- *
- * @hide
- */
- private @Nullable IBinder mToken = null;
-
- /**
- * Permissions that should be considered revoked regardless if granted.
- *
- * @hide
- */
- @DataClass.ParcelWith(RenouncedPermissionsParcelling.class)
- private @Nullable Set<String> mRenouncedPermissions = null;
-
- /**
- * The next app to receive the permission protected data.
- */
- private @Nullable AttributionSource mNext = null;
+ private @Nullable AttributionSource mNextCached;
+ private @Nullable Set<String> mRenouncedPermissionsCached;
/** @hide */
@TestApi
@@ -171,8 +101,7 @@ public final class AttributionSource implements Parcelable {
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, /*token*/ null,
- /*renouncedPermissions*/ null, next);
+ this(uid, packageName, attributionTag, /*renouncedPermissions*/ null, next);
}
/** @hide */
@@ -180,8 +109,8 @@ public final class AttributionSource implements Parcelable {
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
@Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, /*token*/ null,
- renouncedPermissions, next);
+ this(uid, packageName, attributionTag, /*token*/ null, (renouncedPermissions != null)
+ ? renouncedPermissions.toArray(new String[0]) : null, next);
}
/** @hide */
@@ -191,16 +120,49 @@ public final class AttributionSource implements Parcelable {
/*token*/ null, /*renouncedPermissions*/ null, next);
}
+ AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+ @Nullable IBinder token, @Nullable String[] renouncedPermissions,
+ @Nullable AttributionSource next) {
+ mAttributionSourceState = new AttributionSourceState();
+ mAttributionSourceState.uid = uid;
+ mAttributionSourceState.packageName = packageName;
+ mAttributionSourceState.attributionTag = attributionTag;
+ mAttributionSourceState.token = token;
+ mAttributionSourceState.renouncedPermissions = renouncedPermissions;
+ mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
+ {next.mAttributionSourceState} : null;
+ }
+
+ AttributionSource(@NonNull Parcel in) {
+ this(AttributionSourceState.CREATOR.createFromParcel(in));
+ }
+
+ /** @hide */
+ public AttributionSource(@NonNull AttributionSourceState attributionSourceState) {
+ mAttributionSourceState = attributionSourceState;
+ }
+
/** @hide */
public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
- return new AttributionSource(mUid, mPackageName, mAttributionTag, mToken,
- mRenouncedPermissions, next);
+ return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, next);
}
/** @hide */
public AttributionSource withToken(@Nullable IBinder token) {
- return new AttributionSource(mUid, mPackageName, mAttributionTag, token,
- mRenouncedPermissions, mNext);
+ return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+ token, mAttributionSourceState.renouncedPermissions, getNext());
+ }
+
+ /** @hide */
+ public AttributionSource withPackageName(@Nullable String packageName) {
+ return new AttributionSource(getUid(), packageName, getAttributionTag(), getToken(),
+ mAttributionSourceState.renouncedPermissions, getNext());
+ }
+
+ /** @hide */
+ public @NonNull AttributionSourceState asState() {
+ return mAttributionSourceState;
}
/**
@@ -213,10 +175,9 @@ public final class AttributionSource implements Parcelable {
* from the caller.
*/
public void enforceCallingUid() {
- final int callingUid = Binder.getCallingUid();
- if (callingUid != Process.SYSTEM_UID && callingUid != mUid) {
- throw new SecurityException("Calling uid: " + callingUid
- + " doesn't match source uid: " + mUid);
+ if (!checkCallingUid()) {
+ throw new SecurityException("Calling uid: " + Binder.getCallingUid()
+ + " doesn't match source uid: " + mAttributionSourceState.uid);
}
// No need to check package as app ops manager does it already.
}
@@ -231,7 +192,8 @@ public final class AttributionSource implements Parcelable {
*/
public boolean checkCallingUid() {
final int callingUid = Binder.getCallingUid();
- if (callingUid != Process.SYSTEM_UID && callingUid != mUid) {
+ if (callingUid != Process.SYSTEM_UID
+ && callingUid != mAttributionSourceState.uid) {
return false;
}
// No need to check package as app ops manager does it already.
@@ -242,11 +204,12 @@ public final class AttributionSource implements Parcelable {
public String toString() {
if (Build.IS_DEBUGGABLE) {
return "AttributionSource { " +
- "uid = " + mUid + ", " +
- "packageName = " + mPackageName + ", " +
- "attributionTag = " + mAttributionTag + ", " +
- "token = " + mToken + ", " +
- "next = " + mNext +
+ "uid = " + mAttributionSourceState.uid + ", " +
+ "packageName = " + mAttributionSourceState.packageName + ", " +
+ "attributionTag = " + mAttributionSourceState.attributionTag + ", " +
+ "token = " + mAttributionSourceState.token + ", " +
+ "next = " + (mAttributionSourceState.next != null
+ ? mAttributionSourceState.next[0]: null) +
" }";
}
return super.toString();
@@ -258,8 +221,8 @@ public final class AttributionSource implements Parcelable {
* @hide
*/
public int getNextUid() {
- if (mNext != null) {
- return mNext.getUid();
+ if (mAttributionSourceState.next != null) {
+ return mAttributionSourceState.next[0].uid;
}
return Process.INVALID_UID;
}
@@ -270,8 +233,8 @@ public final class AttributionSource implements Parcelable {
* @hide
*/
public @Nullable String getNextPackageName() {
- if (mNext != null) {
- return mNext.getPackageName();
+ if (mAttributionSourceState.next != null) {
+ return mAttributionSourceState.next[0].packageName;
}
return null;
}
@@ -283,8 +246,8 @@ public final class AttributionSource implements Parcelable {
* @hide
*/
public @Nullable String getNextAttributionTag() {
- if (mNext != null) {
- return mNext.getAttributionTag();
+ if (mAttributionSourceState.next != null) {
+ return mAttributionSourceState.next[0].attributionTag;
}
return null;
}
@@ -297,8 +260,9 @@ public final class AttributionSource implements Parcelable {
* @return Whether this is a trusted source.
*/
public boolean isTrusted(@NonNull Context context) {
- return mToken != null && context.getSystemService(PermissionManager.class)
- .isRegisteredAttributionSource(this);
+ return mAttributionSourceState.token != null
+ && context.getSystemService(PermissionManager.class)
+ .isRegisteredAttributionSource(this);
}
/**
@@ -310,71 +274,36 @@ public final class AttributionSource implements Parcelable {
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
@NonNull
public Set<String> getRenouncedPermissions() {
- return CollectionUtils.emptyIfNull(mRenouncedPermissions);
- }
-
- @DataClass.Suppress({"setUid", "setToken"})
- static class BaseBuilder {}
-
-
-
-
-
-
- // Code below generated by codegen v1.0.22.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/AttributionSource.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- /* package-private */ AttributionSource(
- int uid,
- @Nullable String packageName,
- @Nullable String attributionTag,
- @Nullable IBinder token,
- @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) @Nullable Set<String> renouncedPermissions,
- @Nullable AttributionSource next) {
- this.mUid = uid;
- this.mPackageName = packageName;
- this.mAttributionTag = attributionTag;
- this.mToken = token;
- this.mRenouncedPermissions = renouncedPermissions;
- com.android.internal.util.AnnotationValidations.validate(
- SystemApi.class, null, mRenouncedPermissions);
- com.android.internal.util.AnnotationValidations.validate(
- RequiresPermission.class, null, mRenouncedPermissions,
- "value", android.Manifest.permission.RENOUNCE_PERMISSIONS);
- this.mNext = next;
-
- // onConstructed(); // You can define this method to get a callback
+ if (mRenouncedPermissionsCached == null) {
+ if (mAttributionSourceState.renouncedPermissions != null) {
+ mRenouncedPermissionsCached = new ArraySet<>(
+ mAttributionSourceState.renouncedPermissions);
+ } else {
+ mRenouncedPermissionsCached = Collections.emptySet();
+ }
+ }
+ return mRenouncedPermissionsCached;
}
/**
* The UID that is accessing the permission protected data.
*/
public int getUid() {
- return mUid;
+ return mAttributionSourceState.uid;
}
/**
* The package that is accessing the permission protected data.
*/
public @Nullable String getPackageName() {
- return mPackageName;
+ return mAttributionSourceState.packageName;
}
/**
* The attribution tag of the app accessing the permission protected data.
*/
public @Nullable String getAttributionTag() {
- return mAttributionTag;
+ return mAttributionSourceState.attributionTag;
}
/**
@@ -383,113 +312,56 @@ public final class AttributionSource implements Parcelable {
* @hide
*/
public @Nullable IBinder getToken() {
- return mToken;
+ return mAttributionSourceState.token;
}
/**
* The next app to receive the permission protected data.
*/
public @Nullable AttributionSource getNext() {
- return mNext;
+ if (mNextCached == null && mAttributionSourceState.next != null) {
+ mNextCached = new AttributionSource(mAttributionSourceState.next[0]);
+ }
+ return mNextCached;
}
@Override
public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(AttributionSource other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
AttributionSource that = (AttributionSource) o;
- //noinspection PointlessBooleanExpression
- return true
- && mUid == that.mUid
- && Objects.equals(mPackageName, that.mPackageName)
- && Objects.equals(mAttributionTag, that.mAttributionTag)
- && Objects.equals(mToken, that.mToken)
- && Objects.equals(mRenouncedPermissions, that.mRenouncedPermissions)
- && Objects.equals(mNext, that.mNext);
+ return mAttributionSourceState.uid == that.mAttributionSourceState.uid
+ && Objects.equals(mAttributionSourceState.packageName,
+ that.mAttributionSourceState.packageName)
+ && Objects.equals(mAttributionSourceState.attributionTag,
+ that.mAttributionSourceState.attributionTag)
+ && Objects.equals(mAttributionSourceState.token,
+ that.mAttributionSourceState.token)
+ && Arrays.equals(mAttributionSourceState.renouncedPermissions,
+ that.mAttributionSourceState.renouncedPermissions)
+ && Objects.equals(getNext(), that.getNext());
}
@Override
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
int _hash = 1;
- _hash = 31 * _hash + mUid;
- _hash = 31 * _hash + Objects.hashCode(mPackageName);
- _hash = 31 * _hash + Objects.hashCode(mAttributionTag);
- _hash = 31 * _hash + Objects.hashCode(mToken);
- _hash = 31 * _hash + Objects.hashCode(mRenouncedPermissions);
- _hash = 31 * _hash + Objects.hashCode(mNext);
+ _hash = 31 * _hash + mAttributionSourceState.uid;
+ _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.packageName);
+ _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.attributionTag);
+ _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.token);
+ _hash = 31 * _hash + Objects.hashCode(mAttributionSourceState.renouncedPermissions);
+ _hash = 31 * _hash + Objects.hashCode(getNext());
return _hash;
}
- static Parcelling<Set<String>> sParcellingForRenouncedPermissions =
- Parcelling.Cache.get(
- RenouncedPermissionsParcelling.class);
- static {
- if (sParcellingForRenouncedPermissions == null) {
- sParcellingForRenouncedPermissions = Parcelling.Cache.put(
- new RenouncedPermissionsParcelling());
- }
- }
-
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- byte flg = 0;
- if (mPackageName != null) flg |= 0x2;
- if (mAttributionTag != null) flg |= 0x4;
- if (mToken != null) flg |= 0x8;
- if (mRenouncedPermissions != null) flg |= 0x10;
- if (mNext != null) flg |= 0x20;
- dest.writeByte(flg);
- dest.writeInt(mUid);
- if (mPackageName != null) dest.writeString(mPackageName);
- if (mAttributionTag != null) dest.writeString(mAttributionTag);
- if (mToken != null) dest.writeStrongBinder(mToken);
- sParcellingForRenouncedPermissions.parcel(mRenouncedPermissions, dest, flags);
- if (mNext != null) dest.writeTypedObject(mNext, flags);
+ mAttributionSourceState.writeToParcel(dest, flags);
}
@Override
public int describeContents() { return 0; }
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- /* package-private */ AttributionSource(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- int uid = in.readInt();
- String packageName = (flg & 0x2) == 0 ? null : in.readString();
- String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
- IBinder token = (flg & 0x8) == 0 ? null : in.readStrongBinder();
- Set<String> renouncedPermissions = sParcellingForRenouncedPermissions.unparcel(in);
- AttributionSource next = (flg & 0x20) == 0 ? null : (AttributionSource) in.readTypedObject(AttributionSource.CREATOR);
-
- this.mUid = uid;
- this.mPackageName = packageName;
- this.mAttributionTag = attributionTag;
- this.mToken = token;
- this.mRenouncedPermissions = renouncedPermissions;
- com.android.internal.util.AnnotationValidations.validate(
- SystemApi.class, null, mRenouncedPermissions);
- com.android.internal.util.AnnotationValidations.validate(
- RequiresPermission.class, null, mRenouncedPermissions,
- "value", android.Manifest.permission.RENOUNCE_PERMISSIONS);
- this.mNext = next;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR
= new Parcelable.Creator<AttributionSource>() {
@Override
@@ -506,15 +378,9 @@ public final class AttributionSource implements Parcelable {
/**
* A builder for {@link AttributionSource}
*/
- @SuppressWarnings("WeakerAccess")
- public static final class Builder extends BaseBuilder {
-
- private int mUid;
- private @Nullable String mPackageName;
- private @Nullable String mAttributionTag;
- private @Nullable IBinder mToken;
- private @Nullable Set<String> mRenouncedPermissions;
- private @Nullable AttributionSource mNext;
+ public static final class Builder {
+ private @NonNull final AttributionSourceState mAttributionSourceState =
+ new AttributionSourceState();
private long mBuilderFieldsSet = 0L;
@@ -524,9 +390,8 @@ public final class AttributionSource implements Parcelable {
* @param uid
* The UID that is accessing the permission protected data.
*/
- public Builder(
- int uid) {
- mUid = uid;
+ public Builder(int uid) {
+ mAttributionSourceState.uid = uid;
}
/**
@@ -535,7 +400,7 @@ public final class AttributionSource implements Parcelable {
public @NonNull Builder setPackageName(@Nullable String value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
- mPackageName = value;
+ mAttributionSourceState.packageName = value;
return this;
}
@@ -545,7 +410,7 @@ public final class AttributionSource implements Parcelable {
public @NonNull Builder setAttributionTag(@Nullable String value) {
checkNotUsed();
mBuilderFieldsSet |= 0x4;
- mAttributionTag = value;
+ mAttributionSourceState.attributionTag = value;
return this;
}
@@ -578,7 +443,8 @@ public final class AttributionSource implements Parcelable {
public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
checkNotUsed();
mBuilderFieldsSet |= 0x10;
- mRenouncedPermissions = value;
+ mAttributionSourceState.renouncedPermissions = (value != null)
+ ? value.toArray(new String[0]) : null;
return this;
}
@@ -588,7 +454,8 @@ public final class AttributionSource implements Parcelable {
public @NonNull Builder setNext(@Nullable AttributionSource value) {
checkNotUsed();
mBuilderFieldsSet |= 0x20;
- mNext = value;
+ mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
+ {value.mAttributionSourceState} : null;
return this;
}
@@ -598,28 +465,21 @@ public final class AttributionSource implements Parcelable {
mBuilderFieldsSet |= 0x40; // Mark builder used
if ((mBuilderFieldsSet & 0x2) == 0) {
- mPackageName = null;
+ mAttributionSourceState.packageName = null;
}
if ((mBuilderFieldsSet & 0x4) == 0) {
- mAttributionTag = null;
+ mAttributionSourceState.attributionTag = null;
}
if ((mBuilderFieldsSet & 0x8) == 0) {
- mToken = null;
+ mAttributionSourceState.token = null;
}
if ((mBuilderFieldsSet & 0x10) == 0) {
- mRenouncedPermissions = null;
+ mAttributionSourceState.renouncedPermissions = null;
}
if ((mBuilderFieldsSet & 0x20) == 0) {
- mNext = null;
+ mAttributionSourceState.next = null;
}
- AttributionSource o = new AttributionSource(
- mUid,
- mPackageName,
- mAttributionTag,
- mToken,
- mRenouncedPermissions,
- mNext);
- return o;
+ return new AttributionSource(mAttributionSourceState);
}
private void checkNotUsed() {
@@ -629,9 +489,4 @@ public final class AttributionSource implements Parcelable {
}
}
}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 5089f30585b4..66e088359459 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,21 +16,19 @@
package android.content;
-import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
import android.os.Binder;
+import android.os.IBinder;
import android.os.Process;
-import android.util.Slog;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.permission.IPermissionChecker;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
/**
* This class provides permission check APIs that verify both the
@@ -72,34 +70,44 @@ import java.util.concurrent.ConcurrentHashMap;
* @hide
*/
public final class PermissionChecker {
- private static final String LOG_TAG = PermissionChecker.class.getName();
-
- private static final String PLATFORM_PACKAGE_NAME = "android";
-
- /** The permission is granted. */
- public static final int PERMISSION_GRANTED = AppOpsManager.MODE_ALLOWED;
+ /**
+ * The permission is granted.
+ *
+ * @hide
+ */
+ public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
- /** Only for runtime permissions, its returned when the runtime permission
- * is granted, but the corresponding app op is denied. */
- public static final int PERMISSION_SOFT_DENIED = AppOpsManager.MODE_IGNORED;
+ /**
+ * The permission is denied. Applicable only to runtime and app op permissions.
+ *
+ * <p>Returned when:
+ * <ul>
+ * <li>the runtime permission is granted, but the corresponding app op is denied
+ * for runtime permissions.</li>
+ * <li>the app ops is ignored for app op permissions.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
- /** Returned when:
+ /**
+ * The permission is denied.
+ *
+ * <p>Returned when:
* <ul>
- * <li>For non app op permissions, returned when the permission is denied.</li>
- * <li>For app op permissions, returned when the app op is denied or app op is
- * {@link AppOpsManager#MODE_DEFAULT} and permission is denied.</li>
+ * <li>the permission is denied for non app op permissions.</li>
+ * <li>the app op is denied or app op is {@link AppOpsManager#MODE_DEFAULT}
+ * and permission is denied.</li>
* </ul>
*
+ * @hide
*/
- public static final int PERMISSION_HARD_DENIED = AppOpsManager.MODE_ERRORED;
+ public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED;
/** Constant when the PID for which we check permissions is unknown. */
public static final int PID_UNKNOWN = -1;
- // Cache for platform defined runtime permissions to avoid multi lookup (name -> info)
- private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
- = new ConcurrentHashMap<>();
-
/** @hide */
@IntDef({PERMISSION_GRANTED,
PERMISSION_SOFT_DENIED,
@@ -107,6 +115,8 @@ public final class PermissionChecker {
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionResult {}
+ private static volatile IPermissionChecker sService;
+
private PermissionChecker() {
/* do nothing */
}
@@ -232,7 +242,7 @@ public final class PermissionChecker {
public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context,
@NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
+ return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
message, false /*startDataDelivery*/, /*fromDatasource*/ true);
}
@@ -307,21 +317,23 @@ public final class PermissionChecker {
public static int checkPermissionForDataDelivery(@NonNull Context context,
@NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean startDataDelivery) {
- return checkPermissionForDataDeliveryCommon(context, permission, pid, attributionSource,
+ return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
message, startDataDelivery, /*fromDatasource*/ false);
}
private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
- @NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
+ @NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
// If the check failed in the middle of the chain, finish any started op.
- final int result = checkPermissionCommon(context, permission, attributionSource,
- message, true /*forDataDelivery*/, startDataDelivery, fromDatasource);
- if (startDataDelivery && result != PERMISSION_GRANTED) {
- finishDataDelivery(context, AppOpsManager.permissionToOp(permission),
- attributionSource);
+ try {
+ final int result = getPermissionCheckerService().checkPermission(permission,
+ attributionSource.asState(), message, true /*forDataDelivery*/,
+ startDataDelivery, fromDatasource);
+ return result;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
- return result;
+ return PERMISSION_HARD_DENIED;
}
/**
@@ -356,9 +368,14 @@ public final class PermissionChecker {
public static int checkPermissionAndStartDataDelivery(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- return checkPermissionCommon(context, permission, attributionSource,
- message, true /*forDataDelivery*/, /*startDataDelivery*/ true,
- /*fromDatasource*/ false);
+ try {
+ return getPermissionCheckerService().checkPermission(permission,
+ attributionSource.asState(), message, true /*forDataDelivery*/,
+ /*startDataDelivery*/ true, /*fromDatasource*/ false);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PERMISSION_HARD_DENIED;
}
/**
@@ -390,13 +407,14 @@ public final class PermissionChecker {
public static int startOpForDataDelivery(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- final int result = checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
- message, true /*forDataDelivery*/, true /*startDataDelivery*/);
- // It is important to finish any started op if some step in the attribution chain failed.
- if (result != PERMISSION_GRANTED) {
- finishDataDelivery(context, opName, attributionSource);
+ try {
+ return getPermissionCheckerService().checkOp(
+ AppOpsManager.strOpToOp(opName), attributionSource.asState(), message,
+ true /*forDataDelivery*/, true /*startDataDelivery*/);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
- return result;
+ return PERMISSION_HARD_DENIED;
}
/**
@@ -412,15 +430,10 @@ public final class PermissionChecker {
*/
public static void finishDataDelivery(@NonNull Context context, @NonNull String op,
@NonNull AttributionSource attributionSource) {
- if (op == null || attributionSource.getPackageName() == null) {
- return;
- }
-
- final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- appOpsManager.finishProxyOp(op, attributionSource);
-
- if (attributionSource.getNext() != null) {
- finishDataDelivery(context, op, attributionSource.getNext());
+ try {
+ getPermissionCheckerService().finishDataDelivery(op, attributionSource.asState());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
}
@@ -456,8 +469,14 @@ public final class PermissionChecker {
public static int checkOpForPreflight(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
- message, false /*forDataDelivery*/, false /*startDataDelivery*/);
+ try {
+ return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
+ attributionSource.asState(), message, false /*forDataDelivery*/,
+ false /*startDataDelivery*/);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PERMISSION_HARD_DENIED;
}
/**
@@ -489,8 +508,14 @@ public final class PermissionChecker {
public static int checkOpForDataDelivery(@NonNull Context context,
@NonNull String opName, @NonNull AttributionSource attributionSource,
@Nullable String message) {
- return checkOp(context, AppOpsManager.strOpToOp(opName), attributionSource,
- message, true /*forDataDelivery*/, false /*startDataDelivery*/);
+ try {
+ return getPermissionCheckerService().checkOp(AppOpsManager.strOpToOp(opName),
+ attributionSource.asState(), message, true /*forDataDelivery*/,
+ false /*startDataDelivery*/);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PERMISSION_HARD_DENIED;
}
/**
@@ -561,9 +586,14 @@ public final class PermissionChecker {
@PermissionResult
public static int checkPermissionForPreflight(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource) {
- return checkPermissionCommon(context, permission, attributionSource,
- null /*message*/, false /*forDataDelivery*/, /*startDataDelivery*/ false,
- /*fromDatasource*/ false);
+ try {
+ return getPermissionCheckerService().checkPermission(permission,
+ attributionSource.asState(), null /*message*/, false /*forDataDelivery*/,
+ /*startDataDelivery*/ false, /*fromDatasource*/ false);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return PERMISSION_HARD_DENIED;
}
/**
@@ -798,356 +828,12 @@ public final class PermissionChecker {
Binder.getCallingUid(), packageName);
}
- @PermissionResult
- private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission,
- @NonNull AttributionSource attributionSource,
- @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
- boolean fromDatasource) {
- PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
-
- if (permissionInfo == null) {
- try {
- permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
- if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
- // Double addition due to concurrency is fine - the backing store is concurrent.
- sPlatformPermissions.put(permission, permissionInfo);
- }
- } catch (PackageManager.NameNotFoundException ignored) {
- return PERMISSION_HARD_DENIED;
- }
- }
-
- if (permissionInfo.isAppOp()) {
- return checkAppOpPermission(context, permission, attributionSource, message,
- forDataDelivery, fromDatasource);
- }
- if (permissionInfo.isRuntime()) {
- return checkRuntimePermission(context, permission, attributionSource, message,
- forDataDelivery, startDataDelivery, fromDatasource);
- }
-
- if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
- attributionSource.getRenouncedPermissions())) {
- return PERMISSION_HARD_DENIED;
- }
-
- if (attributionSource.getNext() != null) {
- return checkPermissionCommon(context, permission,
- attributionSource.getNext(), message, forDataDelivery,
- startDataDelivery, /*fromDatasource*/ false);
- }
-
- return PERMISSION_GRANTED;
- }
-
- @PermissionResult
- private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission,
- @NonNull AttributionSource attributionSource, @Nullable String message,
- boolean forDataDelivery, boolean fromDatasource) {
- final int op = AppOpsManager.permissionToOpCode(permission);
- if (op < 0) {
- Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!");
- return PERMISSION_HARD_DENIED;
- }
-
- AttributionSource current = attributionSource;
- AttributionSource next = null;
-
- while (true) {
- final boolean skipCurrentChecks = (fromDatasource || next != null);
-
- next = current.getNext();
-
- // If the call is from a datasource we need to vet only the chain before it. This
- // way we can avoid the datasource creating an attribution context for every call.
- if (!(fromDatasource && current == attributionSource)
- && next != null && !current.isTrusted(context)) {
- return PERMISSION_HARD_DENIED;
- }
-
- // The access is for oneself if this is the single receiver of data
- // after the data source or if this is the single attribution source
- // in the chain if not from a datasource.
- final boolean singleReceiverFromDatasource = (fromDatasource
- && current == attributionSource && next != null && next.getNext() == null);
- final boolean selfAccess = singleReceiverFromDatasource || next == null;
-
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
- selfAccess, singleReceiverFromDatasource);
-
- switch (opMode) {
- case AppOpsManager.MODE_IGNORED:
- case AppOpsManager.MODE_ERRORED: {
- return PERMISSION_HARD_DENIED;
- }
- case AppOpsManager.MODE_DEFAULT: {
- if (!skipCurrentChecks && !checkPermission(context, permission,
- attributionSource.getUid(), attributionSource
- .getRenouncedPermissions())) {
- return PERMISSION_HARD_DENIED;
- }
- if (next != null && !checkPermission(context, permission,
- next.getUid(), next.getRenouncedPermissions())) {
- return PERMISSION_HARD_DENIED;
- }
- }
- }
-
- if (next == null || next.getNext() == null) {
- return PERMISSION_GRANTED;
- }
-
- current = next;
- }
- }
-
- private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission,
- @NonNull AttributionSource attributionSource, @Nullable String message,
- boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
- // Now let's check the identity chain...
- final int op = AppOpsManager.permissionToOpCode(permission);
-
- AttributionSource current = attributionSource;
- AttributionSource next = null;
-
- while (true) {
- final boolean skipCurrentChecks = (fromDatasource || next != null);
- next = current.getNext();
-
- // If the call is from a datasource we need to vet only the chain before it. This
- // way we can avoid the datasource creating an attribution context for every call.
- if (!(fromDatasource && current == attributionSource)
- && next != null && !current.isTrusted(context)) {
- return PERMISSION_HARD_DENIED;
- }
-
- // If we already checked the permission for this one, skip the work
- if (!skipCurrentChecks && !checkPermission(context, permission,
- current.getUid(), current.getRenouncedPermissions())) {
- return PERMISSION_HARD_DENIED;
- }
-
- if (next != null && !checkPermission(context, permission,
- next.getUid(), next.getRenouncedPermissions())) {
- return PERMISSION_HARD_DENIED;
- }
-
- if (op < 0) {
- // Bg location is one-off runtime modifier permission and has no app op
- if (sPlatformPermissions.contains(permission)
- && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
- Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
- + " with no app op defined!");
- }
- if (next == null) {
- return PERMISSION_GRANTED;
- }
- current = next;
- continue;
- }
-
- // The access is for oneself if this is the single receiver of data
- // after the data source or if this is the single attribution source
- // in the chain if not from a datasource.
- final boolean singleReceiverFromDatasource = (fromDatasource
- && current == attributionSource && next != null && next.getNext() == null);
- final boolean selfAccess = singleReceiverFromDatasource || next == null;
-
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
- singleReceiverFromDatasource);
-
- switch (opMode) {
- case AppOpsManager.MODE_ERRORED: {
- return PERMISSION_HARD_DENIED;
- }
- case AppOpsManager.MODE_IGNORED: {
- return PERMISSION_SOFT_DENIED;
- }
- }
-
- if (next == null || next.getNext() == null) {
- return PERMISSION_GRANTED;
- }
-
- current = next;
- }
- }
-
- private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
- int uid, @NonNull Set<String> renouncedPermissions) {
- final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
- uid) == PackageManager.PERMISSION_GRANTED;
- if (permissionGranted && renouncedPermissions.contains(permission)
- && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
- /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- return permissionGranted;
- }
-
- private static int checkOp(@NonNull Context context, @NonNull int op,
- @NonNull AttributionSource attributionSource, @Nullable String message,
- boolean forDataDelivery, boolean startDataDelivery) {
- if (op < 0 || attributionSource.getPackageName() == null) {
- return PERMISSION_HARD_DENIED;
- }
-
- AttributionSource current = attributionSource;
- AttributionSource next = null;
-
- while (true) {
- final boolean skipCurrentChecks = (next != null);
- next = current.getNext();
-
- // If the call is from a datasource we need to vet only the chain before it. This
- // way we can avoid the datasource creating an attribution context for every call.
- if (next != null && !current.isTrusted(context)) {
- return PERMISSION_HARD_DENIED;
- }
-
- // The access is for oneself if this is the single attribution source in the chain.
- final boolean selfAccess = (next == null);
-
- final int opMode = performOpTransaction(context, op, current, message,
- forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
- /*fromDatasource*/ false);
-
- switch (opMode) {
- case AppOpsManager.MODE_ERRORED: {
- return PERMISSION_HARD_DENIED;
- }
- case AppOpsManager.MODE_IGNORED: {
- return PERMISSION_SOFT_DENIED;
- }
- }
-
- if (next == null || next.getNext() == null) {
- return PERMISSION_GRANTED;
- }
-
- current = next;
- }
- }
-
- private static int performOpTransaction(@NonNull Context context, int op,
- @NonNull AttributionSource attributionSource, @Nullable String message,
- boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
- boolean selfAccess, boolean singleReceiverFromDatasource) {
- // We cannot perform app ops transactions without a package name. In all relevant
- // places we pass the package name but just in case there is a bug somewhere we
- // do a best effort to resolve the package from the UID (pick first without a loss
- // of generality - they are in the same security sandbox).
- final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- final AttributionSource accessorSource = (!singleReceiverFromDatasource)
- ? attributionSource : attributionSource.getNext();
- if (!forDataDelivery) {
- final String resolvedAccessorPackageName = resolvePackageName(context, accessorSource);
- if (resolvedAccessorPackageName == null) {
- return AppOpsManager.MODE_ERRORED;
- }
- final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
- accessorSource.getUid(), resolvedAccessorPackageName);
- final AttributionSource next = accessorSource.getNext();
- if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) {
- final String resolvedNextPackageName = resolvePackageName(context, next);
- if (resolvedNextPackageName == null) {
- return AppOpsManager.MODE_ERRORED;
- }
- return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
- resolvedNextPackageName);
- }
- return opMode;
- } else if (startDataDelivery) {
- final AttributionSource resolvedAttributionSource = resolveAttributionSource(
- context, accessorSource);
- if (resolvedAttributionSource.getPackageName() == null) {
- return AppOpsManager.MODE_ERRORED;
- }
- if (selfAccess) {
- // If the datasource is not in a trusted platform component then in would not
- // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
- // an app is exposing runtime permission protected data but cannot blame others
- // in a trusted way which would not properly show in permission usage UIs.
- // As a fallback we note a proxy op that blames the app and the datasource.
- try {
- return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
- resolvedAttributionSource.getPackageName(),
- /*startIfModeDefault*/ false,
- resolvedAttributionSource.getAttributionTag(),
- message);
- } catch (SecurityException e) {
- Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
- + " platform defined runtime permission "
- + AppOpsManager.opToPermission(op) + " while not having "
- + Manifest.permission.UPDATE_APP_OPS_STATS);
- return appOpsManager.startProxyOpNoThrow(op, attributionSource, message,
- skipProxyOperation);
- }
- } else {
- return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
- skipProxyOperation);
- }
- } else {
- final AttributionSource resolvedAttributionSource = resolveAttributionSource(
- context, accessorSource);
- if (resolvedAttributionSource.getPackageName() == null) {
- return AppOpsManager.MODE_ERRORED;
- }
- if (selfAccess) {
- // If the datasource is not in a trusted platform component then in would not
- // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
- // an app is exposing runtime permission protected data but cannot blame others
- // in a trusted way which would not properly show in permission usage UIs.
- // As a fallback we note a proxy op that blames the app and the datasource.
- try {
- return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
- resolvedAttributionSource.getPackageName(),
- resolvedAttributionSource.getAttributionTag(),
- message);
- } catch (SecurityException e) {
- Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
- + " platform defined runtime permission "
- + AppOpsManager.opToPermission(op) + " while not having "
- + Manifest.permission.UPDATE_APP_OPS_STATS);
- return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
- skipProxyOperation);
- }
- } else {
- return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
- skipProxyOperation);
- }
- }
- }
-
- private static @Nullable String resolvePackageName(@NonNull Context context,
- @NonNull AttributionSource attributionSource) {
- if (attributionSource.getPackageName() != null) {
- return attributionSource.getPackageName();
- }
- final String[] packageNames = context.getPackageManager().getPackagesForUid(
- attributionSource.getUid());
- if (packageNames != null) {
- // This is best effort if the caller doesn't pass a package. The security
- // sandbox is UID, therefore we pick an arbitrary package.
- return packageNames[0];
- }
- // Last resort to handle special UIDs like root, etc.
- return AppOpsManager.resolvePackageName(attributionSource.getUid(),
- attributionSource.getPackageName());
- }
-
- private static @NonNull AttributionSource resolveAttributionSource(
- @NonNull Context context, @NonNull AttributionSource attributionSource) {
- if (attributionSource.getPackageName() != null) {
- return attributionSource;
+ private static @NonNull IPermissionChecker getPermissionCheckerService() {
+ // Race is fine, we may end up looking up the same instance twice, no big deal.
+ if (sService == null) {
+ final IBinder service = ServiceManager.getService("permission_checker");
+ sService = IPermissionChecker.Stub.asInterface(service);
}
- return new AttributionSource(attributionSource.getUid(),
- resolvePackageName(context, attributionSource),
- attributionSource.getAttributionTag(),
- attributionSource.getToken(),
- attributionSource.getRenouncedPermissions(),
- attributionSource.getNext());
+ return sService;
}
}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index d026e959905c..2a9b703583a6 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -560,9 +560,6 @@ public final class BinderProxy implements IBinder {
}
}
- final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
- AppOpsManager.pauseNotedAppOpsCollection();
-
if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isListeningForOpNoted()) {
flags |= FLAG_COLLECT_NOTED_APP_OPS;
}
@@ -571,8 +568,6 @@ public final class BinderProxy implements IBinder {
boolean replyOwnsNative = (reply == null) ? false : reply.ownsNativeParcelObject();
return transactNative(code, data, reply, replyOwnsNative, flags);
} finally {
- AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
-
if (transactListener != null) {
transactListener.onTransactEnded(session);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7eaf18fc971b..c73593ff009c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3085,7 +3085,8 @@ public class AppOpsService extends IAppOpsService.Stub {
String resolveProxyPackageName = AppOpsManager.resolvePackageName(proxyUid,
proxyPackageName);
if (resolveProxyPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code,
+ proxiedAttributionTag, proxiedPackageName);
}
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
@@ -3101,14 +3102,16 @@ public class AppOpsService extends IAppOpsService.Stub {
resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
- return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag);
+ return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
+ proxiedPackageName);
}
}
String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
proxiedPackageName);
if (resolveProxiedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
}
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
@@ -3135,7 +3138,8 @@ public class AppOpsService extends IAppOpsService.Stub {
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
}
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
@@ -3152,7 +3156,8 @@ public class AppOpsService extends IAppOpsService.Stub {
bypass = verifyAndGetBypass(uid, packageName, attributionTag);
} catch (SecurityException e) {
Slog.e(TAG, "noteOperation", e);
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
}
synchronized (this) {
@@ -3163,7 +3168,8 @@ public class AppOpsService extends IAppOpsService.Stub {
AppOpsManager.MODE_IGNORED);
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName);
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
@@ -3179,7 +3185,8 @@ public class AppOpsService extends IAppOpsService.Stub {
attributedOp.rejected(uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
AppOpsManager.MODE_IGNORED);
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
}
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
@@ -3192,7 +3199,7 @@ public class AppOpsService extends IAppOpsService.Stub {
attributedOp.rejected(uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
uidMode);
- return new SyncNotedAppOp(uidMode, code, attributionTag);
+ return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
@@ -3205,7 +3212,7 @@ public class AppOpsService extends IAppOpsService.Stub {
attributedOp.rejected(uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
mode);
- return new SyncNotedAppOp(mode, code, attributionTag);
+ return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
if (DEBUG) {
@@ -3224,7 +3231,8 @@ public class AppOpsService extends IAppOpsService.Stub {
shouldCollectMessage);
}
- return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
+ packageName);
}
}
@@ -3528,7 +3536,8 @@ public class AppOpsService extends IAppOpsService.Stub {
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
}
// As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
@@ -3539,7 +3548,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (code == OP_RECORD_AUDIO_HOTWORD) {
int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
if (result != AppOpsManager.MODE_ALLOWED) {
- return new SyncNotedAppOp(result, code, attributionTag);
+ return new SyncNotedAppOp(result, code, attributionTag, packageName);
}
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
@@ -3578,7 +3587,8 @@ public class AppOpsService extends IAppOpsService.Stub {
String resolvedProxyPackageName = AppOpsManager.resolvePackageName(proxyUid,
proxyPackageName);
if (resolvedProxyPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
}
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
@@ -3589,7 +3599,8 @@ public class AppOpsService extends IAppOpsService.Stub {
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
proxiedPackageName);
if (resolvedProxiedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
+ proxiedPackageName);
}
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
@@ -3637,7 +3648,8 @@ public class AppOpsService extends IAppOpsService.Stub {
bypass = verifyAndGetBypass(uid, packageName, attributionTag);
} catch (SecurityException e) {
Slog.e(TAG, "startOperation", e);
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
}
synchronized (this) {
@@ -3649,7 +3661,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName);
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
@@ -3657,7 +3670,8 @@ public class AppOpsService extends IAppOpsService.Stub {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, AppOpsManager.MODE_IGNORED);
}
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
}
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
@@ -3678,7 +3692,7 @@ public class AppOpsService extends IAppOpsService.Stub {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, uidMode);
}
- return new SyncNotedAppOp(uidMode, code, attributionTag);
+ return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
@@ -3694,7 +3708,7 @@ public class AppOpsService extends IAppOpsService.Stub {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, mode);
}
- return new SyncNotedAppOp(mode, code, attributionTag);
+ return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
@@ -3716,7 +3730,8 @@ public class AppOpsService extends IAppOpsService.Stub {
message, shouldCollectMessage);
}
- return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag);
+ return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
+ packageName);
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 884bbea2eb28..b3fa98e810cd 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -70,7 +70,9 @@ import android.app.admin.DevicePolicyManagerInternal;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
+import android.content.AttributionSourceState;
import android.content.Context;
+import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
@@ -104,6 +106,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.permission.IOnPermissionsChangeListener;
+import android.permission.IPermissionChecker;
import android.permission.IPermissionManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
@@ -164,6 +167,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -451,6 +455,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (permissionService == null) {
permissionService = new PermissionManagerService(context, availableFeatures);
ServiceManager.addService("permissionmgr", permissionService);
+ ServiceManager.addService("permission_checker", new PermissionCheckerService(context));
}
return LocalServices.getService(PermissionManagerServiceInternal.class);
}
@@ -5448,4 +5453,419 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
}
+
+ /**
+ * TODO: We need to consolidate these APIs either on PermissionManager or an extension
+ * object or a separate PermissionChecker service in context. The impartant part is to
+ * keep a single impl that is exposed to Java and native. We are not sure about the
+ * API shape so let is soak a bit.
+ */
+ private static final class PermissionCheckerService extends IPermissionChecker.Stub {
+ // Cache for platform defined runtime permissions to avoid multi lookup (name -> info)
+ private static final ConcurrentHashMap<String, PermissionInfo> sPlatformPermissions
+ = new ConcurrentHashMap<>();
+
+ private final @NonNull Context mContext;
+ private final @NonNull AppOpsManager mAppOpsManager;
+
+ PermissionCheckerService(@NonNull Context context) {
+ mContext = context;
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ }
+
+ @Override
+ @PermissionChecker.PermissionResult
+ public int checkPermission(@NonNull String permission,
+ @NonNull AttributionSourceState attributionSourceState, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+ Objects.requireNonNull(permission);
+ Objects.requireNonNull(attributionSourceState);
+ final AttributionSource attributionSource = new AttributionSource(
+ attributionSourceState);
+ final int result = checkPermission(mContext, permission, attributionSource, message,
+ forDataDelivery, startDataDelivery, fromDatasource);
+ // Finish any started op if some step in the attribution chain failed.
+ if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+ finishDataDelivery(AppOpsManager.permissionToOp(permission),
+ attributionSource.asState());
+ }
+ return result;
+ }
+
+ @Override
+ public void finishDataDelivery(@NonNull String op,
+ @NonNull AttributionSourceState attributionSourceState) {
+ if (op == null || attributionSourceState.packageName == null) {
+ return;
+ }
+ mAppOpsManager.finishProxyOp(op, new AttributionSource(attributionSourceState));
+ if (attributionSourceState.next != null) {
+ finishDataDelivery(op, attributionSourceState.next[0]);
+ }
+ }
+
+ @Override
+ @PermissionChecker.PermissionResult
+ public int checkOp(int op, AttributionSourceState attributionSource,
+ String message, boolean forDataDelivery, boolean startDataDelivery) {
+ int result = checkOp(mContext, op, new AttributionSource(attributionSource), message,
+ forDataDelivery, startDataDelivery);
+ if (result != PermissionChecker.PERMISSION_GRANTED && startDataDelivery) {
+ // Finish any started op if some step in the attribution chain failed.
+ finishDataDelivery(AppOpsManager.opToName(op), attributionSource);
+ }
+ return result;
+ }
+
+ @PermissionChecker.PermissionResult
+ private static int checkPermission(@NonNull Context context, @NonNull String permission,
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource) {
+ PermissionInfo permissionInfo = sPlatformPermissions.get(permission);
+
+ if (permissionInfo == null) {
+ try {
+ permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+ if (PLATFORM_PACKAGE_NAME.equals(permissionInfo.packageName)) {
+ // Double addition due to concurrency is fine - the backing
+ // store is concurrent.
+ sPlatformPermissions.put(permission, permissionInfo);
+ }
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+ }
+
+ if (permissionInfo.isAppOp()) {
+ return checkAppOpPermission(context, permission, attributionSource, message,
+ forDataDelivery, fromDatasource);
+ }
+ if (permissionInfo.isRuntime()) {
+ return checkRuntimePermission(context, permission, attributionSource, message,
+ forDataDelivery, startDataDelivery, fromDatasource);
+ }
+
+ if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(),
+ attributionSource.getRenouncedPermissions())) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ if (attributionSource.getNext() != null) {
+ return checkPermission(context, permission,
+ attributionSource.getNext(), message, forDataDelivery,
+ startDataDelivery, /*fromDatasource*/ false);
+ }
+
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ @PermissionChecker.PermissionResult
+ private static int checkAppOpPermission(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean forDataDelivery, boolean fromDatasource) {
+ final int op = AppOpsManager.permissionToOpCode(permission);
+ if (op < 0) {
+ Slog.wtf(LOG_TAG, "Appop permission " + permission + " with no app op defined!");
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentChecks = (fromDatasource || next != null);
+
+ next = current.getNext();
+
+ // If the call is from a datasource we need to vet only the chain before it. This
+ // way we can avoid the datasource creating an attribution context for every call.
+ if (!(fromDatasource && current == attributionSource)
+ && next != null && !current.isTrusted(context)) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ // The access is for oneself if this is the single receiver of data
+ // after the data source or if this is the single attribution source
+ // in the chain if not from a datasource.
+ final boolean singleReceiverFromDatasource = (fromDatasource
+ && current == attributionSource && next != null && next.getNext() == null);
+ final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+ final int opMode = performOpTransaction(context, op, current, message,
+ forDataDelivery, /*startDataDelivery*/ false, skipCurrentChecks,
+ selfAccess, singleReceiverFromDatasource);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_IGNORED:
+ case AppOpsManager.MODE_ERRORED: {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_DEFAULT: {
+ if (!skipCurrentChecks && !checkPermission(context, permission,
+ attributionSource.getUid(), attributionSource
+ .getRenouncedPermissions())) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+ if (next != null && !checkPermission(context, permission,
+ next.getUid(), next.getRenouncedPermissions())) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ current = next;
+ }
+ }
+
+ private static int checkRuntimePermission(@NonNull Context context,
+ @NonNull String permission, @NonNull AttributionSource attributionSource,
+ @Nullable String message, boolean forDataDelivery, boolean startDataDelivery,
+ boolean fromDatasource) {
+ // Now let's check the identity chain...
+ final int op = AppOpsManager.permissionToOpCode(permission);
+
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentChecks = (fromDatasource || next != null);
+ next = current.getNext();
+
+ // If the call is from a datasource we need to vet only the chain before it. This
+ // way we can avoid the datasource creating an attribution context for every call.
+ if (!(fromDatasource && current == attributionSource)
+ && next != null && !current.isTrusted(context)) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ // If we already checked the permission for this one, skip the work
+ if (!skipCurrentChecks && !checkPermission(context, permission,
+ current.getUid(), current.getRenouncedPermissions())) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ if (next != null && !checkPermission(context, permission,
+ next.getUid(), next.getRenouncedPermissions())) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ if (op < 0) {
+ // Bg location is one-off runtime modifier permission and has no app op
+ if (sPlatformPermissions.contains(permission)
+ && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
+ Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
+ + " with no app op defined!");
+ }
+ if (next == null) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+ current = next;
+ continue;
+ }
+
+ // The access is for oneself if this is the single receiver of data
+ // after the data source or if this is the single attribution source
+ // in the chain if not from a datasource.
+ final boolean singleReceiverFromDatasource = (fromDatasource
+ && current == attributionSource && next != null && next.getNext() == null);
+ final boolean selfAccess = singleReceiverFromDatasource || next == null;
+
+ final int opMode = performOpTransaction(context, op, current, message,
+ forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+ singleReceiverFromDatasource);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_ERRORED: {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_IGNORED: {
+ return PermissionChecker.PERMISSION_SOFT_DENIED;
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ current = next;
+ }
+ }
+
+ private static boolean checkPermission(@NonNull Context context, @NonNull String permission,
+ int uid, @NonNull Set<String> renouncedPermissions) {
+ final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
+ uid) == PackageManager.PERMISSION_GRANTED;
+ if (permissionGranted && renouncedPermissions.contains(permission)
+ && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
+ /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ return permissionGranted;
+ }
+
+ private static int checkOp(@NonNull Context context, @NonNull int op,
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery) {
+ if (op < 0 || attributionSource.getPackageName() == null) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ AttributionSource current = attributionSource;
+ AttributionSource next = null;
+
+ while (true) {
+ final boolean skipCurrentChecks = (next != null);
+ next = current.getNext();
+
+ // If the call is from a datasource we need to vet only the chain before it. This
+ // way we can avoid the datasource creating an attribution context for every call.
+ if (next != null && !current.isTrusted(context)) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ // The access is for oneself if this is the single attribution source in the chain.
+ final boolean selfAccess = (next == null);
+
+ final int opMode = performOpTransaction(context, op, current, message,
+ forDataDelivery, startDataDelivery, skipCurrentChecks, selfAccess,
+ /*fromDatasource*/ false);
+
+ switch (opMode) {
+ case AppOpsManager.MODE_ERRORED: {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+ case AppOpsManager.MODE_IGNORED: {
+ return PermissionChecker.PERMISSION_SOFT_DENIED;
+ }
+ }
+
+ if (next == null || next.getNext() == null) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ current = next;
+ }
+ }
+
+ private static int performOpTransaction(@NonNull Context context, int op,
+ @NonNull AttributionSource attributionSource, @Nullable String message,
+ boolean forDataDelivery, boolean startDataDelivery, boolean skipProxyOperation,
+ boolean selfAccess, boolean singleReceiverFromDatasource) {
+ // We cannot perform app ops transactions without a package name. In all relevant
+ // places we pass the package name but just in case there is a bug somewhere we
+ // do a best effort to resolve the package from the UID (pick first without a loss
+ // of generality - they are in the same security sandbox).
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final AttributionSource accessorSource = (!singleReceiverFromDatasource)
+ ? attributionSource : attributionSource.getNext();
+ if (!forDataDelivery) {
+ final String resolvedAccessorPackageName = resolvePackageName(context,
+ accessorSource);
+ if (resolvedAccessorPackageName == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
+ accessorSource.getUid(), resolvedAccessorPackageName);
+ final AttributionSource next = accessorSource.getNext();
+ if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) {
+ final String resolvedNextPackageName = resolvePackageName(context, next);
+ if (resolvedNextPackageName == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
+ resolvedNextPackageName);
+ }
+ return opMode;
+ } else if (startDataDelivery) {
+ final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+ context, accessorSource);
+ if (resolvedAttributionSource.getPackageName() == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ if (selfAccess) {
+ // If the datasource is not in a trusted platform component then in would not
+ // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+ // an app is exposing runtime permission protected data but cannot blame others
+ // in a trusted way which would not properly show in permission usage UIs.
+ // As a fallback we note a proxy op that blames the app and the datasource.
+ try {
+ return appOpsManager.startOpNoThrow(op, resolvedAttributionSource.getUid(),
+ resolvedAttributionSource.getPackageName(),
+ /*startIfModeDefault*/ false,
+ resolvedAttributionSource.getAttributionTag(),
+ message);
+ } catch (SecurityException e) {
+ Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+ + " platform defined runtime permission "
+ + AppOpsManager.opToPermission(op) + " while not having "
+ + Manifest.permission.UPDATE_APP_OPS_STATS);
+ return appOpsManager.startProxyOpNoThrow(op, attributionSource, message,
+ skipProxyOperation);
+ }
+ } else {
+ return appOpsManager.startProxyOpNoThrow(op, resolvedAttributionSource, message,
+ skipProxyOperation);
+ }
+ } else {
+ final AttributionSource resolvedAttributionSource = resolveAttributionSource(
+ context, accessorSource);
+ if (resolvedAttributionSource.getPackageName() == null) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ if (selfAccess) {
+ // If the datasource is not in a trusted platform component then in would not
+ // have UPDATE_APP_OPS_STATS and the call below would fail. The problem is that
+ // an app is exposing runtime permission protected data but cannot blame others
+ // in a trusted way which would not properly show in permission usage UIs.
+ // As a fallback we note a proxy op that blames the app and the datasource.
+ try {
+ return appOpsManager.noteOpNoThrow(op, resolvedAttributionSource.getUid(),
+ resolvedAttributionSource.getPackageName(),
+ resolvedAttributionSource.getAttributionTag(),
+ message);
+ } catch (SecurityException e) {
+ Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
+ + " platform defined runtime permission "
+ + AppOpsManager.opToPermission(op) + " while not having "
+ + Manifest.permission.UPDATE_APP_OPS_STATS);
+ return appOpsManager.noteProxyOpNoThrow(op, attributionSource, message,
+ skipProxyOperation);
+ }
+ } else {
+ return appOpsManager.noteProxyOpNoThrow(op, resolvedAttributionSource, message,
+ skipProxyOperation);
+ }
+ }
+ }
+
+ private static @Nullable String resolvePackageName(@NonNull Context context,
+ @NonNull AttributionSource attributionSource) {
+ if (attributionSource.getPackageName() != null) {
+ return attributionSource.getPackageName();
+ }
+ final String[] packageNames = context.getPackageManager().getPackagesForUid(
+ attributionSource.getUid());
+ if (packageNames != null) {
+ // This is best effort if the caller doesn't pass a package. The security
+ // sandbox is UID, therefore we pick an arbitrary package.
+ return packageNames[0];
+ }
+ // Last resort to handle special UIDs like root, etc.
+ return AppOpsManager.resolvePackageName(attributionSource.getUid(),
+ attributionSource.getPackageName());
+ }
+
+ private static @NonNull AttributionSource resolveAttributionSource(
+ @NonNull Context context, @NonNull AttributionSource attributionSource) {
+ if (attributionSource.getPackageName() != null) {
+ return attributionSource;
+ }
+ return attributionSource.withPackageName(resolvePackageName(context,
+ attributionSource));
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 41237c8ae51f..12e0d8bc5d62 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -168,7 +168,7 @@ public class ActivityManagerServiceTest {
private void mockNoteOperation() {
SyncNotedAppOp allowed = new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED,
- AppOpsManager.OP_GET_USAGE_STATS, null);
+ AppOpsManager.OP_GET_USAGE_STATS, null, mContext.getPackageName());
when(mAppOpsService.noteOperation(eq(AppOpsManager.OP_GET_USAGE_STATS), eq(Process.myUid()),
nullable(String.class), nullable(String.class), any(Boolean.class),
nullable(String.class), any(Boolean.class))).thenReturn(allowed);