# App Ops

Go to the related issue (opens new window) for discussion.

Table of Contents

# Background

App Ops (short hand for Application Operations) are used by Android system (since Android 4.3) to control application permissions. The user can control some permissions, but only the permissions that are considered dangerous (and Google thinks knowing your phone number isn't a dangerous thing). So, app ops seems to be the one we need if we want to install apps like Facebook and it's Messenger (which literary records everything) and still want some privacy and/or security. Although certain features of app ops were available in Settings and later in hidden settings in older version of Android, it's completely hidden in newer versions of Android and is continued to be kept hidden. Now, any app with android.Manifest.permission.GET_APP_OPS_STATS permission can get the app ops information for other applications but this permission is hidden from users and can only be enabled using ADB or root. Still, the app with this permission cannot grant or revoke permissions (actually mode of operation) for apps other than itself (with limited capacity, of course). To modify the ops of other app, the app needs android.Manifest.permission.UPDATE_APP_OPS_STATS permissions which isn't accessible via pm command. So, you cannot grant it via root or ADB, the permission is only granted to the system apps. There are very few apps who support disabling permissions via app ops. The best one to my knowledge is AppOpsX (opens new window). The main (visible) difference between my app (AppManager) and this app is that the later also provides you the ability to revoke internet permissions (by writing ip tables). Another difference is that the author used the hidden API to access/grant/revoke ops whereas I used appops command-line tool to do that. I did this because of the limit of Reflection (opens new window) that Android recently imposed which rendered many hidden APIs unusable (there are some hacks but they may not work after the final release of R, I believe). One crucial problem that I faced during developing an API for App Ops is the lack of documentation in English language.

# Introduction to App Ops

How AppOps Works

The figure (taken from this article (opens new window)) above describes the process of changing and processing permission. AppOpsManager can be used to manage permissions in Settings app. AppOpsManager is also useful in determining if a certain permission (or operation) is granted to the application. Most of the methods of AppOpsManager are accessible to the user app but unlike a system app, it can only be used to check permissions for any app or for the app itself and start or terminating certain operations. Moreover, not all operations are actually accessible from this Java class. AppOpsManager holds all the necessary constants such as OP_*, OPSTR_*, MODE_* which describes operation code, operation string and mode of operations respectively. It also holds necessary data stuctures such as PackageOps and OpEntry. PackageOps holds OpEntry for a package, and OpEntry, as the name suggests, describes each operation. Under the hood, AppOpsManager calls AppOpsService to perform any real work.

AppOpService (opens new window) is completely hidden from a user application but acessible to the system applications. As seen in the picture, this is the class that does the actual management stuff. It contains data structures such as Ops to store basic package info and Op which is similar to OpEntry of AppOpsManager. It also has Shell which is actually the source code of the appops command line tool. It writes configurations to or read configurations from /data/system/appops.xml. System services calls AppOpsService to find out what an application is allowed and what is not allowed to perform, and AppOpsService determines these permissions by parsing /data/system/appops.xml. If no custom values are set in appops.xml, it returns the default mode available in AppOpsManager.

# AppOpsManager

AppOpsManager (opens new window) stands for application operations manager. It consists of various constants and classes to modify app operations. Official documentation can be found here (opens new window).

# OP_* Constants

OP_* are the integer constants starting from 0. OP_NONE implies that no operations are specified whereas _NUM_OP denotes the number of operations defined in OP_* prefix. These denotes each operations. But these operations are not necessarily unique. In fact, there are many operations that are actually a single operation denoted by multiple OP_* constant (possibly for future use). Vendors may define their own op based on their requirements. MIUI is one of the vendors who are known to do that.

Sneak-peek of OP_*:

 








 

public static final int OP_NONE = -1;
public static final int OP_COARSE_LOCATION = 0;
public static final int OP_FINE_LOCATION = 1;
public static final int OP_GPS = 2;
public static final int OP_VIBRATE = 3;
...
public static final int OP_READ_DEVICE_IDENTIFIERS = 89;
public static final int OP_ACCESS_MEDIA_LOCATION = 90;
public static final int OP_ACTIVATE_PLATFORM_VPN = 91;
public static final int _NUM_OP = 92;
1
2
3
4
5
6
7
8
9
10

Whether an operation is unique is defined by sOpToSwitch (opens new window). It maps each operation to another operation or to itself (if it's a unique operation). For instance, OP_FINE_LOCATION and OP_GPS are mapped to OP_COARSE_LOCATION.

Each operation has a private name which are described by sOpNames (opens new window). These names are usually the same names as the constants without the OP_ prefix. Some operations have public names as well which are described by sOpToString. For instance, OP_COARSE_LOCATION has the public name android:coarse_location.

As a gradual process of moving permissions to app ops, there are already many permissions that are defined under some operations. These permissions are mapped in sOpPerms (opens new window). For example, the permission android.Manifest.permission.ACCESS_COARSE_LOCATION is mapped to OP_COARSE_LOCATION. Some operations may not have any associated permissions which have null values.

As described in the previous section, operations that are configured for an app are stored at /data/system/appops.xml. If an operation is not configured, then whether system will allow that operation is determined from sOpDefaultMode (opens new window). It lists the default mode for each operation.

# MODE_* Constants

MODE_* constants also integer constants starting from 0. These constants are assigned to each operations describing whether an app is authorised to perform that operation. These modes usually have associated names such as allow for MODE_ALLOWED, ignore for MODE_IGNORED, deny for MODE_ERRORED (a rather misonomer), default for MODE_DEFAULT and foreground for MODE_FOREGROUND.

Default modes:

/**
 * the given caller is allowed to perform the given operation.
 */
public static final int MODE_ALLOWED = 0;
/**
 * the given caller is not allowed to perform the given operation, and this attempt should
 * <em>silently fail</em> (it should not cause the app to crash).
 */
public static final int MODE_IGNORED = 1;
/**
 * the given caller is not allowed to perform the given operation, and this attempt should
 * cause it to have a fatal error, typically a {@link SecurityException}.
 */
public static final int MODE_ERRORED = 1 << 1;  // 2
/**
 * the given caller should use its default security check. This mode is not normally used
 */
public static final int MODE_DEFAULT = 3;
/**
 * Special mode that means "allow only when app is in foreground."
 */
public static final int MODE_FOREGROUND = 1 << 2;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Besides these default modes, vendors can set custom modes such as MODE_ASK (with the name ask) which is actively used by MIUI. MIUI also uses some other modes without any name associated with them.

# PackageOps

AppOpsManager.PackageOps is a data structure to store all the OpEntry for a package. In simple terms, it stores all the customised operations for a package.

public static class PackageOps implements Parcelable {
  private final String mPackageName;
  private final int mUid;
  private final List<OpEntry> mEntries;
  ...
}
1
2
3
4
5
6

As can be seen above, it stores all OpEntry for a package as well as the corresponding package name and it's kernel user ID.

# OpEntry

AppOpsManager.OpEntry is a data structure that stores a single operation for any package.

public static final class OpEntry implements Parcelable {
    private final int mOp;
    private final boolean mRunning;
    private final @Mode int mMode;
    private final @Nullable LongSparseLongArray mAccessTimes;
    private final @Nullable LongSparseLongArray mRejectTimes;
    private final @Nullable LongSparseLongArray mDurations;
    private final @Nullable LongSparseLongArray mProxyUids;
    private final @Nullable LongSparseArray<String> mProxyPackageNames;
    ...
}
1
2
3
4
5
6
7
8
9
10
11

Here:

  • mOp: Denotes one of the OP_* constants.
  • mRunning: Whether the operations is in progress (ie. the operation has started but not finished yet). Not all operations can be started or finished this way.
  • mMOde: One of the MODE_* constants.
  • mAccessTimes: Stores all the available access times
  • mRejectTimes: Stores all the available reject times
  • mDurations: All available access durations, checking this with mRunning will tell you for how long the app is performing a certain app operation.
  • mProxyUids: No documentation found
  • mProxyPackageNames: No documentation found

# Usage

TODO

# AppOpsService

TODO

# appops.xml

Latest appops.xml has the following format: (This DTD is made by me and by no means perfect, has compatibility issues.)

<!DOCTYPE app-ops [

<!ELEMENT app-ops (uid|pkg)*>
<!ATTLIST app-ops v CDATA #IMPLIED>

<!ELEMENT uid (op)*>
<!ATTLIST uid n CDATA #REQUIRED>

<!ELEMENT pkg (uid)*>
<!ATTLIST pkg n CDATA #REQUIRED>

<!ELEMENT uid (op)*>
<!ATTLIST uid
n CDATA #REQUIRED
p CDATA #IMPLIED>

<!ELEMENT op (st)*>
<!ATTLIST op
n CDATA #REQUIRED
m CDATA #REQUIRED>

<!ELEMENT st EMPTY>
<!ATTLIST st
n CDATA #REQUIRED
t CDATA #IMPLIED
r CDATA #IMPLIED
d CDATA #IMPLIED
pp CDATA #IMPLIED
pu CDATA #IMPLIED>

]>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

The instructions below follows the exact order given above:

  • app-ops: The root element. It can contain any number of pkg or package uid
    • v: (optional, integer) The version number (default: NO_VERSION or -1)
  • pkg: Stores package info. It can contain any number of uid
    • n: (required, string) Name of the package
  • Package uid: Stores package or packages info
    • n: (required, integer) The user ID
  • uid: The package user ID. It can contain any number of op
    • n: (required, integer) The user ID
    • p: (optional, boolean) Is the app is a private/system app
  • op: The operation, can contain st or nothing at all
    • n: (required, integer) The op name in integer, ie. AppOpsManager.OP_*
    • m: (required, integer) The op mode, ie. AppOpsManager.MODE_*
  • st: State of operation: whether the operation is accessed, rejected or running (not available on old versions)
    • n: (required, long) Key containing flags and uid
    • t: (optional, long) Access time (default: 0)
    • r: (optional, long) Reject time (default: 0)
    • d: (optional, long) Access duration (default: 0)
    • pp: (optional, string) Proxy package name
    • pu: (optional, integer) Proxy package uid

This definition can be found at AppOpsService (opens new window).

# appops command line interface

appops or cmd appops (on latest versions) can be accessible via ADB or root. This is an easier method to get or update any operation for a package (provided the package name is known). The help page of this command is self explanatory:

AppOps service (appops) commands:
help
  Print this help text.
start [--user <USER_ID>] <PACKAGE | UID> <OP> 
  Starts a given operation for a particular application.
stop [--user <USER_ID>] <PACKAGE | UID> <OP> 
  Stops a given operation for a particular application.
set [--user <USER_ID>] <[--uid] PACKAGE | UID> <OP> <MODE>
  Set the mode for a particular application and operation.
get [--user <USER_ID>] <PACKAGE | UID> [<OP>]
  Return the mode for a particular application and optional operation.
query-op [--user <USER_ID>] <OP> [<MODE>]
  Print all packages that currently have the given op in the given mode.
reset [--user <USER_ID>] [<PACKAGE>]
  Reset the given application or all applications to default modes.
write-settings
  Immediately write pending changes to storage.
read-settings
  Read the last written settings, replacing current state in RAM.
options:
  <PACKAGE> an Android package name or its UID if prefixed by --uid
  <OP>      an AppOps operation.
  <MODE>    one of allow, ignore, deny, or default
  <USER_ID> the user id under which the package is installed. If --user is not
            specified, the current user is assumed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Last Updated: 8/18/2020, 8:28:20 PM