Summary
This contribution adds two artifacts to ALEAPP and corrects a third. Blocked Numbers reads the table in which Android keeps the numbers a user blocked. Android Auto - Rejected Cars reads the second car table in the Android Auto database. The existing Android Auto - Allowed Cars artifact now reads every Android Auto database it finds, one per Android user, where before it read the first match only.
The research images gave an uneven picture, and this post reports it as it was. Blocked Numbers held rows on three of four Android 16 images. Allowed Cars held rows on one of four. Rejected Cars held no row on any image we examined, so its row handling has only been exercised on constructed data.
- 2New artifacts
- 1Existing artifact changed
- +677 / -20Lines changed in 18 files
- 7New synthetic test cases
The issue
Two questions come up in phone examinations: did the user block a number, and has this phone been set up with a particular car. Both have a home in the Android system, and both were gaps in the report.
Blocked numbers live in the database of the Android BlockedNumberProvider. ALEAPP had no artifact for it, so a report simply had no entry. A reader could not tell “no numbers were blocked” from “this parser does not look”. The list is short, so the cost was not volume but silence.
The Android Auto car database had two separate problems. First, carservicedata.db holds an allowedcars table and a rejectedcars table with the same columns, and the existing artifact read only the first; its own notes said so. Second, the artifact took the first matching database. A phone with a second Android user, for example a secure-folder user, keeps a second copy of the app data under another user number. The artifact read one copy and dropped the other without a message. The pull request puts it plainly: the existing artifact silently dropped a second user's database. A report that shows no error and fewer rows than the phone holds is the failure that is hardest to notice.
Figure 1 shows the mechanism with invented values: two Android users, each with a car database.
Show this figure as a table
| View | Android user 0 | Android user 150 | What the report shows |
|---|---|---|---|
| Before | read (2 allowed rows) | not read | Allowed Cars only, no Source File |
| After | read (2 allowed, 2 rejected) | read (1 allowed, 1 rejected) | Allowed Cars 3 rows and Rejected Cars 3 rows, each with Source File |
The essence
Blocked Numbers reports the blocked table as stored: an ID, the number as entered, and its E.164 form, plus the file the row came from. The table has no timestamp, so the artifact has no time column and says so in its notes. Android Auto - Rejected Cars reads the rejectedcars table with the same code and the same 13 columns as Allowed Cars.
Both Android Auto artifacts now read every carservicedata.db found, once per Android user, with duplicate storage views of one copy collapsed to one, and add a Source File column. A database without the table reports no rows instead of an error, and a blocked-numbers database from an older schema without an e164_number column is read with that column blank.
| Artifact | Source table | Report fields | Time column |
|---|---|---|---|
| Blocked Numbers (new) | blocked in blockednumbers.db | ID, Original Number, E.164 Number, Source File | none; the table has no timestamp |
| Android Auto - Rejected Cars (new) | rejectedcars in carservicedata.db | 12 car and head unit fields, Source File (13 columns) | connectiontime, Unix milliseconds shown in UTC |
| Android Auto - Allowed Cars (changed) | allowedcars in carservicedata.db | same 13 columns; every database read; Source File added | connectiontime, as above |
Our methodology
We started from the file, not the artifact list. For each store we opened the database read-only, listed its tables and columns, and compared the schema across the images we had. For the car database that showed rejectedcars with the same columns as allowedcars in every copy examined: 20 copies in 11 research images, with 16 to 24 columns depending on the app version. For blocked numbers it showed the same three-column table on all 50 copies in 34 research images.
The meaning of a column came from the Android source, not from guesswork. The artifact notes cite the AOSP BlockedNumberProvider: unblocking a number deletes its row, and _id is an auto-increment key that is not reused. So a gap in the ID sequence is consistent with rows having been removed, and the notes add that the table keeps no record of what a missing ID held. On the first Android 16 image the IDs ran from 1 to 126 without a gap. On the third and fourth they reached 612 and 960 over 561 and 958 rows. In the same notes, six numbers of five or six digits had an empty E.164 value and the other 120 had a plus-prefixed one; the notes call that an observation on one database, not a rule for the column.
Checks came in four kinds. Five synthetic cases with invented values cover two Android users plus a duplicate storage view, an older schema without e164_number, an empty table, a car database with both tables, and one without rejectedcars. An independent count(*) on the source tables, with none of the parser's code, gave the same row counts on all four images, and the eight connectiontime values were all 13-digit integers, read as Unix milliseconds. The tool's output checker found nothing to look at on the full reports. And a doubling test placed the same databases under a second Android user: Blocked Numbers read 1,122 rows from 561, and Allowed Cars 16 from 8, exactly double. The suite passed 139 cases with no failure, seven more than before the change.
Figure 2 shows the steps; Figure 3 shows the counts and the doubling test.
Show this figure as a table
| Step | Blocked Numbers | Android Auto cars |
|---|---|---|
| Source file | blockednumbers.db, one per Android user | carservicedata.db, one per Android user |
| Collapse views | unique_files() | unique_files() |
| Read | SELECT from blocked; missing e164_number reads blank | check sqlite_master, then SELECT from allowedcars or rejectedcars |
| Report | ID, Original Number, E.164 Number, Source File | connectiontime, 11 car and head unit columns, Source File (13 columns) |
Show this figure as a table
| Measure | Value |
|---|---|
| Blocked Numbers rows, image 1 to 4 | 126, 0, 561, 958 |
| Blocked Numbers, one user to two users (image 3) | 561 to 1,122 |
| Allowed Cars, one user to two users | 8 to 16 |
The value
An examiner can now answer two questions from the report instead of from a manual query. Which numbers are on the Android block list, with their stored and E.164 forms? And which cars does the Android Auto app hold in each of its two tables, for every Android user on the phone? A second user's cars no longer vanish from the report, and a reader can see which copy a row came from.
The limits matter as much as the new rows. The artifact reads the Android provider's list only; the Samsung dialer keeps its own block list, which is not read. A block list has no time, so the report cannot say when a number was blocked, and a number missing from it was not necessarily never blocked. For the cars, the app is closed source: what places a car in either table, and what event connectiontime marks, is not established, so a row is an indicator of a record in the app, not proof of a completed connection. An empty rejected-cars table is not evidence that no car was ever rejected. Figure 4 lists where rows were, and were not, seen.
Show this figure as a table
| Artifact | Image 1 | Image 2 | Image 3 | Image 4 | Other research images |
|---|---|---|---|---|---|
| Blocked Numbers | 126 | 0 | 561 | 958 | 2 of 34 images hold rows; 50 copies |
| Allowed Cars | 0 | no database | 8 | 0 | 15 rows in 8 of 11 images |
| Rejected Cars | 0 | no database | 0 | 0 | 20 copies in 11 images, no rows |
Where this sits in the Lab's research
No Lab report is devoted to these stores yet; the method above comes from the pull request. They appear as case studies in Lab Technical Report LAB-TR-0006, Section 7.1 (counts per image and the empty rejected-cars table) and Section 6 (the doubling test). That report states that the Android Auto artifacts had the same first-match pattern and that no real image held a second user's Android Auto database, so the second-user case is shown on constructed data only.
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; 18 files, +677 and -20 lines | Pull request 1780 metadata (mergedAt, files, additions, deletions) |
| Blocked Numbers 126 / 0 / 561 / 958 rows; Allowed Cars 0 / no database / 8 / 0; Rejected Cars 0 / no database / 0 / 0 | PR body, test record bullet 1; LAB-TR-0006 Table 8 |
| Independent count(*) gives the same numbers; eight connectiontime values are 13-digit integers | PR body, test record bullet 1 |
| Doubling test: 561 to 1,122 and 8 to 16 | PR body, bullet 3; LAB-TR-0006 Section 6.2 |
| 20 copies of carservicedata.db in 11 images, rejectedcars present, no rows, 16 to 24 columns | Diff, notes of extract_android_auto_rejected in androidauto.py |
| 50 copies of blockednumbers.db in 34 images, two images hold rows (1 and 6), none with a second user | Diff, notes of get_blocked_numbers in blockedNumbers.py |
| 15 rows in 8 of 11 images; 8 rows on one further Android 16 image | Diff, notes of extract_android_auto in androidauto.py |
| IDs 1 to 126 without gap; 612 and 960 over 561 and 958 rows; 6 empty and 120 plus-prefixed E.164 values | PR body, test record row Independent check of the notes; blockedNumbers.py notes |
| Unblocking deletes the row; _id is AUTOINCREMENT | blockedNumbers.py notes, citing the AOSP BlockedNumberProvider source |
| run_test_cases.py PASS=139, upstream PASS=132, seven new cases | PR body, test record table |
| Samsung dialer's own block list is not read | PR body, section Anything reviewers should know |
| Five synthetic cases with invented values; users 0 and 150 in the fixture | Diff, admin/test/cases/testdata.blockedNumbers.json and testdata.androidauto.json |
| Figures 1 and 2 row counts and steps | Figure 1: synthetic example; Figure 2: diagram of androidauto.py and blockedNumbers.py |