Merge "Prepare AttributionSource to expose to native" into sc-dev
This commit is contained in:
commit
720add59c7
@ -326,6 +326,7 @@ java_defaults {
|
||||
"tv_tuner_resource_manager_aidl_interface-java",
|
||||
"soundtrigger_middleware-aidl-java",
|
||||
"modules-utils-os",
|
||||
"framework-permission-aidl-java",
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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,69 +8882,10 @@ 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.
|
||||
*
|
||||
@ -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());
|
||||
if (appOpsNotedForAttribution == null) {
|
||||
appOpsNotedForAttribution = new long[2];
|
||||
appOpsNoted.put(syncOp.getAttributionTag(), appOpsNotedForAttribution);
|
||||
ArrayMap<String, long[]> packageAppOpsNotedForAttribution = appOpsNoted.get(packageName);
|
||||
if (packageAppOpsNotedForAttribution == null) {
|
||||
packageAppOpsNotedForAttribution = new ArrayMap<>(1);
|
||||
appOpsNoted.put(packageName, packageAppOpsNotedForAttribution);
|
||||
}
|
||||
|
||||
if (op < 64) {
|
||||
appOpsNotedForAttribution[0] |= 1L << op;
|
||||
long[] appOpsNotedForAttribution = packageAppOpsNotedForAttribution.get(attributionTag);
|
||||
if (appOpsNotedForAttribution == null) {
|
||||
appOpsNotedForAttribution = new long[2];
|
||||
packageAppOpsNotedForAttribution.put(attributionTag, appOpsNotedForAttribution);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
|
||||
String attributionTag = p.readString();
|
||||
long[] rawNotedAppOps = new long[2];
|
||||
rawNotedAppOps[0] = p.readLong();
|
||||
rawNotedAppOps[1] = p.readLong();
|
||||
final String myPackageName = ActivityThread.currentPackageName();
|
||||
if (myPackageName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
|
||||
BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
|
||||
synchronized (sLock) {
|
||||
for (int i = 0; i < packageCount; i++) {
|
||||
final String packageName = p.readString();
|
||||
|
||||
synchronized (sLock) {
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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() {}
|
||||
|
||||
|
@ -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>> {
|
||||
private final @NonNull AttributionSourceState mAttributionSourceState;
|
||||
|
||||
@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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
/**
|
||||
* The permission is granted.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
|
||||
|
||||
private static final String PLATFORM_PACKAGE_NAME = "android";
|
||||
|
||||
/** The permission is granted. */
|
||||
public static final int PERMISSION_GRANTED = AppOpsManager.MODE_ALLOWED;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Returned when:
|
||||
/**
|
||||
* The permission is denied. Applicable only to runtime and app op permissions.
|
||||
*
|
||||
* <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 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_HARD_DENIED = AppOpsManager.MODE_ERRORED;
|
||||
public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
|
||||
|
||||
/**
|
||||
* The permission is denied.
|
||||
*
|
||||
* <p>Returned when:
|
||||
* <ul>
|
||||
* <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 = 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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return new AttributionSource(attributionSource.getUid(),
|
||||
resolvePackageName(context, attributionSource),
|
||||
attributionSource.getAttributionTag(),
|
||||
attributionSource.getToken(),
|
||||
attributionSource.getRenouncedPermissions(),
|
||||
attributionSource.getNext());
|
||||
return sService;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user