From a04b9ab403c0df8b074e5fba5b98037ed6a00140 Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Thu, 13 Feb 2020 09:54:04 -0800 Subject: [PATCH] Improve AppOpsManager documentation ... focusing on public APIs. Test: one docs changes Bug: 136505050 Change-Id: I90215a638a3c380fcffde2b9209c4386f8fd70ea --- api/current.txt | 4 +- api/system-current.txt | 4 +- api/test-current.txt | 4 +- core/java/android/app/AppOpsManager.java | 167 +++++++++++++++-------- 4 files changed, 113 insertions(+), 66 deletions(-) diff --git a/api/current.txt b/api/current.txt index 5e8974fb82d70..d2998e9e08a7a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4354,7 +4354,7 @@ package android.app { public class AppOpsManager { method @Deprecated public int checkOp(@NonNull String, int, @NonNull String); method @Deprecated public int checkOpNoThrow(@NonNull String, int, @NonNull String); - method public void checkPackage(int, @NonNull String); + method @Deprecated public void checkPackage(int, @NonNull String); method @Deprecated public void finishOp(@NonNull String, int, @NonNull String); method public void finishOp(@NonNull String, int, @NonNull String, @Nullable String); method public boolean isOpActive(@NonNull String, int, @NonNull String); @@ -4367,7 +4367,7 @@ package android.app { 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, @Nullable String); - method public static String permissionToOp(String); + method @Nullable public static String permissionToOp(@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, @Nullable String); diff --git a/api/system-current.txt b/api/system-current.txt index 8725538f1fd8e..6b734819d288a 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -370,8 +370,8 @@ package android.app { method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List getPackagesForOps(@Nullable String[]); method public static int opToDefaultMode(@NonNull String); method @Nullable public static String opToPermission(@NonNull String); - method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(String, int, String, int); - method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(String, int, int); + method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int); + method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int); field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility"; field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; diff --git a/api/test-current.txt b/api/test-current.txt index 4c8bb0290caef..93f650d12de96 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -178,8 +178,8 @@ package android.app { method @RequiresPermission("android.permission.MANAGE_APPOPS") public void resetHistoryParameters(); method @RequiresPermission("android.permission.MANAGE_APPOPS") public void setHistoryParameters(int, long, int); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(int, int, String, int); - method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(String, int, String, int); - method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(String, int, int); + method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int); + method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int); method public static int strOpToOp(@NonNull String); field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0 field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1 diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1de68ba56c6ac..90e6ad3a83abd 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -90,15 +90,77 @@ import java.util.function.Consumer; import java.util.function.Supplier; /** - * AppOps are mappings of [package/uid, op-name] -> [mode]. The list of existing appops is defined - * by the system and cannot be amended by apps. Only system apps can change appop-modes. + * App-ops are used for two purposes: Access control and tracking. * - *

Beside a mode the system tracks when an op was {@link #noteOp noted}. The tracked data can - * only be read by system components. + *

App-ops cover a wide variety of functionality from helping with runtime permissions access + * control and tracking to battery consumption tracking. * - *

Installed apps can usually only listen to changes and events on their own ops. E.g. - * {@link AppOpsCollector} allows to get a callback each time an app called {@link #noteOp} or - * {@link #startOp} for an op belonging to the app. + *

Access control

+ * + *

App-ops can either be controlled for each uid or for each package. Which one is used depends + * on the API provider maintaining this app-op. For any security or privacy related app-op the + * provider needs to control the app-op for per uid as all security and privacy is based on uid in + * Android. + * + *

To control access the app-op can be set to a mode to: + *

+ *
{@link #MODE_DEFAULT} + *
Default behavior, might differ from app-op or app-op + *
{@link #MODE_ALLOWED} + *
Allow the access + *
{@link #MODE_IGNORED} + *
Don't allow the access, i.e. don't perform the requested action or return no or dummy + * data + *
{@link #MODE_ERRORED} + *
Throw a {@link SecurityException} on access. This can be suppressed by using a + * {@code ...noThrow} method to check the mode + *
+ * + *

API providers need to check the mode returned by {@link #noteOp} if they are are allowing + * access to operations gated by the app-op. {@link #unsafeCheckOp} should be used to check the + * mode if no access is granted. E.g. this can be used for displaying app-op state in the UI or + * when checking the state before later calling {@link #noteOp} anyway. + * + *

If an operation refers to a time span (e.g. a audio-recording session) the API provider + * should use {@link #startOp} and {@link #finishOp} instead of {@link #noteOp}. + * + *

Runtime permissions and app-ops

+ * + *

Each platform defined runtime permission (beside background modifiers) has an associated app + * op which is used for tracking but also to allow for silent failures. I.e. if the runtime + * permission is denied the caller gets a {@link SecurityException}, but if the permission is + * granted and the app-op is {@link #MODE_IGNORED} then the callers gets dummy behavior, e.g. + * location callbacks would not happen. + * + *

App-op permissions

+ * + *

App-ops permissions are platform defined permissions that can be overridden. The security + * check for app-op permissions should by {@link #MODE_DEFAULT default} check the permission grant + * state. If the app-op state is set to {@link #MODE_ALLOWED} or {@link #MODE_IGNORED} the app-op + * state should be checked instead of the permission grant state. + * + *

This functionality allows to grant access by default to apps fulfilling the requirements for + * a certain permission level. Still the behavior can be overridden when needed. + * + *

Tracking

+ * + *

App-ops track many important events, including all accesses to runtime permission protected + * APIs. This is done by tracking when an app-op was {@link #noteOp noted} or + * {@link #startOp started}. The tracked data can only be read by system components. + * + *

Only {@link #noteOp}/{@link #startOp} are tracked; {@link #unsafeCheckOp} is not tracked. + * Hence it is important to eventually call {@link #noteOp} or {@link #startOp} when providing + * access to protected operations or data. + * + *

Some apps are forwarding access to other apps. E.g. an app might get the location from the + * system's location provider and then send the location further to a 3rd app. In this case the + * app passing on the data needs to call {@link #noteProxyOp} to signal the access proxying. This + * might also make sense inside of a single app if the access is forwarded between two features of + * the app. + * + *

An app can register an {@link AppOpsCollector} to get informed about what accesses the + * system is tracking for it. As each runtime permission has an associated app-op this API is + * particularly useful for an app that want to find unexpected private data accesses. */ @SystemService(Context.APP_OPS_SERVICE) public class AppOpsManager { @@ -114,28 +176,6 @@ public class AppOpsManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) public static final long CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE = 148180766L; - /** - *

App ops allows callers to:

- * - * - * - *

Each operation is identified by a single integer; these integers are a fixed set of - * operations, enumerated by the OP_* constants. - * - *

When checking operations, the result is a "mode" integer indicating the current - * setting for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute - * the operation but fake its behavior enough so that the caller doesn't crash), - * MODE_ERRORED (throw a SecurityException back to the caller; the normal operation calls - * will do this for you). - */ - final Context mContext; @UnsupportedAppUsage @@ -1024,7 +1064,7 @@ public class AppOpsManager { /** Required to draw on top of other apps. */ public static final String OPSTR_SYSTEM_ALERT_WINDOW = "android:system_alert_window"; - /** Required to write/modify/update system settingss. */ + /** Required to write/modify/update system settings. */ public static final String OPSTR_WRITE_SETTINGS = "android:write_settings"; /** @hide Get device accounts. */ @@ -6042,7 +6082,7 @@ public class AppOpsManager { */ public interface OnOpActiveChangedListener { /** - * Called when the active state of an app op changes. + * Called when the active state of an app-op changes. * * @param op The operation that changed. * @param packageName The package performing the operation. @@ -6333,7 +6373,7 @@ public class AppOpsManager { @SystemApi @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) - public void setUidMode(String appOp, int uid, @Mode int mode) { + public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) { try { mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode); } catch (RemoteException e) { @@ -6388,7 +6428,8 @@ public class AppOpsManager { @TestApi @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) - public void setMode(String op, int uid, String packageName, @Mode int mode) { + public void setMode(@NonNull String op, int uid, @Nullable String packageName, + @Mode int mode) { try { mService.setMode(strOpToOp(op), uid, packageName, mode); } catch (RemoteException e) { @@ -6431,16 +6472,17 @@ public class AppOpsManager { } /** - * Gets the app op name associated with a given permission. - * The app op name is one of the public constants defined + * Gets the app-op name associated with a given permission. + * + *

The app-op name is one of the public constants defined * in this class such as {@link #OPSTR_COARSE_LOCATION}. * This API is intended to be used for mapping runtime - * permissions to the corresponding app op. + * permissions to the corresponding app-op. * * @param permission The permission. - * @return The app op associated with the permission or null. + * @return The app-op associated with the permission or {@code null}. */ - public static String permissionToOp(String permission) { + public static @Nullable String permissionToOp(@NonNull String permission) { final Integer opCode = sPermToOp.get(permission); if (opCode == null) { return null; @@ -6558,7 +6600,7 @@ public class AppOpsManager { } /** - * Start watching for changes to the active state of app ops. An app op may be + * Start watching for changes to the active state of app-ops. An app-op may be * long running and it has a clear start and stop delimiters. If an op is being * started or stopped by any package you will get a callback. To change the * watched ops for a registered callback you need to unregister and register it @@ -6617,7 +6659,7 @@ public class AppOpsManager { } /** - * Stop watching for changes to the active state of an app op. An app op may be + * Stop watching for changes to the active state of an app-op. An app-op may be * long running and it has a clear start and stop delimiters. Unregistering a * non-registered callback has no effect. * @@ -6848,7 +6890,8 @@ public class AppOpsManager { * @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 featureId The feature in the app or {@code null} for default feature + * @param featureId The {@link Context#createFeatureContext feature} in the package or {@code + * null} for default feature * @param message A message describing the reason the op was noted * * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or @@ -6923,6 +6966,8 @@ public class AppOpsManager { * @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 featureId The {@link Context#createFeatureContext feature} in the package or {@code + * null} for default feature * @param message A message describing the reason the op was noted * * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or @@ -6930,8 +6975,8 @@ public class AppOpsManager { * causing the app to crash). */ public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName, - @Nullable String feature, @Nullable String message) { - return noteOpNoThrow(strOpToOp(op), uid, packageName, feature, message); + @Nullable String featureId, @Nullable String message) { + return noteOpNoThrow(strOpToOp(op), uid, packageName, featureId, message); } /** @@ -7200,11 +7245,9 @@ public class AppOpsManager { } /** - * Do a quick check to validate if a package name belongs to a UID. - * - * @throws SecurityException if the package name doesn't belong to the given - * UID, or if ownership cannot be verified. + * @deprecated Use {@link PackageManager#getPackageUid} instead */ + @Deprecated public void checkPackage(int uid, @NonNull String packageName) { try { if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) { @@ -7312,7 +7355,8 @@ public class AppOpsManager { * @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 featureId The feature in the app or {@code null} for default feature + * @param featureId The {@link Context#createFeatureContext feature} in the package or {@code + * null} for default feature * @param message Description why op was started * * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or @@ -7503,7 +7547,8 @@ public class AppOpsManager { } /** - * Checks whether the given op for a package is active. + * Checks whether the given op for a package is active, i.e. did someone call {@link #startOp} + * without {@link #finishOp} yet. *

* If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS} * permission you can query only for your UID. @@ -7828,18 +7873,19 @@ public class AppOpsManager { } /** - * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted - * appops. I.e. each time any app calls {@link #noteOp} or {@link #startOp} one of the callback - * methods of this object is called. + * Callback an app can {@link #setNotedAppOpsCollector register} to monitor the app-ops the + * system has tracked for it. I.e. each time any app calls {@link #noteOp} or {@link #startOp} + * one of the callback methods of this object is called. * - *

Only appops related to dangerous permissions are collected. + *

There will be a callback for all app-ops related to runtime permissions, but not + * necessarily for all other app-ops. * *

      * setNotedAppOpsCollector(new AppOpsCollector() {
      *     ArraySet> opsNotedForThisProcess = new ArraySet<>();
      *
      *     private synchronized void addAccess(String op, String accessLocation) {
-     *         // Ops are often noted when permission protected APIs were called.
+     *         // Ops are often noted when runtime permission protected APIs were called.
      *         // In this case permissionToOp() allows to resolve the permission<->op
      *         opsNotedForThisProcess.add(new Pair(accessType, accessLocation));
      *     }
@@ -7881,20 +7927,21 @@ public class AppOpsManager {
         }
 
         /**
-         * Called when an app-op was noted for this package inside of a two-way binder-call.
+         * Called when an app-op was {@link #noteOp noted} for this package inside of a synchronous
+         * API call, i.e. a API call that returned data or waited until the action was performed.
          *
-         * 

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. + *

Called on the calling thread before the API returns. 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 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. + *

This is very similar to {@link #onNoted} only that the tracking was not caused by the + * API provider in a separate process, but by one in the app's own process. * * @param op The op noted */