summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt44
-rw-r--r--core/java/android/app/AppOpsManager.java1043
-rw-r--r--core/java/android/app/AsyncNotedAppOp.aidl19
-rw-r--r--core/java/android/app/AsyncNotedAppOp.java245
-rw-r--r--core/java/android/app/SyncNotedAppOp.java68
-rw-r--r--core/java/android/content/PermissionChecker.java53
-rw-r--r--core/java/android/os/Binder.java13
-rw-r--r--core/java/android/os/BinderProxy.java10
-rw-r--r--core/java/android/os/IBinder.java5
-rw-r--r--core/java/android/os/Parcel.java21
-rw-r--r--core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl24
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl10
-rw-r--r--core/java/com/android/internal/util/AnnotationValidations.java44
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java186
14 files changed, 1545 insertions, 240 deletions
diff --git a/api/current.txt b/api/current.txt
index 6adddd064fb5..1c68aaf9620a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4279,14 +4279,21 @@ package android.app {
method public void checkPackage(int, @NonNull String);
method public void finishOp(@NonNull String, int, @NonNull String);
method public boolean isOpActive(@NonNull String, int, @NonNull String);
- method public int noteOp(@NonNull String, int, @NonNull String);
- method public int noteOpNoThrow(@NonNull String, int, @NonNull String);
- method public int noteProxyOp(@NonNull String, @NonNull String);
- method public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
- method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
+ method @Deprecated public int noteOp(@NonNull String, int, @NonNull String);
+ method public int noteOp(@NonNull String, int, @Nullable String, @Nullable String);
+ method @Deprecated public int noteOpNoThrow(@NonNull String, int, @NonNull String);
+ method public int noteOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
+ method @Deprecated public int noteProxyOp(@NonNull String, @NonNull String);
+ method public int noteProxyOp(@NonNull String, @Nullable String, int, @Nullable String);
+ method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
+ method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
+ method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int, @Nullable String);
method public static String permissionToOp(String);
- method public int startOp(@NonNull String, int, @NonNull String);
- method public int startOpNoThrow(@NonNull String, int, @NonNull String);
+ method public void setNotedAppOpsCollector(@Nullable android.app.AppOpsManager.AppOpsCollector);
+ method @Deprecated public int startOp(@NonNull String, int, @NonNull String);
+ method public int startOp(@NonNull String, int, @Nullable String, @Nullable String);
+ method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
+ method public int startOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener);
method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener);
@@ -4338,6 +4345,14 @@ package android.app {
field public static final int WATCH_FOREGROUND_CHANGES = 1; // 0x1
}
+ public abstract static class AppOpsManager.AppOpsCollector {
+ ctor public AppOpsManager.AppOpsCollector();
+ method @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor();
+ method public abstract void onAsyncNoted(@NonNull android.app.AsyncNotedAppOp);
+ method public abstract void onNoted(@NonNull android.app.SyncNotedAppOp);
+ method public abstract void onSelfNoted(@NonNull android.app.SyncNotedAppOp);
+ }
+
public static interface AppOpsManager.OnOpActiveChangedListener {
method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
}
@@ -4458,6 +4473,17 @@ package android.app {
field public String serviceDetails;
}
+ public final class AsyncNotedAppOp implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getMessage();
+ method @Nullable public String getNotingPackageName();
+ method @IntRange(from=0) public int getNotingUid();
+ method @NonNull public String getOp();
+ method @IntRange(from=0) public long getTime();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AsyncNotedAppOp> CREATOR;
+ }
+
public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
ctor public AuthenticationRequiredException(Throwable, android.app.PendingIntent);
method public int describeContents();
@@ -6267,6 +6293,10 @@ package android.app {
public class StatusBarManager {
}
+ public final class SyncNotedAppOp {
+ method @NonNull public String getOp();
+ }
+
@Deprecated public class TabActivity extends android.app.ActivityGroup {
ctor @Deprecated public TabActivity();
method @Deprecated public android.widget.TabHost getTabHost();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 34045c995aed..d1be17c68dde 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -28,13 +28,19 @@ import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.usage.UsageStatsManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
+import android.database.DatabaseUtils;
import android.media.AudioAttributes.AttributeUsage;
import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
@@ -49,9 +55,12 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.ZygoteInit;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
@@ -59,10 +68,12 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -118,6 +129,12 @@ public class AppOpsManager {
private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers =
new ArrayMap<>();
+ private static final Object sLock = new Object();
+
+ /** Current {@link AppOpsCollector}. Change via {@link #setNotedAppOpsCollector} */
+ @GuardedBy("sLock")
+ private static @Nullable AppOpsCollector sNotedAppOpsCollector;
+
static IBinder sToken;
/** @hide */
@@ -1126,6 +1143,22 @@ public class AppOpsManager {
/** @hide Query all packages on device */
public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
+
+ /** {@link #sAppOpsToNote} not initialized yet for this op */
+ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
+ /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
+ private static final byte SHOULD_NOT_COLLECT_NOTE_OP = 1;
+ /** Should collect noting of this app-op in {@link #sAppOpsToNote} */
+ private static final byte SHOULD_COLLECT_NOTE_OP = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "SHOULD_" }, value = {
+ SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED,
+ SHOULD_NOT_COLLECT_NOTE_OP,
+ SHOULD_COLLECT_NOTE_OP
+ })
+ private @interface ShouldCollectNoteOp {}
+
// Warning: If an permission is added here it also has to be added to
// com.android.packageinstaller.permission.utils.EventLogger
private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
@@ -1988,6 +2021,27 @@ public class AppOpsManager {
*/
private static HashMap<String, Integer> sPermToOp = new HashMap<>();
+ /**
+ * Set to the uid of the caller if this thread is currently executing a two-way binder
+ * transaction. Not set if this thread is currently not executing a two way binder transaction.
+ *
+ * @see #startNotedAppOpsCollection
+ * @see #markAppOpNoted
+ */
+ 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.
+ *
+ * @see #markAppOpNoted
+ */
+ private static final ThreadLocal<long[]> sAppOpsNotedInThisBinderTransaction =
+ new ThreadLocal<>();
+
+ /** Whether noting for an appop should be collected */
+ private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
+
static {
if (sOpToSwitch.length != _NUM_OP) {
throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length
@@ -2031,6 +2085,12 @@ public class AppOpsManager {
sPermToOp.put(sOpPerms[op], op);
}
}
+
+ if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) {
+ // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is
+ // two longs
+ throw new IllegalStateException("notedAppOps collection code assumes < 128 appops");
+ }
}
/** @hide */
@@ -4350,8 +4410,8 @@ public class AppOpsManager {
* The mode of the ops returned are set for the package but may not reflect their effective
* state due to UID policy or because it's controlled by a different master op.
*
- * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
- * if the effective mode is needed.
+ * Use {@link #unsafeCheckOp(String, int, String)}} or
+ * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
*
* @param ops The set of operations you are interested in, or null if you want all of them.
* @hide
@@ -4374,8 +4434,8 @@ public class AppOpsManager {
* The mode of the ops returned are set for the package but may not reflect their effective
* state due to UID policy or because it's controlled by a different master op.
*
- * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
- * if the effective mode is needed.
+ * Use {@link #unsafeCheckOp(String, int, String)}} or
+ * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
*
* @param ops The set of operations you are interested in, or null if you want all of them.
* @hide
@@ -4396,8 +4456,8 @@ public class AppOpsManager {
* The mode of the ops returned are set for the package but may not reflect their effective
* state due to UID policy or because it's controlled by a different master op.
*
- * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
- * if the effective mode is needed.
+ * Use {@link #unsafeCheckOp(String, int, String)}} or
+ * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
*
* @param uid The uid of the application of interest.
* @param packageName The name of the application of interest.
@@ -4429,8 +4489,8 @@ public class AppOpsManager {
* The mode of the ops returned are set for the package but may not reflect their effective
* state due to UID policy or because it's controlled by a different master op.
*
- * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
- * if the effective mode is needed.
+ * Use {@link #unsafeCheckOp(String, int, String)}} or
+ * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
*
* @param uid The uid of the application of interest.
* @param packageName The name of the application of interest.
@@ -4820,7 +4880,7 @@ public class AppOpsManager {
*
* @see #isOperationActive
* @see #stopWatchingActive
- * @see #startOp(int, int, String)
+ * @see #startOp(int, int, String, boolean, String)
* @see #finishOp(int, int, String)
*/
// TODO: Uncomment below annotation once b/73559440 is fixed
@@ -4871,7 +4931,7 @@ public class AppOpsManager {
*
* @see #isOperationActive
* @see #startWatchingActive
- * @see #startOp(int, int, String)
+ * @see #startOp(int, int, String, boolean, String)
* @see #finishOp(int, int, String)
*/
public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) {
@@ -4902,7 +4962,7 @@ public class AppOpsManager {
*
* @see #startWatchingActive(int[], OnOpActiveChangedListener)
* @see #stopWatchingNoted(OnOpNotedListener)
- * @see #noteOp(String, int, String)
+ * @see #noteOp(String, int, String, String)
*
* @hide
*/
@@ -4934,7 +4994,7 @@ public class AppOpsManager {
* Unregistering a non-registered callback has no effect.
*
* @see #startWatchingNoted(int[], OnOpNotedListener)
- * @see #noteOp(String, int, String)
+ * @see #noteOp(String, int, String, String)
*
* @hide
*/
@@ -4969,15 +5029,16 @@ public class AppOpsManager {
/**
* Do a quick check for whether an application might be able to perform an operation.
- * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String)}
- * or {@link #startOp(String, int, String)} for your actual security checks, which also
- * ensure that the given uid and package name are consistent. This function can just be
- * used for a quick check to see if an operation has been disabled for the application,
+ * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
+ * String)} or {@link #startOp(String, int, String, String)} for your actual security checks,
+ * which also ensure that the given uid and package name are consistent. This function can just
+ * be used for a quick check to see if an operation has been disabled for the application,
* as an early reject of some work. This does not modify the time stamp or other data
* about the operation.
*
* <p>Important things this will not do (which you need to ultimate use
- * {@link #noteOp(String, int, String)} or {@link #startOp(String, int, String)} to cover):</p>
+ * {@link #noteOp(String, int, String, String)} or
+ * {@link #startOp(String, int, String, String)} to cover):</p>
* <ul>
* <li>Verifying the uid and package are consistent, so callers can't spoof
* their identity.</li>
@@ -5048,126 +5109,305 @@ public class AppOpsManager {
}
/**
+ * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+ */
+ @Deprecated
+ public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
+ return noteOp(op, uid, packageName, null);
+ }
+
+ /**
+ * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "#noteOp(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+ @Deprecated
+ public int noteOp(int op) {
+ return noteOp(op, Process.myUid(), mContext.getOpPackageName(), null);
+ }
+
+ /**
+ * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "#noteOp(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+ public int noteOp(int op, int uid, @Nullable String packageName) {
+ return noteOp(op, uid, packageName, null);
+ }
+
+ /**
* Make note of an application performing an operation. Note that you must pass
* in both the uid and name of the application to be checked; this function will verify
* that these two match, and if not, return {@link #MODE_IGNORED}. If this call
* succeeds, the last execution time of the operation for this app will be updated to
* the current time.
+ *
* @param op The operation to note. One of the OPSTR_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
+ * @param message A message describing the reason the op was noted
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
+ *
* @throws SecurityException If the app has been configured to crash on this op.
*/
- public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
- return noteOp(strOpToOp(op), uid, packageName);
+ public int noteOp(@NonNull String op, int uid, @Nullable String packageName,
+ @Nullable String message) {
+ return noteOp(strOpToOp(op), uid, packageName, message);
}
/**
- * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
- * returns {@link #MODE_ERRORED}.
+ * Make note of an application performing an operation. Note that you must pass
+ * in both the uid and name of the application to be checked; this function will verify
+ * that these two match, and if not, return {@link #MODE_IGNORED}. If this call
+ * succeeds, the last execution time of the operation for this app will be updated to
+ * the current time.
+ *
+ * @param op The operation to note. One of the OP_* constants.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
+ * @param message A message describing the reason the op was noted
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+ * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+ * causing the app to crash).
+ *
+ * @throws SecurityException If the app has been configured to crash on this op.
+ *
+ * @hide
*/
+ public int noteOp(int op, int uid, @Nullable String packageName, @Nullable String message) {
+ final int mode = noteOpNoThrow(op, uid, packageName, message);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+ }
+ return mode;
+ }
+
+ /**
+ * @deprecated Use {@link #noteOpNoThrow(String, int, String, String)} instead
+ */
+ @Deprecated
public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
- return noteOpNoThrow(strOpToOp(op), uid, packageName);
+ return noteOpNoThrow(op, uid, packageName, null);
}
/**
- * Make note of an application performing an operation on behalf of another
- * application when handling an IPC. Note that you must pass the package name
- * of the application that is being proxied while its UID will be inferred from
- * the IPC state; this function will verify that the calling uid and proxied
- * package name match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for the proxied app and
- * your app will be updated to the current time.
+ * @deprecated Use {@link #noteOpNoThrow(int, int, String, String)} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "#noteOpNoThrow(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+ public int noteOpNoThrow(int op, int uid, String packageName) {
+ return noteOpNoThrow(op, uid, packageName, null);
+ }
+
+ /**
+ * Like {@link #noteOp(String, int, String, String)} but instead of throwing a
+ * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
* @param op The operation to note. One of the OPSTR_* constants.
- * @param proxiedPackageName The name of the application calling into the proxy application.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
+ * @param message A message describing the reason the op was noted
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
- * @throws SecurityException If the app has been configured to crash on this op.
*/
+ public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String message) {
+ return noteOpNoThrow(strOpToOp(op), uid, packageName, message);
+ }
+
+ /**
+ * Like {@link #noteOp(String, int, String, String)} but instead of throwing a
+ * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * @param op The operation to note. One of the OP_* constants.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
+ * @param message A message describing the reason the op was noted
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+ * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+ * causing the app to crash).
+ *
+ * @hide
+ */
+ public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
+ @Nullable String message) {
+ try {
+ int mode = mService.noteOperation(op, uid, packageName);
+ if (mode == MODE_ALLOWED) {
+ markAppOpNoted(uid, packageName, op, message);
+ }
+
+ return mode;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead
+ */
+ @Deprecated
public int noteProxyOp(@NonNull String op, @NonNull String proxiedPackageName) {
- return noteProxyOp(strOpToOp(op), proxiedPackageName);
+ return noteProxyOp(op, proxiedPackageName, Binder.getCallingUid(), null);
}
/**
- * Like {@link #noteProxyOp(String, String)} but instead
- * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead
*
- * <p>This API requires the package with the {@code proxiedPackageName} to belongs to
- * {@link Binder#getCallingUid()}.
+ * @hide
*/
- public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
- return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName);
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "#noteProxyOp(java.lang.String, java.lang.String, int, java.lang.String)} instead")
+ public int noteProxyOp(int op, @Nullable String proxiedPackageName) {
+ return noteProxyOp(op, proxiedPackageName, Binder.getCallingUid(), null);
}
/**
- * Like {@link #noteProxyOpNoThrow(String, String)} but allows to specify the proxied uid.
+ * Make note of an application performing an operation on behalf of another application when
+ * handling an IPC. This function will verify that the calling uid and proxied package name
+ * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
+ * time of the operation for the proxied app and your app will be updated to the current time.
*
- * <p>This API requires package with the {@code proxiedPackageName} to belong to
- * {@code proxiedUid}.
+ * @param op The operation to note. One of the OP_* constants.
+ * @param proxiedPackageName The name of the application calling into the proxy application.
+ * @param proxiedUid The uid of the proxied application
+ * @param message A message describing the reason the op was noted
*
- * @param op The op to note
- * @param proxiedPackageName The package to note the op for or {@code null} if the op should be
- * noted for the "android" package
- * @param proxiedUid The uid the package belongs to
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+ * if it is not allowed and should be silently ignored (without causing the app to crash).
+ *
+ * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+ * op.
+ *
+ * @hide
*/
- public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
- int proxiedUid) {
- return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid);
+ public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
+ @Nullable String message) {
+ int mode = noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, message);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Proxy package " + mContext.getOpPackageName()
+ + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName
+ + " from uid " + proxiedUid + " not allowed to perform " + sOpNames[op]);
+ }
+ return mode;
}
/**
- * Report that an application has started executing a long-running operation. Note that you
- * must pass in both the uid and name of the application to be checked; this function will
- * verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for this app will be updated to
- * the current time and the operation will be marked as "running". In this case you must
- * later call {@link #finishOp(String, int, String)} to report when the application is no
- * longer performing the operation.
- * @param op The operation to start. One of the OPSTR_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
- * @throws SecurityException If the app has been configured to crash on this op.
+ * Make note of an application performing an operation on behalf of another application when
+ * handling an IPC. This function will verify that the calling uid and proxied package name
+ * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
+ * time of the operation for the proxied app and your app will be updated to the current time.
+ *
+ * @param op The operation to note. One of the OPSTR_* constants.
+ * @param proxiedPackageName The name of the application calling into the proxy application.
+ * @param proxiedUid The uid of the proxied application
+ * @param message A message describing the reason the op was noted
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+ * if it is not allowed and should be silently ignored (without causing the app to crash).
+ *
+ * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+ * op.
*/
- public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
- return startOp(strOpToOp(op), uid, packageName);
+ public int noteProxyOp(@NonNull String op, @Nullable String proxiedPackageName, int proxiedUid,
+ @Nullable String message) {
+ return noteProxyOp(strOpToOp(op), proxiedPackageName, proxiedUid, message);
}
/**
- * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
- * returns {@link #MODE_ERRORED}.
+ * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead
*/
- public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
- return startOpNoThrow(strOpToOp(op), uid, packageName);
+ @Deprecated
+ public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
+ return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid(), null);
}
/**
- * Report that an application is no longer performing an operation that had previously
- * been started with {@link #startOp(String, int, String)}. There is no validation of input
- * or result; the parameters supplied here must be the exact same ones previously passed
- * in when starting the operation.
+ * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead
*/
- public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
- finishOp(strOpToOp(op), uid, packageName);
+ @Deprecated
+ public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+ int proxiedUid) {
+ return noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, null);
+ }
+
+ /**
+ * Like {@link #noteProxyOp(String, String, int, String)} but instead
+ * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * <p>This API requires package with the {@code proxiedPackageName} to belong to
+ * {@code proxiedUid}.
+ *
+ * @param op The op to note
+ * @param proxiedPackageName The package to note the op for
+ * @param proxiedUid The uid the package belongs to
+ * @param message A message describing the reason the op was noted
+ */
+ public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+ int proxiedUid, @Nullable String message) {
+ return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid, message);
+ }
+
+ /**
+ * Like {@link #noteProxyOp(int, String, int, String)} but instead
+ * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * @param op The op to note
+ * @param proxiedPackageName The package to note the op for or {@code null} if the op should be
+ * noted for the "android" package
+ * @param proxiedUid The uid the package belongs to
+ * @param message A message describing the reason the op was noted
+ *
+ * @hide
+ */
+ public int noteProxyOpNoThrow(int op, @Nullable String proxiedPackageName, int proxiedUid,
+ @Nullable String message) {
+ int myUid = Process.myUid();
+
+ try {
+ int mode = mService.noteProxyOperation(op, myUid, mContext.getOpPackageName(),
+ proxiedUid, proxiedPackageName);
+ if (mode == MODE_ALLOWED
+ // Only collect app-ops when the proxy is trusted
+ && mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1, myUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ markAppOpNoted(proxiedUid, proxiedPackageName, op, message);
+ }
+
+ return mode;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* Do a quick check for whether an application might be able to perform an operation.
- * This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)}
- * or {@link #startOp(int, int, String)} for your actual security checks, which also
- * ensure that the given uid and package name are consistent. This function can just be
- * used for a quick check to see if an operation has been disabled for the application,
- * as an early reject of some work. This does not modify the time stamp or other data
- * about the operation.
+ * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
+ * String)} or {@link #startOp(int, int, String, boolean, String)} for your actual security
+ * checks, which also ensure that the given uid and package name are consistent. This function
+ * can just be used for a quick check to see if an operation has been disabled for the
+ * application, as an early reject of some work. This does not modify the time stamp or other
+ * data about the operation.
*
* <p>Important things this will not do (which you need to ultimate use
- * {@link #noteOp(int, int, String)} or {@link #startOp(int, int, String)} to cover):</p>
+ * {@link #noteOp(String, int, String, String)} or
+ * {@link #startOp(int, int, String, boolean, String)} to cover):</p>
* <ul>
* <li>Verifying the uid and package are consistent, so callers can't spoof
* their identity.</li>
@@ -5259,171 +5499,102 @@ public class AppOpsManager {
}
}
- /**
- * Make note of an application performing an operation. Note that you must pass
- * in both the uid and name of the application to be checked; this function will verify
- * that these two match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for this app will be updated to
- * the current time.
- * @param op The operation to note. One of the OP_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
- * @throws SecurityException If the app has been configured to crash on this op.
- * @hide
- */
+ /** @hide */
@UnsupportedAppUsage
- public int noteOp(int op, int uid, String packageName) {
- final int mode = noteOpNoThrow(op, uid, packageName);
- if (mode == MODE_ERRORED) {
- throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+ public static IBinder getToken(IAppOpsService service) {
+ synchronized (AppOpsManager.class) {
+ if (sToken != null) {
+ return sToken;
+ }
+ try {
+ sToken = service.getToken(new Binder());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return sToken;
}
- return mode;
}
+
/**
- * Make note of an application performing an operation on behalf of another
- * application when handling an IPC. Note that you must pass the package name
- * of the application that is being proxied while its UID will be inferred from
- * the IPC state; this function will verify that the calling uid and proxied
- * package name match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for the proxied app and
- * your app will be updated to the current time.
- * @param op The operation to note. One of the OPSTR_* constants.
- * @param proxiedPackageName The name of the application calling into the proxy application.
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
- * @throws SecurityException If the proxy or proxied app has been configured to
- * crash on this op.
- *
- * @hide
+ * @deprecated use {@link #startOp(String, int, String, String)} instead
*/
- @UnsupportedAppUsage
- public int noteProxyOp(int op, String proxiedPackageName) {
- int mode = noteProxyOpNoThrow(op, proxiedPackageName);
- if (mode == MODE_ERRORED) {
- throw new SecurityException("Proxy package " + mContext.getOpPackageName()
- + " from uid " + Process.myUid() + " or calling package "
- + proxiedPackageName + " from uid " + Binder.getCallingUid()
- + " not allowed to perform " + sOpNames[op]);
- }
- return mode;
+ @Deprecated
+ public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
+ return startOp(op, uid, packageName, null);
}
/**
- * Like {@link #noteProxyOp(int, String)} but instead
- * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
+ *
* @hide
*/
- public int noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid) {
- try {
- return mService.noteProxyOperation(op, Process.myUid(), mContext.getOpPackageName(),
- proxiedUid, proxiedPackageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ @Deprecated
+ public int startOp(int op) {
+ return startOp(op, Process.myUid(), mContext.getOpPackageName(), false, null);
}
/**
- * Like {@link #noteProxyOp(int, String)} but instead
- * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
- *
- * <p>This API requires the package with {@code proxiedPackageName} to belongs to
- * {@link Binder#getCallingUid()}.
+ * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
*
* @hide
*/
- public int noteProxyOpNoThrow(int op, String proxiedPackageName) {
- return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid());
+ @Deprecated
+ public int startOp(int op, int uid, String packageName) {
+ return startOp(op, uid, packageName, false, null);
}
/**
- * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
- * returns {@link #MODE_ERRORED}.
+ * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
+ *
* @hide
*/
- @UnsupportedAppUsage
- public int noteOpNoThrow(int op, int uid, String packageName) {
- try {
- return mService.noteOperation(op, uid, packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public int noteOp(int op) {
- return noteOp(op, Process.myUid(), mContext.getOpPackageName());
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public static IBinder getToken(IAppOpsService service) {
- synchronized (AppOpsManager.class) {
- if (sToken != null) {
- return sToken;
- }
- try {
- sToken = service.getToken(new Binder());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return sToken;
- }
- }
-
- /** @hide */
- public int startOp(int op) {
- return startOp(op, Process.myUid(), mContext.getOpPackageName());
+ @Deprecated
+ public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
+ return startOp(op, uid, packageName, startIfModeDefault, null);
}
/**
- * Report that an application has started executing a long-running operation. Note that you
- * must pass in both the uid and name of the application to be checked; this function will
- * verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for this app will be updated to
- * the current time and the operation will be marked as "running". In this case you must
- * later call {@link #finishOp(int, int, String)} to report when the application is no
- * longer performing the operation.
+ * Report that an application has started executing a long-running operation.
*
- * @param op The operation to start. One of the OP_* constants.
+ * @param op The operation to start. One of the OPSTR_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
+ * @param message Description why op was started
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
- * @throws SecurityException If the app has been configured to crash on this op.
- * @hide
+ *
+ * @throws SecurityException If the app has been configured to crash on this op or
+ * the package is not in the passed in UID.
*/
- public int startOp(int op, int uid, String packageName) {
- return startOp(op, uid, packageName, false);
+ public int startOp(@NonNull String op, int uid, @Nullable String packageName,
+ @Nullable String message) {
+ return startOp(strOpToOp(op), uid, packageName, false, message);
}
/**
- * Report that an application has started executing a long-running operation. Similar
- * to {@link #startOp(String, int, String) except that if the mode is {@link #MODE_DEFAULT}
- * the operation should succeed since the caller has performed its standard permission
- * checks which passed and would perform the protected operation for this mode.
+ * Report that an application has started executing a long-running operation.
*
* @param op The operation to start. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
+ * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+ * @param message Description why op was started
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
- * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
*
* @throws SecurityException If the app has been configured to crash on this op or
* the package is not in the passed in UID.
*
* @hide
*/
- public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
- final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault);
+ public int startOp(int op, int uid, @Nullable String packageName, boolean startIfModeDefault,
+ @Nullable String message) {
+ final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault, message);
if (mode == MODE_ERRORED) {
throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
}
@@ -5431,45 +5602,111 @@ public class AppOpsManager {
}
/**
- * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
- * returns {@link #MODE_ERRORED}.
+ * @deprecated use {@link #startOpNoThrow(String, int, String, String)} instead
+ */
+ @Deprecated
+ public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+ return startOpNoThrow(op, uid, packageName, null);
+ }
+
+ /**
+ * @deprecated Use {@link #startOpNoThrow(int, int, String, boolean, String} instead
+ *
* @hide
*/
+ @Deprecated
public int startOpNoThrow(int op, int uid, String packageName) {
- return startOpNoThrow(op, uid, packageName, false);
+ return startOpNoThrow(op, uid, packageName, false, null);
}
/**
- * Like {@link #startOp(int, int, String, boolean)} but instead of throwing a
+ * @deprecated Use {@link #startOpNoThrow(int, int, String, boolean, String} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+ return startOpNoThrow(op, uid, packageName, startIfModeDefault, null);
+ }
+
+ /**
+ * Like {@link #startOp(String, int, String, String)} but instead of throwing a
* {@link SecurityException} it returns {@link #MODE_ERRORED}.
*
* @param op The operation to start. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
+ * @param message Description why op was started
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
+ */
+ public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String message) {
+ return startOpNoThrow(strOpToOp(op), uid, packageName, false, message);
+ }
+
+ /**
+ * Like {@link #startOp(int, int, String, boolean, String)} but instead of throwing a
+ * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * @param op The operation to start. One of the OP_* constants.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
* @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+ * @param message Description why op was started
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+ * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+ * causing the app to crash).
*
* @hide
*/
- public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+ public int startOpNoThrow(int op, int uid, @NonNull String packageName,
+ boolean startIfModeDefault, @Nullable String message) {
try {
- return mService.startOperation(getToken(mService), op, uid, packageName,
+ int mode = mService.startOperation(getToken(mService), op, uid, packageName,
startIfModeDefault);
+ if (mode == MODE_ALLOWED) {
+ markAppOpNoted(uid, packageName, op, message);
+ }
+
+ return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
+ * @deprecated Use {@link #finishOp(String, int, String)} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ public void finishOp(int op) {
+ finishOp(op, Process.myUid(), mContext.getOpPackageName());
+ }
+
+ /**
* Report that an application is no longer performing an operation that had previously
- * been started with {@link #startOp(int, int, String)}. There is no validation of input
- * or result; the parameters supplied here must be the exact same ones previously passed
+ * been started with {@link #startOp(String, int, String, String)}. There is no validation of
+ * input or result; the parameters supplied here must be the exact same ones previously passed
* in when starting the operation.
+ */
+ public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
+ finishOp(strOpToOp(op), uid, packageName);
+ }
+
+ /**
+ * Report that an application is no longer performing an operation that had previously
+ * been started with {@link #startOp(int, int, String, boolean, String)}. There is no
+ * validation of input or result; the parameters supplied here must be the exact same ones
+ * previously passed in when starting the operation.
+ *
* @hide
*/
- public void finishOp(int op, int uid, String packageName) {
+ public void finishOp(int op, int uid, @NonNull String packageName) {
try {
mService.finishOperation(getToken(mService), op, uid, packageName);
} catch (RemoteException e) {
@@ -5477,25 +5714,399 @@ public class AppOpsManager {
}
}
- /** @hide */
- public void finishOp(int op) {
- finishOp(op, Process.myUid(), mContext.getOpPackageName());
- }
-
/**
* Checks whether the given op for a package is active.
* <p>
* If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS}
* permission you can query only for your UID.
*
- * @see #finishOp(int)
- * @see #startOp(int)
+ * @see #finishOp(String, int, String)
+ * @see #startOp(String, int, String, String)
*/
public boolean isOpActive(@NonNull String op, int uid, @NonNull String packageName) {
return isOperationActive(strOpToOp(op), uid, packageName);
}
/**
+ * Start collection of noted appops on this thread.
+ *
+ * <p>Called at the beginning of a two way binder transaction.
+ *
+ * @see #finishNotedAppOpsCollection()
+ *
+ * @hide
+ */
+ public static void startNotedAppOpsCollection(int callingUid) {
+ sBinderThreadCallingUid.set(callingUid);
+ }
+
+ /**
+ * State of a temporarily paused noted app-ops collection.
+ *
+ * @see #pauseNotedAppOpsCollection()
+ *
+ * @hide
+ */
+ public static class PausedNotedAppOpsCollection {
+ final int mUid;
+ final @Nullable long[] mCollectedNotedAppOps;
+
+ PausedNotedAppOpsCollection(int uid, @Nullable 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) {
+ long[] previousCollectedNotedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+
+ sBinderThreadCallingUid.remove();
+ sAppOpsNotedInThisBinderTransaction.remove();
+
+ return new PausedNotedAppOpsCollection(previousUid, previousCollectedNotedAppOps);
+ }
+
+ return null;
+ }
+
+ /**
+ * Resume a collection paused via {@link #pauseNotedAppOpsCollection}.
+ *
+ * @param prevCollection The state of the previous collection
+ *
+ * @hide
+ */
+ public static void resumeNotedAppOpsCollection(
+ @Nullable PausedNotedAppOpsCollection prevCollection) {
+ if (prevCollection != null) {
+ sBinderThreadCallingUid.set(prevCollection.mUid);
+
+ if (prevCollection.mCollectedNotedAppOps != null) {
+ sAppOpsNotedInThisBinderTransaction.set(prevCollection.mCollectedNotedAppOps);
+ }
+ }
+ }
+
+ /**
+ * Finish collection of noted appops on this thread.
+ *
+ * <p>Called at the end of a two way binder transaction.
+ *
+ * @see #startNotedAppOpsCollection(int)
+ *
+ * @hide
+ */
+ public static void finishNotedAppOpsCollection() {
+ sBinderThreadCallingUid.remove();
+ sAppOpsNotedInThisBinderTransaction.remove();
+ }
+
+ /**
+ * Mark an app-op as noted
+ */
+ private void markAppOpNoted(int uid, @NonNull String packageName, int code,
+ @Nullable String message) {
+ // check it the appops needs to be collected and cache result
+ if (sAppOpsToNote[code] == SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED) {
+ boolean shouldCollectNotes;
+ try {
+ shouldCollectNotes = mService.shouldCollectNotes(code);
+ } catch (RemoteException e) {
+ return;
+ }
+
+ if (shouldCollectNotes) {
+ sAppOpsToNote[code] = SHOULD_COLLECT_NOTE_OP;
+ } else {
+ sAppOpsToNote[code] = SHOULD_NOT_COLLECT_NOTE_OP;
+ }
+ }
+
+ if (sAppOpsToNote[code] != SHOULD_COLLECT_NOTE_OP) {
+ return;
+ }
+
+ Integer binderUid = sBinderThreadCallingUid.get();
+
+ synchronized (sLock) {
+ if (sNotedAppOpsCollector != null && uid == Process.myUid() && packageName.equals(
+ ActivityThread.currentOpPackageName())) {
+ // This app is noting an app-op for itself. Deliver immediately.
+ sNotedAppOpsCollector.onSelfNoted(new SyncNotedAppOp(code));
+ } else if (binderUid != null && binderUid == uid) {
+ // We are inside of a two-way binder call. Delivered to caller via
+ // {@link #prefixParcelWithAppOpsIfNeeded}
+ long[] appOpsNotedInThisBinderTransaction;
+
+ appOpsNotedInThisBinderTransaction = sAppOpsNotedInThisBinderTransaction.get();
+ if (appOpsNotedInThisBinderTransaction == null) {
+ appOpsNotedInThisBinderTransaction = new long[2];
+ sAppOpsNotedInThisBinderTransaction.set(appOpsNotedInThisBinderTransaction);
+ }
+
+ if (code < 64) {
+ appOpsNotedInThisBinderTransaction[0] |= 1L << code;
+ } else {
+ appOpsNotedInThisBinderTransaction[1] |= 1L << (code - 64);
+ }
+ } else {
+ // We cannot deliver the note synchronous. Hence send it to the system server to
+ // notify the noted process.
+ if (message == null) {
+ // Default message is a stack trace
+ message = getFormattedStackTrace();
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ mService.noteAsyncOp(mContext.getOpPackageName(), uid, packageName, code,
+ message);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+
+ /**
+ * Append app-ops noted in the current two-way binder transaction to parcel.
+ *
+ * <p>This is called on the callee side of a two way binder transaction just before the
+ * transaction returns.
+ *
+ * @param p the parcel to append the noted app-ops to
+ *
+ * @hide
+ */
+ public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
+ long[] notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+ if (notedAppOps == null || (notedAppOps[0] == 0 && notedAppOps[1] == 0)) {
+ return;
+ }
+
+ p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
+ p.writeLong(notedAppOps[0]);
+ p.writeLong(notedAppOps[1]);
+ }
+
+ /**
+ * Read app-ops noted during a two-way binder transaction from parcel.
+ *
+ * <p>This is called on the calling side of a two way binder transaction just after the
+ * transaction returns.
+ *
+ * <p>Note: Make sure to keep frameworks/native/libs/binder/Status.cpp::readAndLogNotedAppops
+ * in sync.
+ *
+ * @param p The parcel to read from
+ *
+ * @hide
+ */
+ public static void readAndLogNotedAppops(@NonNull Parcel p) {
+ long[] rawNotedAppOps = new long[2];
+ rawNotedAppOps[0] = p.readLong();
+ rawNotedAppOps[1] = p.readLong();
+
+ if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
+ BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+
+ synchronized (sLock) {
+ for (int code = notedAppOps.nextSetBit(0); code != -1;
+ code = notedAppOps.nextSetBit(code + 1)) {
+ if (sNotedAppOpsCollector != null) {
+ sNotedAppOpsCollector.onNoted(new SyncNotedAppOp(code));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a new {@link AppOpsCollector}.
+ *
+ * <p>There can only ever be one collector per process. If there currently is a collector
+ * registered, it will be unregistered.
+ *
+ * <p><b>Only appops related to dangerous permissions are collected.</b>
+ *
+ * @param collector The collector to set or {@code null} to unregister.
+ */
+ public void setNotedAppOpsCollector(@Nullable AppOpsCollector collector) {
+ synchronized (sLock) {
+ if (sNotedAppOpsCollector != null) {
+ try {
+ mService.stopWatchingAsyncNoted(mContext.getPackageName(),
+ sNotedAppOpsCollector.mAsyncCb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ sNotedAppOpsCollector = collector;
+
+ if (sNotedAppOpsCollector != null) {
+ List<AsyncNotedAppOp> missedAsyncOps = null;
+ try {
+ mService.startWatchingAsyncNoted(mContext.getPackageName(),
+ sNotedAppOpsCollector.mAsyncCb);
+ missedAsyncOps = mService.extractAsyncOps(mContext.getPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (missedAsyncOps != null) {
+ int numMissedAsyncOps = missedAsyncOps.size();
+ for (int i = 0; i < numMissedAsyncOps; i++) {
+ final AsyncNotedAppOp asyncNotedAppOp = missedAsyncOps.get(i);
+ if (sNotedAppOpsCollector != null) {
+ sNotedAppOpsCollector.getAsyncNotedExecutor().execute(
+ () -> sNotedAppOpsCollector.onAsyncNoted(
+ asyncNotedAppOp));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @return {@code true} iff the process currently is currently collecting noted appops.
+ *
+ * @see #setNotedAppOpsCollector(AppOpsCollector)
+ *
+ * @hide
+ */
+ public static boolean isCollectingNotedAppOps() {
+ synchronized (sLock) {
+ return sNotedAppOpsCollector != null;
+ }
+ }
+
+ /**
+ * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted
+ * appops.
+ *
+ * <p><b>Only appops related to dangerous permissions are collected.</b>
+ */
+ public abstract static class AppOpsCollector {
+ /** Callback registered with the system. This will receive the async notes ops */
+ private final IAppOpsAsyncNotedCallback mAsyncCb = new IAppOpsAsyncNotedCallback.Stub() {
+ @Override
+ public void opNoted(AsyncNotedAppOp op) {
+ Preconditions.checkNotNull(op);
+
+ getAsyncNotedExecutor().execute(() -> onAsyncNoted(op));
+ }
+ };
+
+ /**
+ * @return The executor for the system to use when calling {@link #onAsyncNoted}.
+ */
+ public @NonNull Executor getAsyncNotedExecutor() {
+ return new HandlerExecutor(Handler.getMain());
+ }
+
+ /**
+ * Called when an app-op was noted for this package inside of a two-way binder-call.
+ *
+ * <p>Called on the calling thread just after executing the binder-call. This allows
+ * the app to e.g. collect stack traces to figure out where the access came from.
+ *
+ * @param op The op noted
+ */
+ public abstract void onNoted(@NonNull SyncNotedAppOp op);
+
+ /**
+ * Called when this app noted an app-op for its own package.
+ *
+ * <p>Called on the thread the noted the op. This allows the app to e.g. collect stack
+ * traces to figure out where the access came from.
+ *
+ * @param op The op noted
+ */
+ public abstract void onSelfNoted(@NonNull SyncNotedAppOp op);
+
+ /**
+ * Called when an app-op was noted for this package which cannot be delivered via the other
+ * two mechanisms.
+ *
+ * <p>Called as soon as possible after the app-op was noted, but the delivery delay is not
+ * guaranteed. Due to how async calls work in Android this might even be delivered slightly
+ * before the private data is delivered to the app.
+ *
+ * <p>If the app is not running or no {@link AppOpsCollector} is registered a small amount
+ * of noted app-ops are buffered and then delivered as soon as a collector is registered.
+ *
+ * @param asyncOp The op noted
+ */
+ public abstract void onAsyncNoted(@NonNull AsyncNotedAppOp asyncOp);
+ }
+
+ /**
+ * Generate a stack trace used for noted app-ops logging.
+ *
+ * <p>This strips away the first few and last few stack trace elements as they are not
+ * interesting to apps.
+ */
+ private static String getFormattedStackTrace() {
+ StackTraceElement[] trace = new Exception().getStackTrace();
+
+ int firstInteresting = 0;
+ for (int i = 0; i < trace.length; i++) {
+ if (trace[i].getClassName().startsWith(AppOpsManager.class.getName())
+ || trace[i].getClassName().startsWith(Parcel.class.getName())
+ || trace[i].getClassName().contains("$Stub$Proxy")
+ || trace[i].getClassName().startsWith(DatabaseUtils.class.getName())
+ || trace[i].getClassName().startsWith("android.content.ContentProviderProxy")
+ || trace[i].getClassName().startsWith(ContentResolver.class.getName())) {
+ firstInteresting = i;
+ } else {
+ break;
+ }
+ }
+
+ int lastInteresting = trace.length - 1;
+ for (int i = trace.length - 1; i >= 0; i--) {
+ if (trace[i].getClassName().startsWith(HandlerThread.class.getName())
+ || trace[i].getClassName().startsWith(Handler.class.getName())
+ || trace[i].getClassName().startsWith(Looper.class.getName())
+ || trace[i].getClassName().startsWith(Binder.class.getName())
+ || trace[i].getClassName().startsWith(RuntimeInit.class.getName())
+ || trace[i].getClassName().startsWith(ZygoteInit.class.getName())
+ || trace[i].getClassName().startsWith(ActivityThread.class.getName())
+ || trace[i].getClassName().startsWith(Method.class.getName())
+ || trace[i].getClassName().startsWith("com.android.server.SystemServer")) {
+ lastInteresting = i;
+ } else {
+ break;
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = firstInteresting; i <= lastInteresting; i++) {
+ sb.append(trace[i]);
+ if (i != lastInteresting) {
+ sb.append('\n');
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
* Checks whether the given op for a UID and package is active.
*
* <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
@@ -5504,7 +6115,7 @@ public class AppOpsManager {
* @see #startWatchingActive(int[], OnOpActiveChangedListener)
* @see #stopWatchingMode(OnOpChangedListener)
* @see #finishOp(int)
- * @see #startOp(int)
+ * @see #startOp(int, int, String, boolean, String)
*
* @hide */
@TestApi
diff --git a/core/java/android/app/AsyncNotedAppOp.aidl b/core/java/android/app/AsyncNotedAppOp.aidl
new file mode 100644
index 000000000000..ebfefd0ba66c
--- /dev/null
+++ b/core/java/android/app/AsyncNotedAppOp.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable AsyncNotedAppOp;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
new file mode 100644
index 000000000000..64f886aa2f1d
--- /dev/null
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * When an {@link AppOpsManager#noteOp(String, int, String, String) app-op is noted} and the
+ * app the app-op is noted for has a {@link AppOpsManager.AppOpsCollector} registered the note-event
+ * needs to be delivered to the collector. Usually this is done via an {@link SyncNotedAppOp}, but
+ * in some cases this is not possible. In this case an {@link AsyncNotedAppOp} is send to the system
+ * server and then forwarded to the {@link AppOpsManager.AppOpsCollector} in the app.
+ */
+@Immutable
+@DataClass(genEqualsHashCode = true,
+ genAidl = true,
+ genHiddenConstructor = true)
+// - We don't expose the opCode, but rather the public name of the op, hence use a non-standard
+// getter
+@DataClass.Suppress({"getOpCode"})
+public final class AsyncNotedAppOp implements Parcelable {
+ /** Op that was noted */
+ private final @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+
+ /** Uid that noted the op */
+ private final @IntRange(from = 0) int mNotingUid;
+
+ /**
+ * Package that noted the op. {@code null} if the package name that noted the op could be not
+ * be determined (e.g. when the op is noted from native code).
+ */
+ private final @Nullable String mNotingPackageName;
+
+ /** Message associated with the noteOp. This message is set by the app noting the op */
+ private final @NonNull String mMessage;
+
+ /** Milliseconds since epoch when the op was noted */
+ private final @IntRange(from = 0) long mTime;
+
+ /**
+ * @return Op that was noted.
+ */
+ public @NonNull String getOp() {
+ return AppOpsManager.opToPublicName(mOpCode);
+ }
+
+
+
+ // Code below generated by codegen v1.0.0.
+ //
+ // DO NOT MODIFY!
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AsyncNotedAppOp.java
+ //
+ // CHECKSTYLE:OFF Generated code
+
+ /**
+ * Creates a new AsyncNotedAppOp.
+ *
+ * @param opCode
+ * Op that was noted
+ * @param notingUid
+ * Uid that noted the op
+ * @param notingPackageName
+ * Package that noted the op
+ * @param message
+ * Message associated with the noteOp. This message is set by the app noting the op
+ * @param time
+ * Milliseconds since epoch when the op was noted
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public AsyncNotedAppOp(
+ @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode,
+ @IntRange(from = 0) int notingUid,
+ @Nullable String notingPackageName,
+ @NonNull String message,
+ @IntRange(from = 0) long time) {
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mNotingUid = notingUid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mNotingUid,
+ "from", 0);
+ this.mNotingPackageName = notingPackageName;
+ this.mMessage = message;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessage);
+ this.mTime = time;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mTime,
+ "from", 0);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Uid that noted the op
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) int getNotingUid() {
+ return mNotingUid;
+ }
+
+ /**
+ * Package that noted the op
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getNotingPackageName() {
+ return mNotingPackageName;
+ }
+
+ /**
+ * Message associated with the noteOp. This message is set by the app noting the op
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Milliseconds since epoch when the op was noted
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) long getTime() {
+ return mTime;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ AsyncNotedAppOp that = (AsyncNotedAppOp) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mOpCode == that.mOpCode
+ && mNotingUid == that.mNotingUid
+ && java.util.Objects.equals(mNotingPackageName, that.mNotingPackageName)
+ && java.util.Objects.equals(mMessage, that.mMessage)
+ && mTime == that.mTime;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mOpCode;
+ _hash = 31 * _hash + mNotingUid;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mNotingPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mMessage);
+ _hash = 31 * _hash + Long.hashCode(mTime);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mNotingPackageName != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mOpCode);
+ dest.writeInt(mNotingUid);
+ if (mNotingPackageName != null) dest.writeString(mNotingPackageName);
+ dest.writeString(mMessage);
+ dest.writeLong(mTime);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<AsyncNotedAppOp> CREATOR
+ = new Parcelable.Creator<AsyncNotedAppOp>() {
+ @Override
+ public AsyncNotedAppOp[] newArray(int size) {
+ return new AsyncNotedAppOp[size];
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ public AsyncNotedAppOp createFromParcel(android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int opCode = in.readInt();
+ int notingUid = in.readInt();
+ String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
+ String message = in.readString();
+ long time = in.readLong();
+ return new AsyncNotedAppOp(
+ opCode,
+ notingUid,
+ notingPackageName,
+ message,
+ time);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1566503083973L,
+ codegenVersion = "1.0.0",
+ sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=0L, to=90L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+}
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
new file mode 100644
index 000000000000..f7b83d409a02
--- /dev/null
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.Immutable;
+
+/**
+ * Description of an app-op that was noted for the current process.
+ *
+ * <p>This is either delivered after a
+ * {@link AppOpsManager.AppOpsCollector#onNoted(SyncNotedAppOp) two way binder call} or
+ * when the app
+ * {@link AppOpsManager.AppOpsCollector#onSelfNoted(SyncNotedAppOp) notes an app-op for
+ * itself}.
+ */
+@Immutable
+public final class SyncNotedAppOp {
+ private final int mOpCode;
+
+ /**
+ * @return The op that was noted.
+ */
+ public @NonNull String getOp() {
+ return AppOpsManager.opToPublicName(mOpCode);
+ }
+
+ /**
+ * Create a new sync op description
+ *
+ * @param opCode The op that was noted
+ *
+ * @hide
+ */
+ public SyncNotedAppOp(@IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode) {
+ mOpCode = opCode;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof SyncNotedAppOp)) {
+ return false;
+ }
+
+ return mOpCode == ((SyncNotedAppOp) other).mOpCode;
+ }
+
+ @Override
+ public int hashCode() {
+ return mOpCode;
+ }
+}
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 6fe6e991fb1e..e24512ac525d 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,6 +16,8 @@
package android.content;
+import static android.app.AppOpsManager.strOpToOp;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -75,6 +77,16 @@ public final class PermissionChecker {
}
/**
+ * @deprecated Use {@link #checkPermission(Context, String, int, int, String, String)} instead
+ */
+ @Deprecated
+ @PermissionResult
+ public static int checkPermission(@NonNull Context context, @NonNull String permission,
+ int pid, int uid, @Nullable String packageName) {
+ return checkPermission(context, permission, pid, uid, packageName, null);
+ }
+
+ /**
* Checks whether a given package in a UID and PID has a given permission
* and whether the app op that corresponds to this permission is allowed.
*
@@ -84,12 +96,14 @@ public final class PermissionChecker {
* @param uid The uid for which to check.
* @param packageName The package name for which to check. If null the
* the first package for the calling UID will be used.
+ * @param message A message describing the reason the permission was checked
+ *
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
*/
@PermissionResult
public static int checkPermission(@NonNull Context context, @NonNull String permission,
- int pid, int uid, @Nullable String packageName) {
+ int pid, int uid, @Nullable String packageName, @Nullable String message) {
if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
return PERMISSION_DENIED;
}
@@ -108,7 +122,8 @@ public final class PermissionChecker {
packageName = packageNames[0];
}
- if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid) != AppOpsManager.MODE_ALLOWED) {
+ if (appOpsManager.noteProxyOpNoThrow(strOpToOp(op), packageName, uid, message)
+ != AppOpsManager.MODE_ALLOWED) {
return PERMISSION_DENIED_APP_OP;
}
@@ -131,7 +146,17 @@ public final class PermissionChecker {
public static int checkSelfPermission(@NonNull Context context,
@NonNull String permission) {
return checkPermission(context, permission, Process.myPid(),
- Process.myUid(), context.getPackageName());
+ Process.myUid(), context.getPackageName(), null /* self access */);
+ }
+
+ /**
+ * @deprecated Use {@link #checkCallingPermission(Context, String, String, String)} instead
+ */
+ @Deprecated
+ @PermissionResult
+ public static int checkCallingPermission(@NonNull Context context,
+ @NonNull String permission, @Nullable String packageName) {
+ return checkCallingPermission(context, permission, packageName, null);
}
/**
@@ -142,17 +167,29 @@ public final class PermissionChecker {
* @param permission The permission to check.
* @param packageName The package name making the IPC. If null the
* the first package for the calling UID will be used.
+ * @param message A message describing the reason the permission was checked
+ *
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
*/
@PermissionResult
public static int checkCallingPermission(@NonNull Context context,
- @NonNull String permission, @Nullable String packageName) {
+ @NonNull String permission, @Nullable String packageName, @Nullable String message) {
if (Binder.getCallingPid() == Process.myPid()) {
return PERMISSION_DENIED;
}
return checkPermission(context, permission, Binder.getCallingPid(),
- Binder.getCallingUid(), packageName);
+ Binder.getCallingUid(), packageName, message);
+ }
+
+ /**
+ * @deprecated Use {@link #checkCallingOrSelfPermission(Context, String, String)} instead
+ */
+ @Deprecated
+ @PermissionResult
+ public static int checkCallingOrSelfPermission(@NonNull Context context,
+ @NonNull String permission) {
+ return checkCallingOrSelfPermission(context, permission, null);
}
/**
@@ -161,15 +198,17 @@ public final class PermissionChecker {
*
* @param context Context for accessing resources.
* @param permission The permission to check.
+ * @param message A message describing the reason the permission was checked
+ *
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
*/
@PermissionResult
public static int checkCallingOrSelfPermission(@NonNull Context context,
- @NonNull String permission) {
+ @NonNull String permission, @Nullable String message) {
String packageName = (Binder.getCallingPid() == Process.myPid())
? context.getPackageName() : null;
return checkPermission(context, permission, Binder.getCallingPid(),
- Binder.getCallingUid(), packageName);
+ Binder.getCallingUid(), packageName, message);
}
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 553372174a4d..2c9333bc1a7c 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
+import android.app.AppOpsManager;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
@@ -1029,7 +1030,17 @@ public class Binder implements IBinder {
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
+ (transactionName != null ? transactionName : code));
}
- res = onTransact(code, data, reply, flags);
+
+ if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
+ AppOpsManager.startNotedAppOpsCollection(callingUid);
+ try {
+ res = onTransact(code, data, reply, flags);
+ } finally {
+ AppOpsManager.finishNotedAppOpsCollection();
+ }
+ } else {
+ res = onTransact(code, data, reply, flags);
+ }
} catch (RemoteException|RuntimeException e) {
if (observer != null) {
observer.callThrewException(callSession, e);
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index c74cef85f3ea..b3b4f784a04a 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -18,6 +18,7 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.util.Log;
import android.util.SparseIntArray;
@@ -506,9 +507,18 @@ public final class BinderProxy implements IBinder {
}
}
+ final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
+ AppOpsManager.pauseNotedAppOpsCollection();
+
+ if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isCollectingNotedAppOps()) {
+ flags |= FLAG_COLLECT_NOTED_APP_OPS;
+ }
+
try {
return transactNative(code, data, reply, flags);
} finally {
+ AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
+
if (transactListener != null) {
transactListener.onTransactEnded(session);
}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 83f88ad41cfc..12bce8a305f2 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -170,6 +170,11 @@ public interface IBinder {
int FLAG_ONEWAY = 0x00000001;
/**
+ * @hide
+ */
+ int FLAG_COLLECT_NOTED_APP_OPS = 0x00000002;
+
+ /**
* Limit that should be placed on IPC sizes to keep them safely under the
* transaction buffer limit.
* @hide
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e1b55422274a..50487e9e7a99 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
+import android.app.AppOpsManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -267,7 +268,9 @@ public final class Parcel {
private static final int EX_UNSUPPORTED_OPERATION = -7;
private static final int EX_SERVICE_SPECIFIC = -8;
private static final int EX_PARCELABLE = -9;
- private static final int EX_HAS_REPLY_HEADER = -128; // special; see below
+ /** @hide */
+ public static final int EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127; // special; see below
+ private static final int EX_HAS_STRICTMODE_REPLY_HEADER = -128; // special; see below
// EX_TRANSACTION_FAILED is used exclusively in native code.
// see libbinder's binder/Status.h
private static final int EX_TRANSACTION_FAILED = -129;
@@ -1866,6 +1869,8 @@ public final class Parcel {
* @see #readException
*/
public final void writeException(@NonNull Exception e) {
+ AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
int code = 0;
if (e instanceof Parcelable
&& (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
@@ -1944,6 +1949,8 @@ public final class Parcel {
* @see #readException
*/
public final void writeNoException() {
+ AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
// Despite the name of this function ("write no exception"),
// it should instead be thought of as "write the RPC response
// header", but because this function name is written out by
@@ -1951,14 +1958,14 @@ public final class Parcel {
//
// The response header, in the non-exception case (see also
// writeException above, also called by the AIDL compiler), is
- // either a 0 (the default case), or EX_HAS_REPLY_HEADER if
+ // either a 0 (the default case), or EX_HAS_STRICTMODE_REPLY_HEADER if
// StrictMode has gathered up violations that have occurred
// during a Binder call, in which case we write out the number
// of violations and their details, serialized, before the
// actual RPC respons data. The receiving end of this is
// readException(), below.
if (StrictMode.hasGatheredViolations()) {
- writeInt(EX_HAS_REPLY_HEADER);
+ writeInt(EX_HAS_STRICTMODE_REPLY_HEADER);
final int sizePosition = dataPosition();
writeInt(0); // total size of fat header, to be filled in later
StrictMode.writeGatheredViolationsToParcel(this);
@@ -2005,7 +2012,13 @@ public final class Parcel {
@TestApi
public final int readExceptionCode() {
int code = readInt();
- if (code == EX_HAS_REPLY_HEADER) {
+ if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
+ AppOpsManager.readAndLogNotedAppops(this);
+ // Read next header or real exception if there is no more header
+ code = readInt();
+ }
+
+ if (code == EX_HAS_STRICTMODE_REPLY_HEADER) {
int headerSize = readInt();
if (headerSize == 0) {
Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
diff --git a/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl b/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
new file mode 100644
index 000000000000..163e4f7127fb
--- /dev/null
+++ b/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.AsyncNotedAppOp;
+
+// Interface to observe async noted appops
+oneway interface IAppOpsAsyncNotedCallback {
+ void opNoted(in AsyncNotedAppOp op);
+}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 72dbbf3b8b87..fcf09a982463 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -17,12 +17,13 @@
package com.android.internal.app;
import android.app.AppOpsManager;
-import android.app.AppOpsManager;
+import android.app.AsyncNotedAppOp;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.os.RemoteCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsNotedCallback;
interface IAppOpsService {
@@ -40,6 +41,9 @@ interface IAppOpsService {
IBinder getToken(IBinder clientToken);
int permissionToOpCode(String permission);
int checkAudioOperation(int code, int usage, int uid, String packageName);
+ void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
+ String message);
+ boolean shouldCollectNotes(int opCode);
// End of methods also called by native code.
// Any new method exposed to native must be added after the last one, do not reorder
@@ -82,6 +86,10 @@ interface IAppOpsService {
void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
void stopWatchingNoted(IAppOpsNotedCallback callback);
+ void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
+ void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
+ List<AsyncNotedAppOp> extractAsyncOps(String packageName);
+
int checkOperationRaw(int code, int uid, String packageName);
void reloadNonHistoricalState();
diff --git a/core/java/com/android/internal/util/AnnotationValidations.java b/core/java/com/android/internal/util/AnnotationValidations.java
index c8afdd47f295..2d3b45023c9d 100644
--- a/core/java/com/android/internal/util/AnnotationValidations.java
+++ b/core/java/com/android/internal/util/AnnotationValidations.java
@@ -61,16 +61,52 @@ public class AnnotationValidations {
}
public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
- String paramName1, int param1, String paramName2, int param2) {
+ String paramName1, long param1, String paramName2, long param2) {
validate(annotation, ignored, value, paramName1, param1);
validate(annotation, ignored, value, paramName2, param2);
}
public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
- String paramName, int param) {
+ String paramName, long param) {
switch (paramName) {
- case "from": if (value < param) invalid(annotation, value, paramName, param); break;
- case "to": if (value > param) invalid(annotation, value, paramName, param); break;
+ case "from":
+ if (value < param) {
+ invalid(annotation, value, paramName, param);
+ }
+ break;
+ case "to":
+ if (value > param) {
+ invalid(annotation, value, paramName, param);
+ }
+ break;
+ }
+ }
+
+ /**
+ * Validate a long value with two parameters.
+ */
+ public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
+ String paramName1, long param1, String paramName2, long param2) {
+ validate(annotation, ignored, value, paramName1, param1);
+ validate(annotation, ignored, value, paramName2, param2);
+ }
+
+ /**
+ * Validate a long value with one parameter.
+ */
+ public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
+ String paramName, long param) {
+ switch (paramName) {
+ case "from":
+ if (value < param) {
+ invalid(annotation, value, paramName, param);
+ }
+ break;
+ case "to":
+ if (value > param) {
+ invalid(annotation, value, paramName, param);
+ }
+ break;
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6d6a148c105d..6061589929d9 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -31,9 +31,11 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE_LOCATION;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.app.AppOpsManager._NUM_OP;
import static android.app.AppOpsManager.modeToName;
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import android.Manifest;
import android.annotation.NonNull;
@@ -49,6 +51,7 @@ import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
+import android.app.AsyncNotedAppOp;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -58,6 +61,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioAttributes;
@@ -69,6 +73,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteCallback;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -86,6 +91,7 @@ import android.util.AtomicFile;
import android.util.KeyValueListParser;
import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -96,6 +102,7 @@ import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
@@ -181,6 +188,8 @@ public class AppOpsService extends IAppOpsService.Stub {
OP_CAMERA,
};
+ private static final int MAX_UNFORWARED_OPS = 10;
+
Context mContext;
final AtomicFile mFile;
final Handler mHandler;
@@ -188,6 +197,29 @@ public class AppOpsService extends IAppOpsService.Stub {
private final AppOpsManagerInternalImpl mAppOpsManagerInternal
= new AppOpsManagerInternalImpl();
+ /**
+ * Registered callbacks, called from {@link #noteAsyncOp}.
+ *
+ * <p>(package name, uid) -> callbacks
+ *
+ * @see #getAsyncNotedOpsKey(String, int)
+ */
+ @GuardedBy("this")
+ private final ArrayMap<Pair<String, Integer>, RemoteCallbackList<IAppOpsAsyncNotedCallback>>
+ mAsyncOpWatchers = new ArrayMap<>();
+
+ /**
+ * Async note-ops collected from {@link #noteAsyncOp} that have not been delivered to a
+ * callback yet.
+ *
+ * <p>(package name, uid) -> list&lt;ops&gt;
+ *
+ * @see #getAsyncNotedOpsKey(String, int)
+ */
+ @GuardedBy("this")
+ private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
+ mUnforwardedAsyncNotedOps = new ArrayMap<>();
+
boolean mWriteScheduled;
boolean mFastWriteScheduled;
final Runnable mWriteRunner = new Runnable() {
@@ -2098,6 +2130,141 @@ public class AppOpsService extends IAppOpsService.Stub {
}
@Override
+ public void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
+ String message) {
+ Preconditions.checkNotNull(message);
+ Preconditions.checkNotNull(packageName);
+ verifyAndGetIsPrivileged(uid, packageName);
+
+ verifyIncomingUid(uid);
+ verifyIncomingOp(opCode);
+
+ int callingUid = Binder.getCallingUid();
+ long now = System.currentTimeMillis();
+
+ if (callingPackageName != null) {
+ verifyAndGetIsPrivileged(callingUid, callingPackageName);
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+ RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
+ callingPackageName, message, now);
+ final boolean[] wasNoteForwarded = {false};
+
+ if (callbacks != null) {
+ callbacks.broadcast((cb) -> {
+ try {
+ cb.opNoted(asyncNotedOp);
+ wasNoteForwarded[0] = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG,
+ "Could not forward noteOp of " + opCode + " to " + packageName
+ + "/" + uid, e);
+ }
+ });
+ }
+
+ if (!wasNoteForwarded[0]) {
+ ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
+ if (unforwardedOps == null) {
+ unforwardedOps = new ArrayList<>(1);
+ mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
+ }
+
+ unforwardedOps.add(asyncNotedOp);
+ if (unforwardedOps.size() > MAX_UNFORWARED_OPS) {
+ unforwardedOps.remove(0);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Compute a key to be used in {@link #mAsyncOpWatchers} and {@link #mUnforwardedAsyncNotedOps}
+ *
+ * @param packageName The package name of the app
+ * @param uid The uid of the app
+ *
+ * @return They key uniquely identifying the app
+ */
+ private @NonNull Pair<String, Integer> getAsyncNotedOpsKey(@NonNull String packageName,
+ int uid) {
+ return new Pair<>(packageName, uid);
+ }
+
+ @Override
+ public void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(callback);
+
+ int uid = Binder.getCallingUid();
+ Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+ verifyAndGetIsPrivileged(uid, packageName);
+
+ synchronized (this) {
+ RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ if (callbacks == null) {
+ callbacks = new RemoteCallbackList<IAppOpsAsyncNotedCallback>() {
+ @Override
+ public void onCallbackDied(IAppOpsAsyncNotedCallback callback) {
+ synchronized (AppOpsService.this) {
+ if (getRegisteredCallbackCount() == 0) {
+ mAsyncOpWatchers.remove(key);
+ }
+ }
+ }
+ };
+ mAsyncOpWatchers.put(key, callbacks);
+ }
+
+ callbacks.register(callback);
+ }
+ }
+
+ @Override
+ public void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(callback);
+
+ int uid = Binder.getCallingUid();
+ Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+ verifyAndGetIsPrivileged(uid, packageName);
+
+ synchronized (this) {
+ RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ if (callbacks != null) {
+ callbacks.unregister(callback);
+ if (callbacks.getRegisteredCallbackCount() == 0) {
+ mAsyncOpWatchers.remove(key);
+ }
+ }
+ }
+ }
+
+ @Override
+ public List<AsyncNotedAppOp> extractAsyncOps(String packageName) {
+ Preconditions.checkNotNull(packageName);
+
+ int uid = Binder.getCallingUid();
+
+ verifyAndGetIsPrivileged(uid, packageName);
+
+ synchronized (this) {
+ return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
+ }
+ }
+
+ @Override
public int startOperation(IBinder token, int code, int uid, String packageName,
boolean startIfModeDefault) {
verifyIncomingUid(uid);
@@ -2340,6 +2507,25 @@ public class AppOpsService extends IAppOpsService.Stub {
return AppOpsManager.permissionToOpCode(permission);
}
+ @Override
+ public boolean shouldCollectNotes(int opCode) {
+ Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode");
+
+ String perm = AppOpsManager.opToPermission(opCode);
+ if (perm == null) {
+ return false;
+ }
+
+ PermissionInfo permInfo;
+ try {
+ permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ return permInfo.getProtection() == PROTECTION_DANGEROUS;
+ }
+
void finishOperationLocked(Op op, boolean finishNested) {
final int opCode = op.op;
final int uid = op.uidState.uid;