Open Source / ALEAPP #1816

Permissions, usage, alarms and notifications from bug reports

Which apps could use the microphone, and when did one last use it? An Android bug report holds the answer, spread over eight sections of a text file with millions of lines. This contribution teaches ALEAPP to read those sections into 15 tables, and to say how each number was checked.

  • Daniel Addai, SectraTEK Lab
  • Merged
  • CC BY 4.0

View the pull request on GitHub

Summary

  • ALEAPP now has 15 Bug Report artifacts in four groups: permissions and app ops (6), app usage (3), alarms and jobs (3) and notifications (3). The pull request was merged on 27 October 2026.
  • Values stay as the report printed them. Modes, flags, states and reason codes are not decoded. A UTC column is added only where the report holds what the conversion needs.
  • Row counts were compared with counts from the raw text and with a second parser written by a different method. Cases that no research report contained were built as synthetic fixtures.
  • An empty table has three possible causes: nothing was there, the platform cut the dump, or that build does not print the section. The notes of each artifact say which one applies.
  • 15artifacts added in four groups
  • 135test baselines in 2 case folders
  • 30 of 30table comparisons equal a second parser
  • 15,810permission rows on the largest of two reports, equal to a raw-text count

Methodology note

Drafted with AI assistance and reviewed by the author before publication. Every figure uses synthetic example values or counts taken from the pull request.

The issue

Suppose an examiner has to say which apps could use the microphone on a phone, and when one last did. A bug report can answer both without a full file system extraction, but the answer is spread over several sections. The permissions an app holds are in the package dump. The operations it may perform, and a few lines about when it last performed them, are in the app ops dump. The holder of a role such as the default dialer is in the role dump, and on newer builds the permission manager dump lists grants by app id.

Each dump has its own layout, and the layout changes between versions. The standby bucket list is indented under each user on Android 10 and sits under a "User N App Standby States" heading on Android 15 and 16. Android 10 prints no attribution tag lines. The Samsung reports on Android 16 print no role section at all, so a role table for them is empty and says nothing about the phone. Finding the right heading in a file that can pass two million lines, and reading it by eye, is slow and cannot be repeated or checked by a second examiner.

A tool that reads these dumps must also avoid failing quietly. LAB-TR-0001 describes a single damaged line inside the package dump that made a parser end its package list early: 15 of 284 packages remained and no error was shown. Permissions are read from that same dump, so a short permission table is exactly what an examiner could mistake for a quiet device. This contribution is built around counting every table a second way before it is trusted. Figure 1 shows the reading of one line.

One access line in the report text and the row it becomesLeft: four nested lines of an app ops dump with an access line, and the zone property of the report, all invented. Right: the fields of the App Ops Accesses row, with the printed time kept beside the converted UTC time.In the report text (synthetic example)In the artifact (one row)DUMP OF SERVICE appops:  Uid u0a999:    Package org.example.recorder:      RECORD_AUDIO (allow):        Access: [top-s] 2026-03-14 09:26:53.589 (-5s250ms)[persist.sys.timezone]: [Europe/Paris]Two places in one text file: the app ops dump, and thesystem properties that record the zone of the device.KindAccessUid (as printed)u0a999Numeric Uid10999Packageorg.example.recorderOpRECORD_AUDIOOp ModeallowUid StatetopOp FlagssTime As Printed2026-03-14 09:26:53.589Offset From Dump-5s250msTime (UTC)2026-03-14 08:26:53.589Time minus offset (09:26:53.589 + 5.250 s) gives 09:26:58.839, the dump time.Every access row of one report gives the same dump time.Op Mode is the mode when the dump ran, not when the access happened.Uid State and Op Flags are split from the brackets and are not decoded.
Show this figure as a table
Fields of the row in Figure 1
FieldValue (synthetic example)
KindAccess
Uid (as printed)u0a999
Numeric Uid10999
Packageorg.example.recorder
OpRECORD_AUDIO
Op Modeallow
Uid Statetop
Op Flagss
Time As Printed2026-03-14 09:26:53.589
Offset From Dump-5s250ms
Time (UTC)2026-03-14 08:26:53.589
Figure 1One access line becomes one row. The printed time and the offset stay as printed; the UTC time is added from the zone the report records. Source: the field list of App Ops Accesses in scripts/artifacts/bugreportPermissions.py of the pull request. Values are invented (synthetic example).

The essence

The pull request adds 15 artifacts that read eight sections of the report text in one pass. Each row is what the dump printed for one entry. Modes, flags, states and reason codes are kept as printed. The one name that is added is the standby bucket name, which comes from the platform's published constants and is marked as a mapping, not as something decoded from the dump. A UTC column is filled where the dump gives enough to compute it, and a note column says why it is empty when it is.

A row says that the service listed that entry when the dump ran. It does not say that an app used the access, that a job ran or that an alarm fired.

Artifacts added by the pull request
GroupArtifactsReport sections read
Permissions and App Ops (6)Package Permissions, Permission Definitions, App Ops Modes, App Ops Accesses, Role Holders, Permission Manager Grantspackage, appops, role, permissionmgr
App Usage (3)Usage Intervals, Usage Interval Event Counts, Standby Bucketsusagestats and the CHECKIN USAGESTATS command section
Alarms and Jobs (3)Scheduled Jobs, Pending Alarms, Alarm Historyjobscheduler, alarm
Notifications (3)Notification Records, Notification Listeners, Notification Channelsnotification

Our methodology

We start from the text, not from the platform source. For each service we list the heading shapes found in every report we hold, by Android version and vendor, and write the reader to those shapes. A reader is a small class registered for one section. The shared single-pass reader described in LAB-TR-0001 streams the report once and hands the lines of the open section only to the readers registered for it (Figure 2). A reader keeps bounded rows, cuts long text at a set length and decodes nothing.

Time needs the most care, because these dumps print it in three ways. App ops access lines print a device wall-clock time with milliseconds and no zone, and an offset from the dump in brackets. We keep both as printed and add a UTC value computed with the zone the report itself records. Time minus offset gave the same instant for every row of a report, within a second of the section footer, which is a check that the line was read correctly. Pending alarms print offsets from the dump; their UTC value is computed from the nowRTC value printed in the same dump, so it does not depend on a zone, and it is left empty with a note when nowRTC is not printed. The job scheduler prints offsets and no absolute anchor, so those columns stay as offsets and are not converted.

Pipeline from the bug report text to the artifact columnsFive steps: the report file, the section index, the readers registered for eight sections, bounded tables, and 15 artifacts. Below, the columns of App Ops Accesses.From the report text to the report columnsBug report textdumpstate.txt,bugreport-*.txt orbugreport-*.zip(6 path patterns)Section indexevery section entered:name, lines, durationDUMP TIMEOUT keptSection readerspackage, appops,role, permissionmgr,usagestats,jobscheduler, alarm,notificationBounded tablesrows kept as printedlong text cut at aset lengthno second pass15 artifacts4 groupsHTML, TSV and LAVAoutputUTC from report zoneColumns of App Ops Accesses: printed values first, converted values beside themKind | Uid (as printed) | Numeric Uid | Package | Op | Op Mode | Uid State | Op Flags | Attribution TagTime As Printed | Offset From Dump As Printed | Time (UTC) | Time Note | Duration As Printed | Duration (ms)Proxy Uid | Proxy Package | Proxy Attribution Tag | Running Start As Printed | Start Nesting
Show this figure as a table
Steps of Figure 2
StepWhat happens
1. Find the fileSix path patterns match a plain text, a finished zip, a temporary file or a zip kept on the device.
2. Index the sectionsThe shared reader enters every section with kind, name, first and last line and duration, and stores a DUMP TIMEOUT marker when the platform cut a service.
3. Hand over the linesOnly the readers registered for the open section see its lines: package, appops, role, permissionmgr, usagestats, jobscheduler, alarm, notification.
4. Keep bounded rowsReaders keep rows as printed, cut long text and decode nothing.
5. Present15 artifacts show the tables; a UTC column is added only where the report holds what the conversion needs.
Figure 2The shared reader streams the report once and hands each open section only to its registered readers. Names of files, sections and columns are from the pull request. Source: dumpstate_permissions.py, dumpstate_usagejobs.py and bugreportPermissions.py in the pull request; LAB-TR-0001, section 3 (diagram).

Checking has four layers. First, synthetic fixtures: small invented bug reports, one case per situation. The case names include one per Android layout (a10, a15, a16), a dump cut by the time limit, an empty report, a report without a zone, a file that is not a report, odd bytes, two reports in one run and a zip kept on the device. Between them the two modules have 135 baselines in two case folders, and all pass under TZ=UTC.

Second, an independent recount on five older research reports. Each permission table was compared field by field, source line number included, with a second parser built differently (an indentation tree, and a regular expression for roles): 30 of 30 table-by-report comparisons were equal. The Op Count of App Ops Modes adds up to the raw line count on all five. For usage and jobs, all nine row counts equalled counts from the raw text, and a second regular expression parser agreed field by field.

Third, two newer Android 16 reports were run through the whole tool with no failed artifact, and counts made with our own line rules, not the readers, equalled the artifact rows for Package Permissions and Permission Manager Grants (Figure 3). Fourth, the 198 bug report unit tests of the stack so far pass, and the changed files score 10.00 of 10 on pylint.

Missing and cut data get explicit handling. The package dump of one report ends with a 10,000 ms time-limit marker after the block that holds the permission lines, so none were lost; the log says so whenever a cut dump is read, because a report cut earlier would lose the rest. No research report had a usage, job, alarm or notification dump cut, so that case exists only as a synthetic fixture.

Rows per artifact on the two newest Samsung bug reportsGrouped horizontal bars on a logarithmic scale for twelve of the fifteen artifacts, from 18 rows (Notification Records) to 15,810 rows (Package Permissions).Rows per artifact on two bug reports (logarithmic scale)Report 1Report 21101001,00010,000100,000Rows (each gridline is ten times the one before)Package Permissions12,59815,810Permission Definitions2,9393,201App Ops Modes2,1713,199App Ops Accesses1,6322,667Permission Manager Grants12,11515,278Usage Intervals1,056872Standby Buckets806512Scheduled Jobs480558Pending Alarms109204Alarm History9991,544Notification Records1818Notification Channels609900
Show every artifact of this pull request as a table
Rows per artifact on two bug reports
ArtifactReport 1 rowsReport 2 rows
Package Permissions (Permissions and App Ops)12,59815,810
Permission Definitions (Permissions and App Ops)2,9393,201
App Ops Modes (Permissions and App Ops)2,1713,199
App Ops Accesses (Permissions and App Ops)1,6322,667
Role Holders (Permissions and App Ops)4344
Permission Manager Grants (Permissions and App Ops)12,11515,278
Usage Intervals (App Usage)1,056872
Usage Interval Event Counts (App Usage)3216
Standby Buckets (App Usage)806512
Scheduled Jobs (Alarms and Jobs)480558
Pending Alarms (Alarms and Jobs)109204
Alarm History (Alarms and Jobs)9991,544
Notification Records (Notifications)1818
Notification Listeners (Notifications)1823
Notification Channels (Notifications)609900
Figure 3Rows per artifact on two Android 16 bug reports; twelve of the fifteen artifacts are drawn, all fifteen are in the table. The scale is logarithmic. Source: pull request description, table of rows per artifact for the two newest Samsung reports (counts only, not synthetic values).

The value

The gain is a set of questions that can be asked of a bug report and answered from a table: which packages hold a runtime permission and with which flags; which ops are allowed, ignored or foreground-only for a package; which app holds a role; which apps sit in the rare or restricted standby buckets; what is scheduled to run later; which apps may listen to notifications and which channels an app defined. A bug report can be obtained without a full file system extraction (LAB-TR-0003), so these tables reach questions that would otherwise wait for one.

The limits matter as much as the tables. The app ops dump keeps a few lines per op and package, not a log: on the five older reports the printed access times reached back between 283 and 2,277 days from the dump, so an access that is not listed says nothing about whether it happened. Op Mode is the mode at dump time. In LAB-TR-0003, 99.1 to 99.8 percent of the uid-level mode lines on the Android 15 and 16 reports were "ignore", so the modes table is long but narrow. A pending alarm or a scheduled job shows that the service held it when the dump ran. The notification archive printed 5 of 100 entries on the two newest reports, and the tool logs how many it lists. Permission Manager Grants are per app id, so every package sharing an app id repeats the same rows.

Large tables need care in reading. Together the five older reports gave 59,380 Package Permissions rows, above the 50,000-row limit the report builder sets for an HTML page, so the page left that table out; the TSV and LAVA outputs hold every row. As in earlier ALEAPP bug report work, identifiers such as account names are shown as printed, because the tool runs on the examiner's own evidence. This page shows none.

What the evidence for this pull request coversThree columns: what was checked on research bug reports, what exists only as a synthetic fixture, and what was not examined.What the evidence coversChecked on research reports-30 of 30 table-by-report comparisonsequal a second parser (permissions)-9 of 9 usage, job and alarm row countsequal raw-text counts-App Ops Modes: Op Count adds up to theraw line count on five reports-Package Permissions and Grants countsequal raw-text counts on two moreSynthetic fixtures only-Time zones other than UTC+0, anddaylight saving-A second Android user in usagestatistics-A usage, job, alarm or notification dumpcut by the time limit-A report without nowRTC-More than one holder in one roleNot examined-Android 11 to 14-Vendors other than the four seen-Field values of the app ops, permissionand job tables against a second parseron the two newest reports (counts only)
Show this figure as a table
Evidence coverage
ColumnItems
Checked on research reports30 of 30 table-by-report comparisons equal a second parser (permissions); 9 of 9 usage, job and alarm row counts equal raw-text counts; App Ops Modes: Op Count adds up to the raw line count on five reports; Package Permissions and Grants counts equal raw-text counts on two more
Synthetic fixtures onlyTime zones other than UTC+0, and daylight saving; A second Android user in usage statistics; A usage, job, alarm or notification dump cut by the time limit; A report without nowRTC; More than one holder in one role
Not examinedAndroid 11 to 14; Vendors other than the four seen; Field values of the app ops, permission and job tables against a second parser on the two newest reports (counts only)
Figure 4Evidence ladder. All seven research reports record UTC+0, so every other zone is synthetic. Source: the 'Proven on real reports', 'Constructed only' and 'Not examined' paragraphs of the pull request description (counts only, not synthetic values).

Where this sits in the Lab's research

Two Lab reports cover this work. LAB-TR-0003, Where Android Records Who Used What: App Ops History in Databases, XML Files and Bug Reports (Zenodo, version 1.2, 10.5281/zenodo.23720064), uses the two App Ops artifacts of this pull request for its bug report measurements. It reports 30 table-by-report comparisons for the six permission artifacts, that every access line resolves to one dump instant, and that the dump form is narrow beside the Android 16 history databases. Those databases are read by a separate pull request, ALEAPP #1786, which is in review and is not part of this one.

LAB-TR-0001, Making Silent Failures Visible: A Single-Pass Section Reader for Android Bug Reports (Zenodo, version 1.1, 10.5281/zenodo.23719508), describes the reader these artifacts plug into, the section index and the record of cut dumps. The grouping follows the 22 categories of Android bug reports, category by category.

Sources

Every number and claim on this page, with where it came from. Figure values are either synthetic examples (marked) or counts from the pull request.

Sources of the numbers and claims
Number or claimWhere it came from
15 artifacts in 4 groups (6, 3, 3, 3); eight sections readPull request description, section 'What this changes'; artifact list in scripts/artifacts/bugreportPermissions.py and bugreportUsageJobs.py
Merged on 27 October 2026Pull request state and merge time (mergedAt); the review and comment lists are empty
135 baselines in 2 case folders, all pass; 198 cumulative unit tests; pylint 10.00/10Pull request description, 'Test record' table
Fixture case names (a10, a15, a16, cut, empty, no_zone, not_a_report, odd_bytes, two_reports, zip_on_device)File names under admin/test/cases/data/ in the pull request file list
30 of 30 table-by-report comparisons equal; Op Count equals raw lines; 9 of 9 usage and job counts equalPull request description, 'Proven on real reports'; LAB-TR-0003 section 4.5
Package Permissions 12,598 and 15,810; Permission Manager Grants 12,115 and 15,278; all other row counts in Figure 3Pull request description, table of rows per artifact (two newest Samsung reports)
Seven reports: four Samsung (Android 16), Huawei and Transsion-brand (Android 10), Motorola (Android 15); all UTC+0Pull request description; device classes only
Constructed only and not examined lists (Figure 4)Pull request description, 'Constructed only' and 'Not examined'
Access times reach back 283 to 2,277 days from the dump; Op Mode is the mode at dump time; time minus offset gives one dump instantNotes of App Ops Accesses in bugreportPermissions.py
99.1 to 99.8 percent of uid-level mode lines are 'ignore' on the Android 15 and 16 reports; dump instant spread 0.0 sLAB-TR-0003, Table 3 and section 5.5
Pending alarm UTC = nowRTC + printed offset; job times are offsets and are not convertedNotes of Pending Alarms and Scheduled Jobs in bugreportUsageJobs.py
Package dump of one report ends with a 10,000 ms DUMP TIMEOUT marker after the shared users blockNotes of Package Permissions in bugreportPermissions.py
Role section absent on the Samsung Android 16 reports; standby list layout differs between Android 10 and 15/16Notes of Role Holders and Standby Buckets
59,380 Package Permissions rows on five reports exceed the 50,000-row HTML limit; TSV and LAVA hold the rowsNotes of Package Permissions in bugreportPermissions.py
Notification archive prints 5 of 100 entries on the two newest reportsPull request description, 'Anything reviewers should know'
284 packages reduced to 15 by one damaged line; 2 million lines; HTML, TSV and LAVA outputLAB-TR-0001, section 1 and Listing 1