Summary
This contribution adds Bluetooth File Transfers, an artifact that reads the btopp table of the Android Bluetooth app's btopp.db. It looks under either Bluetooth package name, reads one database per Android user, and adds a Source File column. Direction, status, visibility, confirmation and scanned are shown as stored, and the notes give what the Android source says each code means.
The honest part comes first. On the four Android 16 images the pull request was written against, btopp.db exists with its full table and holds no rows. The artifact notes in the merged change later widen the count: 40 research images carry the database, 100 copies counting storage views, second users and one rollback copy, and two copies held one row each. So the code has been run on three synthetic cases and on two real rows.
- 1New artifact
- 16Columns in the report
- 3Synthetic test cases
- 2 of 100Copies that held a row
The issue
A familiar question in an examination is whether a phone handed a file to another device nearby. Bluetooth object push is one route. The Android Bluetooth app keeps a table with one row per transfer, and until now an ALEAPP report had no entry for it. An examiner who wanted the answer opened the database by hand, or did not look.
Two traps sit in the way. The first is the path. The database lives under com.android.bluetooth on some builds and com.google.android.bluetooth on others, 14 and 26 of the 40 research images that carry it. A reader that checks one folder finds nothing on the other and says nothing. The second is that the table is usually empty. On all four Android 16 images it was present, complete and empty. An empty table is consistent with no transfer by this route, but it does not show that no file was ever sent, because Quick Share, Nearby Share and messaging apps do not write here.
Figure 1 separates what this table records from what it cannot.
Show this figure as a table
| Mechanism | Record in btopp.db | Path |
|---|---|---|
| Bluetooth app (object push) | yes, one row per transfer | com.android.bluetooth or com.google.android.bluetooth, databases/btopp.db |
| Quick Share, Nearby Share, messaging apps | not examined, not expected | not applicable |
| Rollback copy | read as its own database | data/misc_de/ |
The essence
Bluetooth File Transfers reports one row per row of the btopp table, in each btopp.db found. Timestamp is converted to UTC with the core's unit detection; every other column is reported as stored, with the integers left as integers. The notes record what the AOSP source says the codes mean and say plainly that a device maker's build may differ, which was not examined.
| Report column | Source column | Reported as |
|---|---|---|
| Timestamp | timestamp | UTC; unit detected from magnitude |
| ID | _id | as stored |
| Direction (as stored) | direction | integer; AOSP: 0 outbound, 1 inbound |
| Destination | destination | Bluetooth device address as stored |
| Device Name | device_name | blank where the schema has no such column |
| File Path, Hint, URI, MIME Type | _data, hint, uri, mimetype | as stored; whether the file still exists is not checked |
| Status (as stored) | status | integer; AOSP: 190 pending, 192 running, 200 success, 400 to 599 errors |
| Total Bytes, Current Bytes | total_bytes, current_bytes | as stored |
| Visibility, Confirm, Scanned (as stored) | visibility, confirm, scanned | integers; AOSP meanings in the notes |
| Source File | path of the database | names the copy a row came from |
Our methodology
We began with the schema. We listed the columns of btopp in every copy we could find and found three variants: 14 columns with user version 1 in 29 images, the same 14 plus device_name and reserved with user version 2 in 10 images, and the 14 plus carrier_name in one image. The artifact reads the columns common to the variants and leaves Device Name blank where the column is absent; carrier_name is not read.
Meaning came from the Android source. The notes cite the AOSP Bluetooth module for the direction, visibility, confirm and status codes, and for how the provider fills the timestamp when the inserting code gives none. We did not translate the codes in the report, because whether a device maker's build uses the same values was not examined. The timestamp is read with the core's magnitude-based unit detection; on the two real rows it was a 13-digit value, read as milliseconds, which is an inference from the digit count and nothing more.
For checking we leaned on invented data and on independent counts. Three synthetic cases with invented values cover an empty table, a database under the Google module path, and two Android users. The storage-view test mode passes: a duplicate view of one copy is unchanged, and a second user exactly doubles the rows. On the four Android 16 images an independent count(*) returned 0 in each, so no real row was parsed in the original work. The suite passed 135 cases with no failure, three more than before. The tool's output checker had no table to read on the real reports, so it was not applicable.
Figure 2 shows the steps. Figure 3 shows the shape of the research images.
Show this figure as a table
| Step | What the parser does |
|---|---|
| Find the file | btopp.db under com.android.bluetooth or com.google.android.bluetooth |
| Collapse views | unique_files(): one copy per Android user; a rollback copy is read as its own database |
| Read the table | SELECT 15 columns FROM btopp ORDER BY _id; columns the schema lacks read blank |
| Convert and report | Timestamp to UTC; other fields as stored; Source File added (16 columns) |
Show this figure as a table
| Grouping | Value | Images |
|---|---|---|
| Schema | 14 columns, user_version 1 | 29 |
| Schema | 16 columns, user_version 2 (adds device_name, reserved) | 10 |
| Schema | 14 columns plus carrier_name, user_version 2 | 1 |
| Package folder | com.android.bluetooth | 14 |
| Package folder | com.google.android.bluetooth | 26 |
The value
The value today is a place to look and a decoded structure, not a stream of findings. An examiner now gets, in the report, any Bluetooth object-push record the phone kept: when the transfer was initialized, the destination address, the file name and type, the byte counts, and the direction and status codes, with the AOSP meaning in the notes. Where the table is empty the report says so, and the notes say what an empty table does not show.
The limits are firm. A row is a record in the Bluetooth app's table and is not by itself proof that a file reached the other device. Whether a device maker's build updates the timestamp later was not examined. Transfers by Quick Share, Nearby Share or messaging apps are not expected here. The pull request asks for one thing from other examiners: a populated btopp.db, from a device that has sent or received a file over Bluetooth, would close the remaining gap. Figure 4 shows how a populated row would read, with invented values.
Show this figure as a table
| Report column | Value as stored (synthetic) | AOSP meaning |
|---|---|---|
| Direction | 1 | inbound (0 is outbound) |
| Status | 200 | success |
| Visibility | 0 | visible |
| Confirm | 1 | confirmed |
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 appears as one of the case studies in Lab Technical Report LAB-TR-0006, Section 7.1 and Table 8, which records the Bluetooth transfer table as present and empty on all four images, with row content and timestamp unit not verified. That is the same boundary stated here, before the later corpus count.
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; 8 files, +416 and -0 lines | Pull request 1781 metadata (mergedAt, files, additions, deletions) |
| btopp.db present with a 16-column table, user_version 2, in all four Android 16 images; 0 rows in each | PR body, first checklist item; LAB-TR-0006 Section 7.1 |
| 40 research images, 100 copies; 14 images under com.android.bluetooth, 26 under com.google.android.bluetooth | Diff, notes of get_bluetooth_opp (validation boundary) |
| Schema variants: 14 columns in 29 images, 16 columns in 10, 14 plus carrier_name in 1 | Diff, same notes |
| Two copies held one row each, both in the 16-column schema; 13-digit timestamps read as milliseconds | Diff, same notes |
| Direction, visibility, confirm and status code meanings; timestamp filled with the current time by the provider | Diff, notes citing the AOSP Bluetooth module (BluetoothShare.java, BluetoothOppProvider.java) |
| Three synthetic cases (empty, Google module path, two users); second user exactly doubled, duplicate view unchanged | PR body, third checklist item and Anything reviewers should know |
| run_test_cases.py PASS=135, upstream PASS=132, three new cases; --storage-views PASS=163, KNOWN_FAIL=3 | PR body, test record table |
| Independent count(*) is 0 on each of four images | PR body, first checklist item |
| Quick Share, Nearby Share and messaging apps not examined and not expected here | Diff, notes of get_bluetooth_opp |
| Figure 4 values | Synthetic example, invented for illustration; code meanings from the notes above |