diff --git a/api/current.txt b/api/current.txt
index 6adddd064fb5a..1c68aaf9620a7 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 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):
This API requires the package with the {@code proxiedPackageName} to belongs to - * {@link Binder#getCallingUid()}. + * @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 noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) { - return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName); + public int noteOp(@NonNull String op, int uid, @Nullable String packageName, + @Nullable String message) { + return noteOp(strOpToOp(op), uid, packageName, message); } /** - * Like {@link #noteProxyOpNoThrow(String, String)} but allows to specify the proxied uid. + * 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(op, uid, packageName, null); + } + + /** + * @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 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). + */ + 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(op, proxiedPackageName, Binder.getCallingUid(), null); + } + + /** + * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead + * + * @hide + */ + @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); + } + + /** + * 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 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 + * + * @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 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; + } + + /** + * 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 noteProxyOp(@NonNull String op, @Nullable String proxiedPackageName, int proxiedUid, + @Nullable String message) { + return noteProxyOp(strOpToOp(op), proxiedPackageName, proxiedUid, message); + } + + /** + * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead + */ + @Deprecated + public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) { + return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid(), null); + } + + /** + * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead + */ + @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}. * *
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(@NonNull String op, @Nullable String proxiedPackageName, - int proxiedUid) { - return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid); - } + public int noteProxyOpNoThrow(int op, @Nullable String proxiedPackageName, int proxiedUid, + @Nullable String message) { + int myUid = Process.myUid(); - /** - * 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. - */ - public int startOp(@NonNull String op, int uid, @NonNull String packageName) { - return startOp(strOpToOp(op), uid, packageName); - } + 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); + } - /** - * Like {@link #startOp} but instead of throwing a {@link SecurityException} it - * returns {@link #MODE_ERRORED}. - */ - public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) { - return startOpNoThrow(strOpToOp(op), uid, packageName); - } - - /** - * 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. - */ - public void finishOp(@NonNull String op, int uid, @NonNull String packageName) { - finishOp(strOpToOp(op), uid, packageName); + 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 not 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 not 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. * *
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):
+ * {@link #noteOp(String, int, String, String)} or + * {@link #startOp(int, int, String, boolean, String)} to cover): *This API requires the package with {@code proxiedPackageName} to belongs to - * {@link Binder#getCallingUid()}. - * - * @hide - */ - public int noteProxyOpNoThrow(int op, String proxiedPackageName) { - return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid()); - } - - /** - * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it - * returns {@link #MODE_ERRORED}. - * @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) { @@ -5376,54 +5515,86 @@ public class AppOpsManager { } } - /** @hide */ - public int startOp(int op) { - return startOp(op, Process.myUid(), mContext.getOpPackageName()); + + /** + * @deprecated use {@link #startOp(String, int, String, String)} instead + */ + @Deprecated + public int startOp(@NonNull String op, int uid, @NonNull String packageName) { + return startOp(op, uid, packageName, 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. + * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead * - * @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. - * @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 startOp(int op, int uid, String packageName) { - return startOp(op, uid, packageName, false); + @Deprecated + public int startOp(int op) { + return startOp(op, Process.myUid(), mContext.getOpPackageName(), false, null); } /** - * 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. + * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead + * + * @hide + */ + @Deprecated + public int startOp(int op, int uid, String packageName) { + return startOp(op, uid, packageName, false, null); + } + + /** + * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead + * + * @hide + */ + @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. + * + * @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 or + * the package is not in the passed in UID. + */ + 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. * * @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}. - * @hide + * @deprecated use {@link #startOpNoThrow(String, int, String, String)} instead */ - public int startOpNoThrow(int op, int uid, String packageName) { - return startOpNoThrow(op, uid, packageName, false); + @Deprecated + public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) { + return startOpNoThrow(op, uid, packageName, 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) { + return startOpNoThrow(op, uid, packageName, false, null); + } + + /** + * @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(); } } /** - * 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 - * in when starting the operation. + * @deprecated Use {@link #finishOp(String, int, String)} instead + * * @hide */ - public void finishOp(int op, int uid, String packageName) { + @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(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, @NonNull String packageName) { try { mService.finishOperation(getToken(mService), op, uid, packageName); } catch (RemoteException e) { @@ -5477,24 +5714,398 @@ 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. *
* 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. + * + *
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. + * + *
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. + * + *
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. + * + *
This is called on the calling side of a two way binder transaction just after the + * transaction returns. + * + *
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}. + * + *
There can only ever be one collector per process. If there currently is a collector + * registered, it will be unregistered. + * + *
Only appops related to dangerous permissions are collected.
+ *
+ * @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 Only appops related to dangerous permissions are collected.
+ */
+ 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
*
@@ -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 0000000000000..ebfefd0ba66c1
--- /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 0000000000000..64f886aa2f1d9
--- /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 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 6fe6e991fb1e7..e24512ac525da 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;
@@ -74,6 +76,16 @@ public final class PermissionChecker {
/* do nothing */
}
+ /**
+ * @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 553372174a4df..2c9333bc1a7c0 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 c74cef85f3eaa..b3b4f784a04a6 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 83f88ad41cfce..12bce8a305f23 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -169,6 +169,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.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e1b55422274af..50487e9e7a991 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 0000000000000..163e4f7127fb6
--- /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 72dbbf3b8b87d..fcf09a9824636 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 (package name, uid) -> callbacks
+ *
+ * @see #getAsyncNotedOpsKey(String, int)
+ */
+ @GuardedBy("this")
+ private final ArrayMap (package name, uid) -> list<ops>
+ *
+ * @see #getAsyncNotedOpsKey(String, int)
+ */
+ @GuardedBy("this")
+ private final ArrayMap