App Ops
Underneath the permission dialog is a second, finer-grained switchboard that records every operation and when it happened. The Privacy Dashboard reads it, data access auditing reads it, and adb shell appops lets you inspect it.
A permission is a coarse question: may this app use the camera? App Ops is the finer one: did it, when, and under what conditions.
TL;DR#
- AppOps is a system service that tracks per-operation state:
allow,ignore,foreground,default.1 - It predates runtime permissions. It was an internal control long before it had a user interface.
- Every runtime permission is backed by one or more app ops, but the mapping is not one-to-one.
adb shell appops get <package>shows the state and the last-use time of each operation.- Data access auditing (Android 11) lets an app observe its own app ops events through
AppOpsManager. - The Privacy Dashboard (Android 12) is a user-facing view over the same recorded data.
Mental model#
Think of a building's key-card system versus its security log.
The key card is a permission: it decides whether a door opens. Revoking it is a deliberate, coarse act.
The security log is App Ops: every attempt, every success, and every timestamp. It does not decide anything on its own — though it can be configured to, which is where it blurs into a permission system.
A building can have doors with no card reader (unprotected APIs), doors with a reader (permissions), and a log that records both. That is roughly how Android is organised.
Where the analogy breaks#
A security log does not usually have a "this door is currently disabled" state. App Ops does: the
ignore mode refuses operations, and the foreground mode restricts them to while the app is
visible. So App Ops is both an audit log and a second permission system, which is exactly why it is
confusing.
The big picture#
Text description of this diagram
An annotated appops get output for a messaging app, with seven lines. READ_CONTACTS is allow, last used 24 October 2026. POST_NOTIFICATION is allow. CAMERA is ignore, meaning the user denied the permission and the operation is refused. RECORD_AUDIO is foreground, meaning it is permitted only while the app is visible. ACCESS_FINE_LOCATION is allow. READ_MEDIA_IMAGES is allow, and the old READ_EXTERNAL_STORAGE op is no longer consulted for apps targeting API 33. BOOT_COMPLETED is allow, showing that app ops gate many system behaviours beyond privacy. A legend explains allow, ignore, foreground and default. A timeline below lists the API levels at which AppOps gained runtime permission backing, foreground-only modes, auto-reset, the Privacy Dashboard and data access auditing.
Click any line to see what that state means. ignore and default are the two that people confuse:
default defers to the permission, ignore refuses regardless.
Deep dive#
What an op is#
An operation is a named capability, finer than a permission. CAMERA is a permission and an op;
RECORD_AUDIO likewise. But there are also ops with no permission at all — BOOT_COMPLETED,
VIBRATE, SYSTEM_ALERT_WINDOW — and permissions that map to several ops.
Each op has a mode per UID:
| Mode | Meaning |
|---|---|
default | Defer to the package's permission state |
allow | Permitted |
ignore | Refused |
foreground | Permitted only while the app is in the foreground |
Reading it#
adb shell appops get com.example.messengerREAD_CONTACTS: allow; time=+1d2h3m4s ago
POST_NOTIFICATION: allow; time=+2h11m ago
CAMERA: ignore; time=+1d9h ago
RECORD_AUDIO: foreground; time=+8m ago
ACCESS_FINE_LOCATION: allow; time=+3d ago
READ_MEDIA_IMAGES: allow; time=+2d4h ago
BOOT_COMPLETED: allow; time=+26d agoFour things to notice:
- The time is the last time the operation was accessed, not the last time it was granted. That is what the Privacy Dashboard plots.
ignoreis not an error state; it is a decision.foregroundis the appop shape of "while using the app".BOOT_COMPLETEDhas nothing to do with privacy and is still an op.
You can also query a single op, set one on a debug build, and watch the state change:
adb shell appops get com.example.messenger CAMERA
adb shell appops set com.example.messenger CAMERA ignore
adb shell appops set com.example.messenger CAMERA defaultData access auditing#
Android 11 added a way for an app to be told about its own accesses:2
val appOps = getSystemService(AppOpsManager::class.java)
appOps.startWatchingActive(
arrayOf(AppOpsManager.OPSTR_CAMERA, AppOpsManager.OPSTR_RECORD_AUDIO),
mainExecutor,
object : AppOpsManager.OnOpActiveChangedListener {
override fun onOpActiveChanged(
op: String,
uid: Int,
packageName: String,
active: Boolean,
) {
// The camera or microphone just started or stopped being used.
}
},
)There is also a self-reporting path: an app can wrap its own access in
AppOpsManager.noteOp()-style reporting with an attestation, so that a system component can verify
the access was attributed correctly. That is the mechanism behind "the app told the system why it
used the camera".2
The practical use for an app is transparency: show the user a history of your own camera and microphone use, rather than making them go to Settings.
The Privacy Dashboard#
The Dashboard (Android 12) is a Settings screen that shows camera, microphone and location access for the last 24 hours, per app, with timestamps.3 It is not a new data source: it is a rendering of the same recorded op usage.
That is the design pattern worth naming. Android did not need a new privacy database; it needed to make an existing internal one legible. The same is true of the status bar indicators, which read the active op state rather than a separate signal.3
Why app ops exist at all#
Three reasons, in historical order:
- Finer granularity than permissions. A permission is package-level; an op can be per-UID and per-operation, and can be toggled by system policy without touching the package's permission state.
- Audit. Recording the last access time is what makes "which apps used my camera" answerable.
- Policy enforcement points. System components — a device policy controller, a parental control app, the privacy toggles — can restrict an operation without revoking a permission.
The third reason is why the two systems sometimes disagree, and why "the permission is granted but it still does not work" is a real diagnosis.
App ops in the AOSP tree#
The service lives in system_server and is reachable through
IAppOpsService. Its state is persisted so that a reboot does not reset a user's choices, which is
also why a corrupted ops file can produce very strange privacy behaviour.
The three things app ops are used for#
It helps to separate three jobs that the same service does, because they explain different behaviours.
Recording. Every access is timestamped. This is what the Privacy Dashboard reads and what data access auditing exposes to the app itself. The record is per-UID, not per-process, so a shared UID (which is rare but real) shares the record.
Deciding. The mode is consulted on each access. ignore refuses, foreground restricts, and
default defers to the permission. This is the part that behaves like a second permission system.
Attributing. When an app reports an access proactively, the op records the attribution — which package actually performed the operation on behalf of which other package. That is how a system component can distinguish the app that used the camera from the SDK that asked for it.
The third job is the newest and the least understood. It is also the one most likely to matter as SDK isolation improves: knowing who performed an access, not just which app's permission covered it, is the difference between a privacy model and an audit log.
The failure mode to watch for#
An app that reads checkPermission() and assumes the operation will succeed will be wrong on
devices where an op is restricted without the permission changing. The defensive pattern is to
attempt the operation and handle the empty result, rather than to gate on the permission and assume
the gate is the whole story.
What to log about your own accesses#
Data access auditing is most useful when an app uses it for transparency rather than for debugging. A settings screen that shows "camera used 3 times in the last 24 hours, most recently at 18:04" is a stronger privacy signal than a paragraph of policy text, and the data is already being recorded.
The implementation is small: register a listener for the ops you care about, persist the events you want to show, and render them. The constraint is that the callback reports activity, not content — you learn that the camera was active, not what it captured, which is exactly the right level of detail for a transparency feature.
What the ops tell you about a device#
Dumping app ops for a package is a quick way to answer questions that would otherwise require guesswork: has this app ever used the microphone on this device, and when? The timestamps survive reboots, so the answer can cover weeks.
That persistence has a privacy implication of its own. The record is on the device, readable by the system and by a privileged app ops reader, and it is the same record the Dashboard presents. Clearing an app's data does not necessarily clear its op history, which is a detail worth knowing when a user reports that a revoked permission still "looks used".
How it evolved#
| API | Version | What changed |
|---|---|---|
| 18 | 4.3 Jelly Bean |
|
| 23 | 6.0 Marshmallow |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 33 | 13 Tiramisu |
|
| 37 | 17 Cinnamon Bun |
|
Under the hood#
- AppOpsService.javathe service: mode storage, evaluation and the access log
- AppOpsManager.javathe client API, op constants and the auditing callbacks
- where the indicators and the dashboard data are assembled
Curiosity corner#
- App Ops shipped with a hidden Settings UI in Android 4.3, was removed from view, and then returned years later as the Privacy Dashboard.
- The
foregroundmode predates the "while using the app" permission option; it existed as an ops mode before the dialog offered it. - Some ops have no permission, which is why
appops getlists things you never declared in a manifest.
Try it#
adb shell appops get com.example.app
Example output
CAMERA: ignore; time=+1d9h ago
RECORD_AUDIO: foreground; time=+8m agoadb shell appops set com.example.app CAMERA ignore
adb shell appops set com.example.app CAMERA default
Build it#
Take any app that uses the camera, set the camera op to ignore, and use the app. Whatever it does
— crash, show a black preview, or explain the problem — is its real behaviour when a user denies
the permission. That is a two-minute test that most apps fail.
Check yourself#
Check yourself
Put these privacy changes in the order they arrived.
Drag, or use the arrows to reorder.
- 1Privacy Dashboard, mic and camera indicators, approximate location
- 2One-time permissions and permission auto-reset
- 3Runtime permissions
- 4Scoped storage and background location as a separate grant
- 5Selected photos access
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsModular system componentssource.android.comaccessed 2026-10-27↩
- docsData access auditingdeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 12developer.android.comaccessed 2026-10-27↩
- docsPermissions on Android | Privacydeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 11developer.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 10developer.android.comaccessed 2026-10-27↩
- docsManifest.permission | API referencedeveloper.android.comaccessed 2026-10-27↩