Export format
Data dictionary
SIGNAL_COLLECTOR_TXT_V4.
Exactly what an exported file contains: the session header, the TSV columns and every payload key the individual sources write. Matches the implementation of version 0.10.0.
This page is written from the code, not from intent. Where the description and the app disagree, the code wins — app/src/main/java/app/signalcollector/.
File structure
The export is a UTF‑8 text file. Depending on the estimated size it is stored as .txt or packed into GZIP. It has three parts: a first line naming the format, a header block of key=value pairs, a blank line, and then a tab-separated table of samples.
SIGNAL_COLLECTOR_TXT_V4
session_id=42
session_status=COMPLETED
…
sources=accelerometer,collector_heartbeat,gnss_raw,location,wifi
timestamp_iso_utc epoch_ms elapsed_realtime_ns session_id sequence_number source payload
2026-08-10T09:14:02.118Z 1786439642118 918273645000 42 1 location record_type=fix;provider=gps;latitude=50.087;…
Samples are ordered by timestampEpochMs and, on a tie, by insertion order. A row never contains a raw tab or line break — see escaping.
Session header
It describes the whole measurement run: when it started, how it ended and how many samples passed through each stage. The header is written by MeasurementRepository.exportLatest from a closed session.
| Key | Type | Description |
|---|---|---|
SIGNAL_COLLECTOR_TXT_V4 | text | The first line of the file, not a key=value pair. The format version; it goes up whenever the structure changes. |
session_id | long | ID of the session in the local database. The same value appears in the session_id column of every row. |
session_status | enum | COMPLETED ordinary Stop · FAILED the write or the start failed · INTERRUPTED the process died and the session was finished off by the next app start. |
end_reason | enum | USER_STOP · WRITER_ERROR · START_ERROR · PROCESS_RECOVERY. Empty only for records from versions that did not have the field yet. |
started_at | ISO‑8601 | Creation of the session in UTC, that is, the moment Start was first pressed. Resuming does not overwrite it. |
ended_at | ISO‑8601 | End of the session, that is, the last Stop. Never earlier than the last confirmed write, even when the callback arrived while the collectors were shutting down. |
running_ms | long | Pure collection time: the sum of the running stretches without the pauses between a Stop and the next Start. For a session with a pause it is smaller than ended_at − started_at; the length of each pause can be derived from the events of the session source. |
last_record_at | ISO‑8601 | Time of the last sample in the file (MAX(timestampEpochMs)). |
last_persisted_at | ISO‑8601 | Time of the last sample the writer actually confirmed into the database. |
tail_gap_ms | long | ended_at − last_persisted_at, at least 0. How much time at the end of the session was left without data — units to hundreds of ms on a healthy run. |
device | text | A nested manufacturer;model;device;android;sdk, escaped the same way as a payload. |
record_count | long | Number of sample rows in this file. |
accepted_samples | long | How many samples the write queue accepted. A collector hands its sample over and carries on; it never waits for the disk. |
written_samples | long | How many samples the writer confirmed into the database. The difference against accepted_samples is the queue that did not get through in time. |
dropped_samples | long | How many samples the queue refused because it was full (capacity 20,000). The sample is lost, but it is counted — silence in the data cannot be mistaken for loss. |
distance_m | double | Distance walked, derived from GPS fixes, to one decimal place. It is not measured by summing neighbouring fixes but by anchor moves — a phone lying on a desk therefore walks not one metre. |
distance_max_accuracy_m | float | Fix accuracy threshold for distance (20.0 m). A worse fix does not contribute to the distance at all. |
writer_error | text | Type and message of the exception that brought the write down. Empty = no error. |
sources | list | Alphabetical list of the sources that actually wrote something in this session — not a list of ticked switches. It includes collector_heartbeat, haptic and session as well. |
TSV columns
Seven tab-separated columns. The first six are the same for every source; the seventh is the payload of that particular source.
| Column | Type | Description |
|---|---|---|
timestamp_iso_utc | ISO‑8601 | A readable transcription of epoch_ms in UTC. A derived column, no extra information. |
epoch_ms | long | System time when the sample was accepted into the queue. It allows comparison with other devices, but it can jump when the phone's clock is reset. |
elapsed_realtime_ns | long | Monotonic time since the phone booted; it keeps running during sleep. This is the time axis for measuring intervals. Most sources take it straight from the Android API (fix, scan, cell, BLE, GNSS clock), so it matches the moment of measurement, not the moment the callback arrived. |
sequence_number | long | Row order in the file, from 1. It is assigned at export time; it is not a database ID. |
source | enum | Source of the sample — one of the values described below, or collector_heartbeat. |
payload | text | key=value pairs separated by semicolons. The record_type key says which payload shape follows. A missing value is written as empty — the key stays in the row. |
Escaping
Payloads such as raw_scan_result, NMEA sentences or exception text routinely contain characters that would break the structure of the file. Escaping is therefore always applied, even when a particular value holds no problematic character.
| Character | Written as | Where |
|---|---|---|
\ | \\ | payload, header and TSV columns |
; | \; | inside a payload and the header — otherwise it would look like a pair separator |
= | \= | inside a payload and the header — otherwise it would look like a key/value separator |
| tab | \t | the source and payload columns, values in the header |
| CR / LF | \r / \n | everywhere |
A parser therefore splits the row on tabs first, then the payload on unescaped semicolons, then each pair on the first unescaped equals sign — and unescapes the value last.
Shared payload suffixes
Some keys do not come from the source itself but are attached to the sample along the way. That is how even an accelerometer sample carries the last known location without knowing anything about location.
Location with every sample
Added to every sample for which the last location is known. If a source already carries a location of its own, it is not attached again. For internet_quality it is always attached — empty if need be, so the data shows that no location was available.
| Key | Type | Description |
|---|---|---|
last_location | bool | Whether a location was available. |
last_latitude | double | Latitude of the last fix. |
last_longitude | double | Longitude of the last fix. |
last_altitude | double | Altitude, only when the fix carried one. |
last_location_accuracy | float | Reported horizontal accuracy of the fix, in metres. |
location_age_ms | long | Age of the fix relative to this sample. Without it there would be no telling whether the location is one second or five minutes old. |
Network context with internet mapping
Attached only to internet_quality samples, tying the measured transfer to a specific network. The set of keys differs by transport.
| Key | Transport | Description |
|---|---|---|
ssid, bssid, rssi_dbm, rssi, frequency_mhz, frequency, channel, wifi_standard, rx_link_speed_mbps, tx_link_speed_mbps | WIFI | A snapshot of the connected Wi‑Fi at the moment of the sample. Unknown values are left empty; Android placeholder values (RSSI −127, frequency −1) never reach the export. |
operator, mcc, mnc, plmn, technology, cell_id, nci, pci, tac, bands, band, earfcn, nrarfcn, rsrp, rsrq, sinr, ss_rsrp, ss_rsrq, ss_sinr | CELLULAR | A snapshot of the serving cell. On 5G, cell_id is filled in from nci and rsrp/rsrq/sinr from ss_*, so the series can be compared across technologies. |
network_context_age_ms | both | Age of the network snapshot relative to the sample. |
network_handle, network_request_transport, network_request_internet, network_has_internet, network_validated, interface_name, subscription_id | both | Proof of which Android Network the transfer actually went through. Without it, samples could not be told apart when Wi‑Fi and cellular are measured at the same time. |
Sensors
source: accelerometer · gyroscope · magnetometer · barometer · light · proximity · rotation_vector
All seven sensors share the same payload shape. Values are taken exactly as SensorEvent returns them, with no filtering and no conversion. elapsed_realtime_ns is the time of the sensor event, not the time of delivery.
| Key | Type | Description |
|---|---|---|
v0, v1, v2, … | float | Axes by sensor type: accelerometer m/s² (x, y, z) · gyroscope rad/s · magnetometer µT · barometer hPa (v0 only) · light lx · proximity cm · rotation vector quaternion (v0–v3, and possibly v4 as the accuracy in radians). |
accuracy | int | Reported sensor accuracy 0–3 (0 = unreliable, 3 = high). |
sensor | text | Name of the specific hardware sensor as the device reports it. |
noise
source: noise · record_type: none, the payload always has the same shape
Only the loudness of a block of samples is computed from the microphone. No audio is stored or sent anywhere; audio cannot be reconstructed from the stored values.
| Key | Type | Description |
|---|---|---|
rms | double | Root mean square of the block, in units of 16-bit PCM. |
dbfs | double | Loudness in dBFS relative to full scale, floored at −120. It is a relative level, not calibrated dB SPL. |
sample_rate_hz | int | The actual sample rate at which the microphone could be opened. |
samples | int | Number of PCM samples in this block. |
location
source: location · record_type: fix · registration
record_type=fix
| Key | Type | Description |
|---|---|---|
provider | enum | gps or network. Both providers run at once when enabled. |
latitude / longitude | double | Position in degrees (WGS‑84). |
altitude | double | Height above the ellipsoid in metres, only when the fix carries it. |
speed_mps | float | Speed in m/s. |
bearing_deg | float | Bearing of travel in degrees. |
horizontal_accuracy_m | float | Radius of 68 % confidence in metres. Above 20 m the fix does not count towards distance_m. |
vertical_accuracy_m | float | Uncertainty of the altitude. |
speed_accuracy_mps | float | Uncertainty of the speed. |
bearing_accuracy_deg | float | Uncertainty of the bearing. |
location_time_epoch_ms | long | Time of the fix according to the location provider — it need not match the row's epoch_ms. |
location_elapsed_realtime_ns | long | Monotonic time of the fix; the row's elapsed_realtime_ns derives from it. |
is_mock | bool | A mocked location. It does not count towards the distance. |
raw_location | raw | toString() of the Location object, in case some new attribute is missing in the future. |
record_type=registration
| Key | Type | Description |
|---|---|---|
callback | text | Always LOCATION_UPDATES. |
provider | enum | Which provider the registration concerns. |
provider_enabled | bool | Whether the provider was enabled in the system. |
status | enum | SUCCESS · PROVIDER_DISABLED · FAILED. It tells a disabled GPS apart from a refused registration. |
registered | bool | Whether the subscription to fixes was actually created. |
error / message | text | Class and message of the exception if the registration failed. |
gnss_satellite
source: gnss_satellite · record_type: satellite · summary · event · registration · callback_error
On every change of satellite status, one row per satellite is written plus a single summary. The summary is precomputed deliberately — otherwise everyone processing the data would have to compute it.
record_type=satellite
| Key | Type | Description |
|---|---|---|
constellation | enum | GPS · GALILEO · GLONASS · BEIDOU · QZSS · NAVIC · SBAS · UNKNOWN. |
svid | int | Identifier of the satellite within its constellation. |
cn0_db_hz | float | Carrier-to-noise density in dB‑Hz. The main indicator of satellite signal strength. |
baseband_cn0_db_hz | float | C/N0 measured at baseband, when the chip reports it. |
elevation_deg / azimuth_deg | float | Position of the satellite in the sky. |
carrier_frequency_hz | float | Carrier frequency of the signal — it tells L1 from L5. |
used_in_fix | bool | Whether the satellite was used in computing the position. |
has_ephemeris / has_almanac | bool | Whether the receiver holds ephemeris / almanac data for the satellite. |
record_type=summary
| Key | Type | Description |
|---|---|---|
satellites_visible / satellites_used | int | How many satellites the receiver sees and how many it used for the fix. |
fix_use_ratio | double | Ratio of used to visible. |
cn0_avg_db_hz / cn0_max_db_hz | double | Mean and best C/N0 across all visible satellites. |
used_cn0_avg_db_hz | double | Mean C/N0 of the satellites used in the fix only. |
ephemeris_count / almanac_count | int | How many satellites hold ephemeris / almanac data. |
gps_count, galileo_count, glonass_count, beidou_count, qzss_count, navic_count, sbas_count, unknown_count | int | Counts of visible satellites per constellation. |
record_type=event / registration / callback_error
| Key | Type | Description |
|---|---|---|
event | enum | GNSS_STARTED · GNSS_STOPPED · GNSS_FIRST_FIX. |
ttff_ms | int | Time to first fix — only with GNSS_FIRST_FIX. |
callback | text | GNSS_STATUS for a registration. |
status, registered, error, message | mixed | Result of the registration, the same shape as with location. |
gnss_raw
source: gnss_raw · record_type: clock · measurement · agc · capabilities · registration · status · callback_error
Raw GNSS measurements for post-processing. One clock record plus as many measurement rows as the chip tracked satellites in that epoch.
record_type=clock
| Key | Type | Description |
|---|---|---|
measurement_count | int | How many measurement rows belong to this epoch. |
full_tracking | bool | Whether the chip runs in full tracking mode (Android 14+). |
time_nanos | long | Hardware time of the receiver in nanoseconds. |
time_uncertainty_nanos | double | Uncertainty of the hardware time. |
full_bias_nanos / bias_nanos | long / double | Coarse and fine offset of the receiver clock against GPS time. |
bias_uncertainty_nanos | double | Uncertainty of the clock offset. |
drift_nanos_per_second | double | Drift of the receiver clock. |
drift_uncertainty_nanos_per_second | double | Uncertainty of the drift. |
leap_second | int | Current number of leap seconds. |
hardware_clock_discontinuity_count | int | Number of clock jumps. A change in the value means phase must not be carried across continuously. |
clock_elapsed_realtime_ns | long | Tie of the receiver clock to the system monotonic time. |
clock_elapsed_realtime_uncertainty_ns | double | Uncertainty of that tie. |
reference_constellation, reference_carrier_frequency_hz, reference_code_type | mixed | Reference signal for inter-system biases. |
raw_clock | raw | toString() of the GnssClock object. |
record_type=measurement
| Key | Type | Description |
|---|---|---|
constellation / svid | enum / int | Constellation and satellite number. |
code_type | text | Type of the signal code, for example C, L, Q. |
state | int | Bit mask of the tracking state. It determines whether received_sv_time_ns is unambiguous. |
received_sv_time_ns | long | Received satellite time — the basis for pseudorange. |
received_sv_time_uncertainty_ns | long | Uncertainty of the received time. |
time_offset_ns | double | Offset of the measurement against the time in clock. |
cn0_db_hz / baseband_cn0_db_hz / snr_db | double | Signal strength and signal-to-noise ratio. |
carrier_frequency_hz | float | Carrier frequency; it tells L1 from L5. |
carrier_cycles, carrier_phase, carrier_phase_uncertainty | long / double | Carrier phase measurements. |
pseudorange_rate_mps | double | Rate of change of the pseudorange in m/s (Doppler measurement). |
pseudorange_rate_uncertainty_mps | double | Uncertainty of the same. |
accumulated_delta_range_m | double | Accumulated range change from the carrier phase. |
accumulated_delta_range_uncertainty_m | double | Uncertainty of the accumulated change. |
accumulated_delta_range_state | int | State of the accumulation; it reports a lost phase (cycle slip). |
multipath_indicator | int | Indicator of signal reflection, when the chip provides one. |
agc_level_db | double | Automatic gain control level — sensitive to interference in the band. |
full_inter_signal_bias_ns, full_inter_signal_bias_uncertainty_ns, satellite_inter_signal_bias_ns, satellite_inter_signal_bias_uncertainty_ns | double | Biases between signals and between satellites. |
raw_measurement | raw | toString() of the GnssMeasurement object. |
record_type=agc / capabilities / status
| Key | Type | Description |
|---|---|---|
constellation, carrier_frequency_hz, level_db, raw_agc | mixed | AGC per band (Android 13+). |
hardware_model, hardware_year, capabilities | text / int | Model and year of the GNSS chip and the capabilities it reports. It explains why certain fields are always missing on a given phone. |
status / status_name | int / enum | READY · LOCATION_DISABLED · NOT_SUPPORTED · UNKNOWN. |
gnss_nmea
source: gnss_nmea · record_type: nmea · registration
| Key | Type | Description |
|---|---|---|
nmea_timestamp_epoch_ms | long | Time of the sentence according to the system. |
sentence | text | The whole NMEA sentence without trailing whitespace. The asterisk and the checksum are kept; any separators are escaped. |
gnss_antenna
source: gnss_antenna · record_type: antenna_info · registration
| Key | Type | Description |
|---|---|---|
index | int | Position of the antenna in the list the phone reports. |
carrier_frequency_mhz | double | The band the values relate to. |
phase_center_offset | raw | Offset of the antenna phase centre, including uncertainties. |
phase_center_variation_corrections | raw | Map of phase centre corrections by direction. |
signal_gain_corrections | raw | Map of gain corrections by direction. |
raw_antenna_info | raw | toString() of the GnssAntennaInfo object. |
wifi
source: wifi · record_type: scan · access_point · connection · mlo_link · status
Every fresh scan writes one row per visible access point plus one row about the phone's own connection. Android throttles scans and scanResults then keeps returning the same last result, which is why every scan attempt has its own record_type=scan row and every AP carries its own scan_timestamp_us.
record_type=scan
Written on every scan attempt, even when no new data comes of it. Without it, an old result returned again and again would look like regular sampling at the configured interval, and a coverage map could not tell where measuring actually happened. access_point rows are written only for the FRESH state.
| Key | Type | Description |
|---|---|---|
status | enum | FRESH a new scan · THROTTLED the system refused startScan() · UNCHANGED the same scan had already been processed by the system callback. The distinction matters: UNCHANGED also grows on a phone that does no throttling at all, where reading it as throttling would be wrong. |
trigger | enum | PERIODIC_POLL the collector's own tick · SCAN_RESULTS_CALLBACK a scan triggered by the system or another app, which the collector took over instead of waiting for its next tick. |
start_scan_requested | bool | Return value of startScan(). Empty for trigger=SCAN_RESULTS_CALLBACK, where the collector did not ask for a scan. |
scan_timestamp_us | long | Time of the newest result in the scan, in microseconds since boot. This is exactly what reveals that it is the same scan as last time. |
scan_age_ms | long | Age of the result relative to this row. Units to hundreds of ms for FRESH; it grows for a repeated scan. |
ap_count | int | How many access points the scan returned. |
fresh_scans / throttled_scans / unchanged_scans | long | Cumulative counters for the running stretch. After a resume within a session they start from zero again, so the ratio is always computed within a single stretch. |
record_type=access_point
| Key | Type | Description |
|---|---|---|
ssid / bssid | text | Network name and MAC address of the access point. |
rssi_dbm | int | Signal strength in dBm. |
frequency_mhz / channel | int | Frequency and the channel derived from it. |
channel_width | int | Channel width as the ScanResult constant (0 = 20 MHz, 1 = 40, 2 = 80, …). |
center_freq_0_mhz / center_freq_1_mhz | int | Centre frequencies of the segments on wide channels. |
capabilities | text | Capability and security string as the scan reports it. |
security_types | list | Numeric security types (Android 13+). |
wifi_standard | enum | LEGACY · 802.11n · 802.11ac · 802.11ax · 802.11ad · 802.11be. An unknown standard is left empty, not UNKNOWN. |
scan_timestamp_us | long | When the AP was last seen, in microseconds since boot. It exposes an old scan returned again. |
passpoint, operator_friendly_name, venue_name | bool / text | Passpoint metadata, when the network reports it. |
raw_scan_result | raw | toString() of the ScanResult object. |
record_type=connection
| Key | Type | Description |
|---|---|---|
ssid / bssid | text | Identification of the connected network. Empty when the system hides it for permission reasons. |
ssid_available / bssid_available | bool | It tells “the network has no name” from “the system did not give us the name”. |
rssi_dbm, frequency_mhz, channel | int | Signal and channel of the current connection. |
link_speed_mbps, rx_link_speed_mbps, tx_link_speed_mbps | int | Negotiated link rate. This is not measured throughput — that is measured by internet_quality. |
max_rx_link_speed_mbps / max_tx_link_speed_mbps | int | The maximum the connection supports. |
wifi_standard | enum | Standard of the current connection. |
security_type | int | Security type of the connected network. |
passpoint_fqdn / passpoint_provider | text | Passpoint provider, if the phone connected through one. |
ap_mld_mac / ap_mlo_link_id | text / int | Wi‑Fi 7 multi-link: the MLD address and the ID of the active link. |
raw_wifi_info | raw | toString() of the WifiInfo object. |
record_type=mlo_link
| Key | Type | Description |
|---|---|---|
mlo_kind | enum | scan_affiliated · connection_affiliated · connection_associated — where the link came from. |
link_id | int | ID of the link within the MLD. |
ap_mac / sta_mac | text | Addresses of both ends of the link. |
band, channel, frequency_mhz | int | Band and channel of the link. |
rssi_dbm, rx_link_speed_mbps, tx_link_speed_mbps | int | Signal and rate of the link (Android 14+). |
state | enum | ACTIVE · IDLE · UNASSOCIATED · INVALID. |
raw_mlo_link | raw | toString() of the MloLink object. |
record_type=status
A single state, status=SCAN_CALLBACK_REGISTRATION_FAILED with the keys error and message. It means the subscription to system scans could not be registered — the data will then hold only the scans the collector requested, that is, trigger=PERIODIC_POLL.
wifi_rtt
source: wifi_rtt · record_type: capabilities · ranging_result · status
Ranging to access points that support FTM (802.11mc / 802.11az). Such APs are rare; when none is in range, status=NO_RESPONDERS is written so that it is clear the measurement ran and simply had nothing to measure.
| Key | Type | Description |
|---|---|---|
available / characteristics | bool / text | Availability of RTT and the characteristics the chip reports (record_type=capabilities). |
status | int / enum | For a result, a numeric code (0 = success); for a status, UNAVAILABLE · NO_RESPONDERS · FAILURE · ERROR. |
mac_address | text | Address of the access point being ranged. |
distance_mm / distance_std_dev_mm | int | Distance and its standard deviation in millimetres. Filled in only for a successful measurement. |
rssi_dbm | int | Signal at the moment of measurement. |
attempted_measurements / successful_measurements | int | How many individual measurements were attempted and how many succeeded. |
ranging_timestamp_ms | long | Time of the measurement according to the chip; the row's elapsed_realtime_ns derives from it. |
measurement_frequency_mhz / measurement_bandwidth | int | Channel and bandwidth of the measurement (Android 14+). |
is_80211mc / is_80211az_ntb | bool | Which FTM variant the measurement used. |
lci_hex / lcr_hex | hex | Reported location and civic address of the access point (Android 15+). |
min_ntb_interval_us / max_ntb_interval_us | long | Range of intervals for 802.11az non-trigger-based ranging. |
ranging_authenticated / ranging_frame_protected | bool | Security of the ranging (Android 16+). |
code, error, message | int / text | Reason for failure on status records. |
raw_ranging_result | raw | toString() of the RangingResult object. |
wifi_throughput
source: wifi_throughput · record_type: wifi_throughput · status
One scan gives the signal of every network around at once; throughput does not — the phone is only ever associated with a single AP. The source therefore visits the networks listed in advance one by one through a WifiNetworkSpecifier and measures download, upload and HTTPS RTT on each. The switch applies to this app only; the rest of the phone's traffic stays on the original network.
Measure standing still: while walking, each network in the cycle would be measured from a different place and the numbers could not be compared. On the first connection to each network Android demands consent through a modal system dialog that cannot be suppressed — a walk therefore starts by clearing those dialogs at the first point.
record_type=wifi_throughput
One row per network per cycle, even when the network was never reached — otherwise the data could not tell a network that was not there from a network that was forgotten.
| Key | Type | Description |
|---|---|---|
status | enum | SUCCESS both transfers went through · PARTIAL at least one ended in an error · NOT_VISIBLE the network was not in the last scan, so no attempt was made · CONNECT_FAILED the connection did not succeed within connect_timeout_s. |
cycle / target_index | int | Which round of the walk, and the position of the network in the configured list. The pair identifies the row uniquely. |
requested_ssid / secured | text / bool | The network as entered in the settings, and whether it has a password. The password itself is never written into the data. |
connect_ms | double | Time from the start of the attempt to an available network, that is, association and DHCP. Without it a slow network could not be told from slow switching. Zero for NOT_VISIBLE. |
total_ms | double | The whole handling of the network, including both transfers and the RTT probes. |
has_internet | bool | Whether the network reports NET_CAPABILITY_INTERNET. A network without internet is still measured and ends with a transfer error — as a result, not as unavailability. |
ssid, bssid, rssi_dbm, frequency_mhz, channel, wifi_standard, rx_link_speed_mbps, tx_link_speed_mbps | text / int | Snapshot of the connection at the moment of measurement: which AP the phone actually joined and at what negotiated rate. Filled in only for a successful connection. |
latency_ms_median / latency_ok / latency_errors | double / int / list | Median HTTPS RTT, the number of probes that came back and the exception types of those that did not. This is not an ICMP ping. |
download_mbps / upload_mbps | double | Throughput of the transfer. On an error it stays empty even when part of the bytes moved — half a transfer is not a measured rate. |
download_bytes / upload_bytes | long | How many bytes actually moved; on an error, up to the moment of failure. |
download_ms / upload_ms | double | Duration of the transfer, including establishing the connection. |
download_error / upload_error | text | Name of the exception class, for example SocketTimeoutException. A refused HTTP status code arrives here as IllegalStateException. Empty on success. |
server_mode / server | text | Mode and host of the test endpoint, the same as with internet_quality. |
record_type=status
A single state, status=NO_TARGETS with an explanatory message. The source is switched on but the list of networks is empty, so it never starts — and the export shows that instead of staying silent.
mobile_network
source: mobile_network · record_type: cell · signal_strength · event
For record_type=cell the set of keys is always the same and always complete — fields a given technology does not have are left empty. The columns of the table therefore never shift about depending on whether the phone is currently on LTE or 5G.
Keys shared by every cell record
| Key | Type | Description |
|---|---|---|
scan_mode | enum | PASSIVE_CELL_SCAN ordinary subscription · ACTIVE_NETWORK_SCAN the experimental active scan. |
trigger | enum | TELEPHONY_CALLBACK · GET_ALL_CELL_INFO · REQUEST_CELL_INFO_UPDATE · ACTIVE_NETWORK_SCAN. It tells a requested snapshot from a spontaneous change. |
technology | enum | NR · LTE · WCDMA · TDSCDMA · GSM · CDMA. |
registered | bool | Whether the phone is registered to this cell. |
connection_status | enum | PRIMARY_SERVING · SECONDARY_SERVING · NONE · UNKNOWN. |
subscription_id / sim_slot | int | Which SIM the cell belongs to. With two SIMs, two independent sets of records run. |
cell_timestamp_ms | long | Time of the snapshot according to the modem; the row's elapsed_realtime_ns derives from it. |
dbm, asu, level | int | Signal strength in dBm, in ASU and as a 0–4 level for the UI. |
mcc, mnc, plmn | text | Country code, network code and the two joined. |
operator | text | Operator name, or the PLMN when there is no name. UNKNOWN for a cell with no identity. |
operator_alpha_long / operator_alpha_short | text | Long and short network name according to the modem. |
additional_plmns | list | Further PLMNs the cell broadcasts (network sharing). |
raw_cell_info, raw_cell_identity, raw_signal_strength | raw | toString() of three modem objects, as a fallback for fields that have no key of their own here yet. |
Keys by technology
| Key | Technology | Description |
|---|---|---|
cell_id | LTE, WCDMA, GSM, TDSCDMA, CDMA | Cell identifier (ci on LTE, cid on the others, base station ID on CDMA). |
nci | NR | The 48-bit 5G NR Cell Identity. |
pci | LTE, NR | Physical Cell ID. |
tac | LTE, NR | Tracking Area Code. |
lac | WCDMA, GSM, TDSCDMA | Location Area Code. |
psc | WCDMA, GSM, TDSCDMA | Primary Scrambling Code; on TDSCDMA it is filled from cpid. |
bsic | GSM | Base Station Identity Code. |
earfcn / nrarfcn / uarfcn / arfcn | LTE / NR / WCDMA, TDSCDMA / GSM | Channel number by technology. |
bands | LTE, NR | Bands the cell operates in. |
bandwidth | LTE | Bandwidth in kHz. |
rssi | LTE, GSM | Total received power. |
rsrp / rsrq | LTE | Reference signal power and quality. |
rssnr | LTE | Signal-to-noise ratio. |
sinr | LTE, NR | A unified key for comparison across technologies: on LTE it copies rssnr, on NR ss_sinr. |
ss_rsrp, ss_rsrq, ss_sinr | NR | Measurements on the synchronisation signal. |
csi_rsrp, csi_rsrq, csi_sinr | NR | Measurements on CSI‑RS. |
cqi / cqi_table_index | LTE, NR | Channel quality indicator; on NR it is a list of values. |
timing_advance / timing_advance_us | LTE, GSM / NR | Timing advance — a coarse hint of the distance to the transmitter. |
ec_no | WCDMA | Chip energy to noise ratio. |
rscp | TDSCDMA | Received Signal Code Power. |
bit_error_rate | GSM | Error rate. |
network_id, system_id, basestation_latitude, basestation_longitude, cdma_dbm, cdma_ecio, evdo_dbm, evdo_ecio, evdo_snr | CDMA | Fields of the old CDMA networks; they stay empty in Europe. |
record_type=signal_strength
| Key | Type | Description |
|---|---|---|
technology | text | Android's measurement class, for example CellSignalStrengthLte. |
dbm, asu, level | int | Signal strength in the three usual units. |
subscription_id / sim_slot | int | Which SIM the measurement concerns. |
raw_signal_strength | raw | toString() of the measurement. |
record_type=event
| Key | Type | Description |
|---|---|---|
event | enum | CELL_LIST_CHANGED · SIGNAL_STRENGTH_CHANGED · SERVICE_STATE_CHANGED · DISPLAY_INFO_CHANGED · DATA_CONNECTION_CHANGED · SERVING_CELL_CHANGED · CELL_INFO_UPDATE_ERROR · RADIO_API_ERROR · ACTIVE_NETWORK_SCAN_*. |
before / after | raw | State before and after the change, so the transition can be reconstructed. |
before_technology, before_cell_id, before_pci, after_technology, after_cell_id, after_pci | mixed | The serving-cell transition broken out — this is a handover. |
operation | text | Which modem call failed on RADIO_API_ERROR. |
has_carrier_privileges / modify_phone_state_granted | bool | Why the system allowed or refused the active scan. |
result_count | int | Number of cells returned by the active scan. |
error_code, error, exception, message | int / text | The exact reason for the refusal or the error. |
manufacturer, model, android, sdk | text / int | Device and system on active scan records — its availability depends heavily on the phone. |
sim_info
source: sim_info · record_type: summary · subscription
Which cards were in the phone while measuring. mobile_network carries the identity of the transmitter but not of whoever was looking at it — and with two cards, or in roaming, that is a crucial difference. It is written once when the measurement starts and after that only when the system reports a change of subscriptions; cards do not change as you walk.
Neither ICCID nor EID is recorded. They are permanent subscription identifiers that outlive even a change of phone, whereas the rest of this section describes the network the measurement ran in. Since Android 10 the system would not hand them to an app anyway.
record_type=summary
| Key | Type | Description |
|---|---|---|
slot_count | int | How many subscriptions the phone can hold at once. |
active_count | int | How many of them are active. Zero means a phone with no card, not a read error. |
esim_count | int | How many of the active ones are eSIM profiles. |
euicc_supported | bool | Whether the phone has an eSIM chip, regardless of any profile on it. |
record_type=subscription
| Key | Type | Description |
|---|---|---|
subscription_id | int | Local subscription number. It changes when the card is re-registered, so it does not identify the phone; it serves to join rows within a single measurement. |
slot_index | int | The slot the card sits in. Numbered from zero. |
carrier_name / display_name | text | Operator according to the card, and the name the user gave the subscription. |
mcc / mnc | text | Country and network code. It joins the card to the cell records in mobile_network. |
country_iso | text | Country of the subscription, ISO code. |
embedded | bool | true for an eSIM profile, false for a physical card. |
roaming | bool | Whether the subscription is currently roaming. |
default_data, default_voice, default_sms | bool | Which card carries data, calls and messages. For analysing a measurement, default_data matters most — its network is the one that shows up in internet_quality. |
network_context
source: network_context · record_type: capabilities · link_properties · event
The operating system's view of the networks: what the phone claims about a network before anything is transferred over it. Written at the start, periodically, and on every change.
record_type=capabilities
| Key | Type | Description |
|---|---|---|
trigger | enum | INITIAL · PERIODIC · CALLBACK. |
network_handle | long | Network identifier. It joins the record to a specific internet_quality sample. |
transports | list | CELLULAR, WIFI, VPN, ETHERNET, BLUETOOTH, THREAD, SATELLITE… |
capabilities | list | Numeric constants of the network capabilities. |
internet / validated | bool | Whether the network promises internet, and whether the system verified that it really works. |
captive_portal | bool | The network requires a sign-in through a portal. |
metered, roaming, congested, suspended | bool | State of the network. Stored in the positive sense, so metered=true means a metered network. |
downstream_kbps / upstream_kbps | int | The system's estimate of link capacity — an estimate, not a measurement. |
signal_strength | int | Signal strength of the network, where the system states it. |
owner_uid, network_specifier, transport_info | mixed | Supplementary identification of the network; on Wi‑Fi it contains WifiInfo. |
raw_capabilities | raw | toString() of the NetworkCapabilities object. |
record_type=link_properties
| Key | Type | Description |
|---|---|---|
interface_name | text | Interface name, for example wlan0 or rmnet_data0. |
link_addresses | list | IP addresses of the interface. |
dns_servers / domains | list / text | DNS servers and search domains. |
routes | list | Routing entries, separated by a pipe. |
mtu | int | Maximum frame size. |
private_dns_active / private_dns_server | bool / text | State of private DNS. |
dhcp_server, nat64_prefix, http_proxy, wake_on_lan | mixed | Other properties of the link. |
raw_link_properties | raw | toString() of the LinkProperties object. |
record_type=event
| Key | Type | Description |
|---|---|---|
event | enum | AVAILABLE · LOSING · LOST · BLOCKED_CHANGED. |
max_ms_to_live | int | How much time the system gives the network on LOSING. |
blocked | bool | Whether the app's traffic on this network is blocked. |
internet_quality
source: internet_quality · record_type: network_quality_sample · network_capacity_probe · network_capacity_probe_started · speed_test_result · speed_test_event
Real HTTPS transfers bound to a specific Android Network, not an estimate of link capacity. The one-second sample is small on purpose: the goal is a continuous map of quality while walking, not a peak rate. That is why every record also carries measurement_semantics=SMALL_TRANSFER_QUALITY_NOT_MAX_SPEED.
Every sample additionally carries the shared suffixes — the location and the network snapshot.
record_type=network_quality_sample / network_capacity_probe
| Key | Type | Description |
|---|---|---|
timestamp / timestamp_epoch_ms | long | Start of the sample. Two keys with the same value, for backward compatibility with older scripts. |
scheduled_at_epoch_ms / started_at_epoch_ms | long | When the sample was due to start and when it did. |
schedule_delay_ms | long | Delay against the plan. It grows when the link is slow or the system throttles the app. |
missed_intervals | long | How many whole intervals were skipped. The per-second sample axis writes a row even when measuring was impossible, so this only grows after the process was genuinely put to sleep. |
status | enum | Measured: SUCCESS · PARTIAL · TIMEOUT (everything failed, and only by running out of time) · ERROR. Not measured: BUSY (a burst or a manual test held the transport) · NETWORK_LOST (there was nothing to measure on). The last two carry empty measurement columns and do not count towards the error rate. |
busy_reason | enum | Who held the transport: QUALITY_SAMPLE · CAPACITY_PROBE · SPEED_TEST. Only with status=BUSY. |
measurement_semantics | enum | Always SMALL_TRANSFER_QUALITY_NOT_MAX_SPEED. A safeguard against reading the values as a speed test. |
server_mode / server | text | Mode and host of the test endpoint. |
network_binding | enum | Always ANDROID_NETWORK — the transfer demonstrably went over the selected network, not the default one. |
transport_target | enum | What the user chose: AUTO · WIFI · CELLULAR · BOTH. |
transport | enum | What the sample actually went over: WIFI · CELLULAR · ETHERNET · BLUETOOTH · VPN · OTHER · NONE. |
download_requested_bytes / upload_requested_bytes | long | How many bytes were meant to be transferred. |
download_bytes / upload_bytes | long | How many actually were. On an error, these are the bytes moved up to the moment of failure. |
download_duration_ms / upload_duration_ms | double | Duration of the transfer, including establishing the connection. |
short_download_mbps / short_upload_mbps | double | Throughput of the one-second sample. A small transfer, so it is a lower bound — not the maximum of the link. |
burst_download_mbps / burst_upload_mbps | double | The same for the capacity burst, which runs once per configured interval and is considerably larger. |
latency_probe_count | int | How many RTT probes were meant to be sent. |
successful_probes / failed_probes | int | How many got through and how many failed. |
rtt_min_ms, rtt_avg_ms, rtt_max_ms | double | Response time of the HTTPS probes. |
jitter_ms | double | Mean absolute change of RTT between neighbouring probes. |
failed_requests / timeout_count | int | How many requests failed in total and how many of those timed out. |
probe_duration_ms | double | How long the whole sample took. On a slow link it is longer than the interval — the following seconds then carry status=BUSY. |
errors / error | text | List of errors in the form phase:type, or a one-word reason (NO_ACTIVE_NETWORK, TARGET_NETWORK_UNAVAILABLE). |
record_type=network_capacity_probe_started
Written the moment a burst begins. The burst holds the transport lock for the whole transfer, so during that time rows with status=BUSY arrive instead of measured samples — this marker says from when and why. Its keys are a subset of the previous table plus transfer_deadline_ms (the latest the transfer may take).
record_type=speed_test_result
A manual test started by the button, not part of continuous mapping. It transfers megabytes, not kilobytes.
| Key | Type | Description |
|---|---|---|
trigger | text | MANUAL for a test from the UI. |
status | enum | SUCCESS · PARTIAL · ERROR. |
transport | enum | WIFI · MOBILE. Choosing BOTH writes two separate results. |
download_mbps / upload_mbps | double | Throughput of the large transfer. |
rtt_median_ms / rtt_p95_ms | double | Median and 95th percentile of the response time — a manual test sends more probes than a sample. |
packet_loss_pct | double | Share of probes that never came back. |
download_error, upload_error, latency_errors | text | Errors of the individual phases of the test. |
record_type=speed_test_event
| Key | Type | Description |
|---|---|---|
event | enum | INTERNET_SPEED_TEST_STARTED · _COMPLETE · _ERROR. |
target | enum | The chosen target of the test, including BOTH. |
status, trigger, error, message | text | State and any error of the test run. |
bluetooth_classic
source: bluetooth_classic · record_type: discovery_result · paired_device · event · registration · status
| Key | Type | Description |
|---|---|---|
discovery_mode | enum | CLASSIC_DISCOVERY found by a scan · BONDED_CACHE listed from the paired devices. |
scan_cycle_id | long | Number of the scan cycle. It ties a find to a specific scan window. |
result_index | int | Which find it is within this cycle. |
name / alias | text | Device name and user alias, where available. |
address | text | MAC address of the device. |
bond_state | int | 10 not paired · 11 pairing · 12 paired. |
device_type | int | 1 Classic · 2 LE · 3 Dual. |
bluetooth_class | text | Device class (type and services). |
uuids | list | Services offered. |
rssi_dbm | int | Signal at the moment of the find. |
paired | bool | Only on paired_device, always true. |
event / status | enum | CLASSIC_DISCOVERY with the state REQUESTED · STARTED · FINISHED · REJECTED · ERROR. |
found_devices | int | How many devices the cycle found — on FINISHED this is its result. |
receiver_flag | enum | RECEIVER_EXPORTED or IMPLICIT. Discovery broadcasts come from the system, so with a non-exported receiver the scan looks dead in the data — which is why the flag used is recorded. |
callback, registered, error, message | mixed | Result of registering the receiver. |
status=ADAPTER_DISABLED means Bluetooth is switched off. Without that report the source would look enabled, just without a single find.
bluetooth_ble
source: bluetooth_ble · record_type: advertisement · status
The complete advertising payload, raw bytes included. The scan runs in low-latency mode and does not filter, so in a busy environment it is the most prolific source of rows.
| Key | Type | Description |
|---|---|---|
callback_type | int | Type of the scanner callback (all matches, first match, match lost). |
name, alias, address | text | Identification of the device. |
address_type | int | Public or random address (Android 15+). It explains why an address changes over time. |
bond_state / device_type | int | The same meaning as with bluetooth_classic. |
rssi_dbm | int | Signal of the received packet. |
tx_power / record_tx_power | int | Transmit power according to the scanner and according to the advertising content. Together with RSSI it gives a coarse distance estimate. |
connectable / legacy | bool | Whether it can be connected to; whether it is the old advertising format. |
data_status | int | Whether the payload is complete or continues in another packet. |
primary_phy / secondary_phy | int | Physical layer (1M, 2M, coded). |
advertising_sid | int | ID of the advertising set in extended advertising. |
periodic_advertising_interval | int | Interval of periodic advertising, where the device uses it. |
advertise_flags / advertised_name | int / text | Flags and name straight from the advertising packet. |
service_uuids / service_solicitation_uuids | list | Services offered and services solicited. |
manufacturer_data | hex | Manufacturer data as 0xIDID:hex, comma separated. The manufacturer ID allows the device to be typed. |
service_data | hex | Service data as uuid:hex. |
advertising_data | hex | The whole map of AD types as 0xTT:hex (Android 13+). |
raw_bytes_hex | hex | The raw advertising packet. It allows anything the other keys do not cover to be derived. |
raw_scan_record / raw_scan_result | raw | toString() of both objects. |
status / error_code | enum / int | SCAN_FAILED with an error code, or ADAPTER_DISABLED. |
collector_heartbeat
source: collector_heartbeat · record_type: collector_heartbeat · every 5 s
The heartbeat is not a source of data about the surroundings but about the measurement itself. It passes through the same queue and the same writer as every other sample, so a finished file shows which module was really running when, and whether writing ever stalled. Silence in the data therefore cannot be mistaken for a fault.
| Key | Type | Description |
|---|---|---|
active_collectors | list | Classes of the running collectors, alphabetically. |
active_sources | list | The sources that were actually registered at start — not the ones ticked. |
writer_status | enum | OK · RETRYING · DRAINING · DRAINED · FAILED · CANCELLED · STOPPED. |
queue_size | long | How many samples are waiting in the queue right now. A value that keeps growing means the write is falling behind. |
accepted_samples, written_samples, dropped_samples | long | Running counters, the same meaning as in the header. |
writer_error | text | The first write error, if there was one. |
haptic
source: haptic · record_type: haptic
A vibration is a mechanical impulse and the phone is recording the accelerometer and the gyroscope at the same time, so every buzz writes itself into its own data as a shake that did not come from walking. This record says when it happened, so a window can be cut around it in the sensor stream. What gets written is the loss and return of the default network and the point marker; the alarm for a failed write has no record, because by then the writer is no longer alive — that one is carried by end_reason=WRITER_ERROR, and no sensor data is produced beyond that moment.
| Key | Type | Description |
|---|---|---|
signal | enum | TRANSPORT_LOST the default network disappeared and nothing replaced it within two seconds · TRANSPORT_RESTORED the network is back · SELECT the operator marked a point, and a survey_point belongs to the same instant. |
pattern | text | Rhythm of the signal: thud, tick-tick or tick. |
nominal_duration_ms | long | Nominal length of the envelope, not a measured one. For a composed effect the decay is up to the system, so the window to cut should be taken with room to spare. |
composed | bool | true = the phone supports VibrationEffect.Composition primitives and this was a tap; false = a substitute waveform with the same rhythm. |
delivered | bool | false means the system refused the vibration — so the shake is not in the data. |
session
source: session · record_type: session_started · session_paused · session_resumed · survey_point
Start resumes into the last measurement that was not reset, so one session can consist of several separate stretches. These records say where a stretch began and where it ended — without them a pause in the data could not be told from a hole in coverage or from frozen collection. A pause is deliberately two separate events rather than one record with a length: a record with a length would have to wait for the end of the pause, and if the system killed the app meanwhile, it would never be written at all.
| Key | Type | Description |
|---|---|---|
session_id | long | The session the stretch belongs to. It does not change on a resume — which is how it shows this is a continuation, not a new measurement. |
segment_started_at_epoch_ms | long | Start of the stretch. On session_paused, the start of the one just ended. |
session_started_at_epoch_ms | long | The first Start of the whole session; on session_started and session_resumed. |
paused_at_epoch_ms | long | The moment of the Stop; only on session_paused. |
paused_since_epoch_ms | long | When the pause being resumed from began; only on session_resumed. The difference against segment_started_at_epoch_ms is the length of the pause. |
segment_running_ms / running_ms_total | long | Length of the finished stretch and the sum of all so far; only on session_paused. The sum matches running_ms in the header. |
running_ms_before | long | Time accumulated before this stretch. Zero on session_started. |
distance_m / distance_m_before | double | Distance at the end of the stretch, and at the beginning of the resumed one. Ground covered during a pause does not count towards the distance — the anchor is discarded on resume. |
accepted_samples_before | long | How many samples the session had before the resume; only on session_resumed. |
end_reason / status | enum | Why and in what state the stretch ended; only on session_paused. The same values as end_reason and session_status in the header. |
previous_status / previous_writer_error | text | State and error of the previous stretch; only on session_resumed. A resume puts the session row back into the running state and overwrites its status and error, so this is the only place where a failed write remains traceable even after a successful finish. |
record_type=survey_point
A marker of the place the measurement is standing at. Indoors GNSS gives no position, so this is the only thing in the data that ties measured values to a point on a floor plan: the operator stops, taps Mark point, waits a moment and walks on. The marker stands on its own in the timeline and point_index is not added to the other records — values are attributed to a point by a time window after the marker, not by a key in every row.
| Key | Type | Description |
|---|---|---|
session_id | long | The session the point belongs to. |
point_index | int | Number of the point, from 1, within the running stretch. After a resume it starts from one again — points from two stretches are told apart by running_ms_total or by the preceding session_resumed event. |
label | text | Optional label from the field above the button. A point left blank stays a sequence number, which is enough for a walk along a numbered floor plan. |
marked_at_epoch_ms | long | System time of the tap. The row also carries the usual elapsed_realtime_ns, which is what the window is cut by. |
running_ms_total | long | Collection time accumulated at the moment of the marker, including previous stretches and excluding pauses. |
distance_m | double | Distance walked at the moment of the marker. The difference against the previous point says how many metres the operator really covered between them. |
The tap also buzzes, so every point has a haptic row with the same timestamp — the shake in the sensors is therefore not unexplained.
Measurement parameters
Every source can be switched on individually before the start, and for some of them the pace can be set as well. Values outside the range are pulled back to the limits — otherwise interval_s=0, say, would turn the periodic loop into a tight loop.
| Source | Parameter | Default · range |
|---|---|---|
| sensors (7×) | interval_ms | 50 ms · 10–1,000 |
noise | sample_rate_hz | 8,000 Hz · 8,000–48,000 |
location | interval_ms | 1,000 ms · 100–60,000 |
gnss_raw | interval_ms | 1,000 ms · 100–60,000 |
wifi, wifi_rtt, mobile_network, network_context | interval_s | 30 s · 5–300 |
bluetooth_classic, bluetooth_ble | scan_s / pause_s | 15 s · 5–60 / 45 s · 5–300 |
internet_quality | transport_target | Wi‑Fi + cellular data · AUTO, WIFI, CELLULAR, BOTH |
sample_interval_ms | 1,000 ms · 500–5,000 | |
download_kib / upload_kib | 64 KiB · 1–1,024 / 8 KiB · 1–256 | |
latency_probe_count | 3 × · 2–3 | |
capacity_interval_s | 30 s · 10–300 | |
capacity_download_kib | 1,024 KiB · 64–10,240 | |
capacity_upload_kib | 128 KiB · 8–2,048 | |
download_mb (manual test) | 10 MB · 1–100 | |
upload_mb (manual test) | 5 MB · 1–50 | |
| Internal limits: 3 s request timeout, 5 s ceiling for a short transfer, 15 s ceiling for a burst, 60 s ceiling for a manual test. | ||
wifi_throughput | cycle_interval_s | 60 s · 10–600 |
connect_timeout_s | 30 s · 5–120 | |
download_kib | 1,024 KiB · 64–10,240 | |
upload_kib | 128 KiB · 8–2,048 | |
latency_probe_count | 3 × · 1–5 | |
| Internal limit: 20 s timeout for a single HTTPS request. The list of measured networks is not a parameter — it is entered separately in the source card, and the passwords are stored in the app preferences in readable form. | ||
Storage in the phone
The data sits in a local Room / SQLite database, schema version 4. It is never sent anywhere; exporting to a file is up to the user.
Table sensor_samples
| Column | Type | Description |
|---|---|---|
id | long | Primary key. It determines the order of samples that share a timestamp. |
sessionId | long | Reference to the session; deleting the session deletes the samples too. |
source | text | Source of the sample. |
timestampEpochMs | long | Exported as epoch_ms. |
elapsedRealtimeNanos | long | Exported as elapsed_realtime_ns. |
payload | text | Payload of the source, including the attached suffixes. |
Table measurement_sessions
One row per measurement run. The columns match the fields in the export header; on top of that, acceptedSamples, writtenSamples, droppedSamples and lastPersistedAtEpochMs are updated as the run proceeds, in the same transaction as the insert of a batch of samples. If the process dies, the session stays in the RUNNING state and the next app start closes it as INTERRUPTED with end_reason=PROCESS_RECOVERY — the partial record stays exportable.