Summary
This contribution adds healthConnect.py, thirteen artifacts for the Android framework Health Connect store: healthconnect.db under system_ce/<user>/healthconnect on Android 14 and later, and the health-permissions-first-grant-times.xml file beside it. Three artifacts cover the registered apps, the store's preferences and the first time each package was granted health permissions. Ten read the records and logs: activity, measurements, heart rate samples, sleep sessions and stages, exercise sessions and route points, and the access, read-access and change logs.
When the pull request was filed, only the first three had rows on real data: every record table was empty on all four Android 16 images, so the ten record artifacts had been checked against the AOSP schema and a synthetic database. The artifact notes in the merged change later add two research stores from other Android versions, on which the record artifacts matched the table row counts. Both statements are listed in the sources table.
- 13New artifacts
- 3 of 13Had rows on the four Android 16 images
- +2,746Lines added in 31 files
- 13New synthetic test cases
The issue
Health Connect is where Android keeps the steps, heart rate, sleep and exercise that fitness apps write, and where other apps can read them with permission. For an examiner it can answer which health apps were registered, what they recorded and which apps read the data. ALEAPP could not read it.
Without a reader, an examiner who wanted those answers opened a SQLite database with many tables by hand. The store has no single table. It has one table per record type, series and session tables joined to a parent row by a key, and logs of who read and wrote. The schema also changes between versions: the stores we examined were versions 12, 20, 23 and 26, and a column present in one is absent in another.
The empty case needs care too. A store can exist and hold no records, and on all four Android 16 images it did. That is not an uninformative result, because the apps and the permission times still show what was registered, but it is not evidence that the person recorded nothing, and it does not show what an app did before the store was reset or trimmed. Figure 1 shows where the data sits.
Show this figure as a table
| Source | Tables | Artifacts |
|---|---|---|
| healthconnect.db | application_info_table, preference_table | Apps, Preferences |
| healthconnect.db | interval and instant record tables | Activity Records, Measurements |
| healthconnect.db | series and session tables | Heart Rate Samples, Sleep Sessions, Sleep Stages, Exercise Sessions, Exercise Route |
| healthconnect.db | access_logs_table, read_access_logs_table, change_logs_table | Access Logs, Read Access Logs, Change Logs |
| health-permissions-first-grant-times.xml | XML entries | Permission First Grant Times |
The essence
Each artifact reads one part of the store and keeps the values the writing app supplied. Numbers that the Android source names, such as record types, exercise types, sleep stages and recording methods, are shown as the stored number followed by the AOSP name; a number the source does not name is shown as stored. Times are converted from milliseconds to UTC with the zone offset the app supplied, and units follow the AOSP record classes.
| Artifact | Source | One row per | Notable fields |
|---|---|---|---|
| Apps | application_info_table | registered app | package, app name, stored icon, record types used |
| Preferences | preference_table | key | key and value as stored |
| Permission First Grant Times | first-grant XML | package or shared-user entry | time as stored and as UTC, Entry Type |
| Activity Records | eight interval record tables | record | steps, distance, energy, floors, elevation, hydration, wheelchair pushes |
| Measurements | sixteen instant record tables | record | weight, height, body composition, resting heart rate, oxygen, blood pressure, glucose, temperature |
| Heart Rate Samples | series table with its parent | sample | time and beats per minute |
| Sleep Sessions, Sleep Stages | session and stage tables | session, stage | times with zone offsets; stage type named from AOSP |
| Exercise Sessions, Exercise Route | session and route tables | session, route point | type named from AOSP; position as the app supplied |
| Access, Read Access, Change Logs | three log tables | log row | operation and record types named from AOSP; time in UTC |
Our methodology
We began with the schema. We listed the tables of each store and, independently of the parser, listed the non-empty ones. On the four Android 16 images that showed only the application and preference tables and, on the version 26 stores, device and data-source tables. The parser reads each database's schema at run time and substitutes NULL for any column the version lacks, so older stores read without an error.
Names and units came from the AOSP HealthFitness module, at a commit the notes cite. We did not guess units: distance and elevation are in metres, energy in calories (the unit of the AOSP energy class, not kilocalories), hydration in litres. The notes warn that distance and calorie totals can overlap between apps, and that a value is what the writing app supplied, so manual entry or an import from another source is a possibility the examiner must rule out with the recording method, the app and the device columns.
The checks were layered. For the three artifacts that had data on the four images, row counts equalled independent counts: Apps 2, 2, 3 and 3, Preferences 7, 7, 9 and 9, First Grant Times 1, 2, 2 and 1. A second Android user at system_ce/10, built from the real files, doubled all three exactly. The record artifacts were exercised on a synthetic database built by a fixture script with invented values, including two users. In the merged notes, on two other research stores the activity artifact reported 68,617 and 79,052 rows, equal to the rows of the tables it reads, and for every steps row the start time and count equalled the stored values. Thirteen new test cases brought the suite to 145 with no failure. The storage-view mode skipped 90 cases, because this store sits under system_ce, not data/data, so the harness has nothing to restage.
Not everything is read. Speed records and skin temperature records exist in the research stores and no Health Connect artifact reads them; nor do reproductive health, medical record and the version 26 device tables. Figure 2 shows the steps and Figure 3 shows where each artifact has been seen with data.
Show this figure as a table
| Step | What the parser does |
|---|---|
| Find the store | healthconnect.db under system_ce/ |
| Read the schema | table_info of each table; a column the version lacks reads NULL |
| Join and name | application_info_table and device_info_table joined by row id; series and stage tables joined to the parent record; numbers named from AOSP classes |
| Report | milliseconds to UTC; zone offset as +HH:MM; units per the AOSP record classes |
Show this figure as a table
| Artifact | Android 16 images (4) | Store A | Store B |
|---|---|---|---|
| Apps | 2 / 2 / 3 / 3 | 2 | 3 |
| Preferences | 7 / 7 / 9 / 9 | 5 | 3 |
| Permission First Grant Times | 1 / 2 / 2 / 1 | 2 | 3 |
| Activity Records | 0 | 68,617 | 79,052 |
| Measurements | 0 | 535 | 8 |
| Heart Rate Samples | 0 | 169,098 | 35,166 |
| Sleep Sessions | 0 | 98 | 17 |
| Sleep Stages | 0 | 5,957 | 1,025 |
| Exercise Sessions | 0 | 126 | 16 |
| Exercise Route (points) | 0 | 64,533 | 0 |
| Access Logs | 0 | 2,972 | 855 |
| Read Access Logs | 0 | 0 | 0 |
| Change Logs | 0 | 9,957 | 3,472 |
One defect found on real data shows why the real stores mattered. On the two version 26 stores the first-grant file holds a shared-user element and no package element, so the first version of the artifact returned no rows and raised no error. It was found by running the artifact on those stores. The artifact now reads both element types and adds an Entry Type column, and a synthetic case and a unit test cover both. Figure 4 shows the mechanism with an invented file.
Show this figure as a table
| State | Elements read | Rows on the Android 16 images |
|---|---|---|
| Before the fix | package only | 0 on the two version 26 stores |
| After the fix | package and shared-user, with Entry Type | 1, 2, 2, 1 |
The value
With the module an examiner can answer, from the report, which apps are registered with Health Connect and when each package was first granted permission, and, where the store holds records, what was recorded, over which intervals, by which app, with which recording method and on which device. The access and change logs add which app read, wrote or deleted which record types and when. These are indicators of what the store holds. They are not evidence of what the person did.
The limits are specific. A session is what the writing app reported, so a sleep session does not establish that the person slept. A route point is a position the app reported and does not by itself show where the device was. A log row records an access the platform logged, but the retention period is not established and the store trims the change log, so a missing row is not evidence that nothing happened. The record artifacts were verified on two research stores and not on a populated Android 16 store, and the pull request asks anyone with a populated store to check the units before relying on them. Health data is among the most sensitive a phone holds; the Lab reports counts and structure only.
Where this sits in the Lab's research
No Lab report is devoted to this store; the method above comes from the pull request. It is one of five case studies in Lab Technical Report LAB-TR-0006, Section 7.1 and Table 8, which records the apps, preferences and first-grant counts, the empty record tables on all four images, and the ten record artifacts as validated on synthetic data only at the time of that report. The report also lists the first-grant defect among the defects that were reproduced with a synthetic file, fixed and covered by a test.
Cite as: Addai, D. (2026). Empty Is Not Absent: Silent Failure Modes in Open-Source Mobile Forensic Parsers on iOS 26 and Android 16. LAB-TR-0006, version 1.0, preprint. doi:10.5281/zenodo.23717929.
Sources
Every number and claim on this page, and where it comes from. Figures marked synthetic example use invented values.
| Number or claim | Where it comes from |
|---|---|
| Merged 25 October 2026; 31 files, +2,746 and -1 lines | Pull request 1784 metadata (mergedAt, files, additions, deletions) |
| Thirteen artifacts; 13 new test cases; PASS=145, upstream PASS=132 | Diff, __artifacts_v2__ in healthConnect.py; PR body, test record table |
| Every record table empty on four Android 16 images; store versions 23, 20, 26, 26 | PR body, first checklist item; LAB-TR-0006 Section 7.1 |
| Apps 2 / 2 / 3 / 3; Preferences 7 / 7 / 9 / 9; First Grant Times 1 / 2 / 2 / 1 | PR body, second checklist item; LAB-TR-0006 Table 8 |
| Doubling test at system_ce/10 reads the three artifacts exactly double | PR body, fourth checklist item |
| First-grant defect: shared-user element, no package element, 0 rows before; 1, 2, 2, 1 after | PR body, test record row Real-data defect; LAB-TR-0006 defect table |
| Research stores A and B: activity 68,617 and 79,052; measurements 535 and 8; heart rate 169,098 and 35,166; sleep sessions 98 and 17; stages 5,957 and 1,025; exercise 126 and 16; route points 64,533 and 0; access logs 2,972 and 855; change logs 9,957 and 3,472; apps 2 and 3; preferences 5 and 3; first grants 2 and 3 | Diff, notes and sample_data of each artifact in healthConnect.py |
| Store A is Android 15, version 20; store B is Android 14, version 12 | Diff, sample_data of healthconnect_apps |
| Read Access Logs: source-verified, no row on any extraction or store | Diff, notes of healthconnect_read_access_logs |
| Speed records 1,784 rows and skin temperature 65 rows exist and are not read | Diff, notes of healthconnect_activity |
| Units: metres, calories (not kilocalories), litres; names from the AOSP HealthFitness module | Diff, notes of healthconnect_activity and the RECORD_TYPES comment |
| Storage-view run skipped 90 cases because the store is under system_ce | PR body, test record table |
| Request for a populated store to check units | PR body, Anything reviewers should know |
| Figure 4 element and value | Synthetic example, invented for illustration |