Wi-Fi RTT in a Device Product: Define Ranging Capability and Failure States

A Wi-Fi RTT callback must be decoded as a status-bearing result, not as an unconditional distance. Establish feature support and current availability, request only the intended responder mode, then inspect each peer’s status before reading measurement fields. A failed result must never become zero distance or reuse an old successful value without an explicit stale label.

This guide pins Android behavior to Android 15, API level 35, source tag android-15.0.0_r1. Its measurement scope is two-sided IEEE 802.11mc ranging to infrastructure AP responders. Wi-Fi Aware peers, one-sided RTT and 802.11az non-trigger-based ranging are excluded. A phone or embedded Android device advertising modern Wi-Fi does not automatically implement the required ranging feature.

Build an eligibility gate before requesting measurements

Check the Wi-Fi RTT system feature, then current service availability. The pinned WifiRttManager documentation says its availability broadcast is not sticky: register for changes and then query isAvailable(). Availability can disappear after that query, so retain a request-level unavailable outcome rather than treating the earlier check as a reservation. Android 15 WifiRttManager

For each intended AP, retain the scan observation and its is80211mcResponder() indication. This is a capability gate, not proof that the next exchange will succeed. The device does not need to associate with that AP merely to range it. Record scan age, peer identity and the actual request set; do not silently replace an unavailable responder with another AP sharing its SSID. Android Wi-Fi RTT guide

Permissions require particular care. In the pinned Android 15 service, the infrastructure-AP ranging branch enforces fine-location permission, along with Wi-Fi access/change permission checks. The nearby-device exception applies to requests containing only Aware peers, outside this guide’s scope. Do not assume a general nearby-Wi-Fi permission statement is sufficient for AP ranging. Keep location-service state and the app’s permitted execution context in the target test matrix. Android 15 RttServiceImpl

Separate request failure from per-peer failure

Outcome Meaning Application representation
Request rejected or request-level failure callback The overall ranging attempt did not produce the expected result list Request failure with reason; no invented peer distance
Result list containing a failed peer That peer’s measurement is invalid Peer failure with its status preserved
Successful peer result Success-only fields may be accessed Candidate sample, still subject to mode and freshness policy
Unexpected, missing or late peer result The response does not complete the current request contract Explicit mismatch, missing or stale outcome

Correlate callbacks with an application request generation and map results by peer identity, not list position. A list can contain different outcomes for different peers. A delayed list from a previous screen or operating cycle must not overwrite the current ranging state. Keep request attempts and accepted peer samples as separate counters.

Wi-Fi RTT result admission first separates request failure, then checks each peer status before reading fields, validating measurement mode and freshness, and retaining signed distance with qualified standard deviation.
Only successful peer results unlock measurement getters. Success does not remove mode, freshness or quality-field validity checks.

Guard every success-only getter

Android 15’s RangingResult defines success, generic failure and a responder-not-supporting-802.11mc status. Methods including getDistanceMm(), measurement counts and the ranging timestamp throw IllegalStateException when called on a non-success result. Read status first and return a failure record without accessing those fields. Android 15 RangingResult

For a successful result, verify that the reported measurement mode is the qualified 802.11mc mode. A newer platform can expose other modes with different semantics; do not funnel all of them through a decoder whose acceptance tests covered only two-sided RTT.

Preserve the reported signed distance in millimeters. The API explicitly permits a negative measured distance for very close devices. Silently clamping it to zero loses evidence and can create a misleading proximity signal. If the product rejects negative estimates, retain the raw value with a specific product-policy rejection instead of relabeling the radio exchange as failed.

Do not interpret zero standard deviation as perfect accuracy

The API’s standard deviation describes measurements within one RTT burst. Its documentation says that a single successful measurement yields an invalid standard deviation represented as zero; zero successful measurements also lack valid spread information. Expose standard deviation as unavailable when fewer than two successful measurements support it. A reported spread from multiple samples is still not an independently validated absolute-error bound. RangingResult measurement-count and deviation contracts

The ranging timestamp uses milliseconds since boot, including sleep, corresponding to SystemClock.elapsedRealtime(). Compare it with the same clock domain when calculating age. Do not subtract it from a wall-clock date or carry it across a reboot without a boot/session identity. Callback arrival is additional context, not a replacement for measurement time.

In a hypothetical sample, status is success, distance is minus 12 mm and the successful-measurement count is one. The decoder should preserve the signed candidate distance and mark standard deviation unavailable. It should not report an exact zero-distance observation with zero uncertainty. Whether that sample can drive the product’s decision is a separate acceptance rule.

Qualify the decoder and the device separately

  1. Replay status cases. Feed generic failure and unsupported-responder results through a getter-trapping test double. Confirm that no success-only accessor is called.
  2. Replay mixed lists. Reorder results, combine success and failure, and include an unexpected peer. Verify identity matching and request-generation checks.
  3. Exercise quality boundaries. Test negative distance, zero and one successful measurement, ordinary multi-measurement spread, stale timestamps and timestamps inconsistent with the current clock epoch.
  4. Test target eligibility failures. On an authorized device/AP fixture, exercise feature absence, unavailable RTT, denied permissions and an unsupported responder. Preserve request-level versus peer-level reasons.
  5. Measure physical performance separately. Use independently established distances and declared orientation/environment conditions. Report attempted requests, per-peer failures, accepted samples and observed error together. Do not hide difficult cases by reporting accuracy only after aggressive rejection.

A local Python admission model passed the status/getter, signed-distance, spread, freshness and reordered-peer cases. It did not compile Android classes, request permissions, transmit ranging frames or measure accuracy. The physical campaign remains unexecuted.

Release a result schema containing request identity, peer identity, status, measurement mode, signed distance, observation age, counts and explicitly qualified spread. That Android-specific contract prevents API failures and sentinel values from becoming plausible-looking distance measurements.

Similar Posts