public final class Ranging

  1. Object
  2. Ranging

Precision ranging: how far away another device is, and in which direction.

This is ultra-wideband ranging – Apple’s Nearby Interaction on iOS and Jetpack UWB on Android – which measures distance by timing a radio round trip rather than by guessing from signal strength. Where an RSSI estimate off a Bluetooth advertisement is worth a few meters on a good day, UWB is worth about ten centimeters, and on hardware with multiple antennas it also reports which way the peer is.

The shape of a session

Both platforms need the two devices to exchange a token over some channel they already share before any radio ranging can begin, so the API is in two steps and there is no way to collapse them:

if (!Ranging.isSupported()) {
    return;                       // no UWB radio on this device
}
Ranging.prepareSession(RangingRole.CONTROLLER).onResult((session, err) -> {
    if (err != null) {
        return;
    }
    // 1. publish our token however the two apps already talk --
    //    a GATT characteristic from com.codename1.bluetooth is typical
    characteristic.writeValue(session.getLocalToken().toByteArray());

    // 2. when theirs arrives, start ranging
    session.addRangingListener(new RangingAdapter() {
        public void updated(RangingUpdate u) {
            if (u.hasDistance()) {
                label.setText(Math.round(u.getDistance(RangingUnit.CENTIMETERS)) + " cm");
            }
        }
    });
    session.start(RangingToken.fromByteArray(theirToken));
});

A session ranges exactly one peer. That is a hard limit of Apple’s NINearbyPeerConfiguration rather than a simplification, so an app that tracks several peers prepares several sessions – which is also what the Android port does under the hood.

Threading

Every callback here – AsyncResource results and every RangingListener method – is delivered on the EDT.

Platform support

  • iOS – Nearby Interaction on devices with a U1 or newer chip (iPhone 11 and later). Peer and accessory ranging, direction where the hardware provides it. Not available on tvOS, watchOS or Mac Catalyst.
  • Android – Jetpack UWB on devices that report the UWB hardware feature. Peer ranging natively; an accessory is ranged by building a token with RangingToken.forUwbAddress.
  • Simulator, desktop and JavaScript – a simulated implementation with peers that really move, so ranging UI is developable without hardware. Reports NearbyAvailability.LOCAL_ONLY.
  • Every other portisSupported() is false and every call fails with NearbyError.NOT_SUPPORTED.

Methods

public static boolean isSupported()true when this port and this device can range at all.
public static NearbyAvailability getAvailability()How usable ranging is at this moment, which is a different question from isSupported(): a phone with a U1 chip whose owner denied the permission is supported and unavailable.
public static RangingCapabilities getCapabilities()What this device can actually measure.
public static AsyncResource<Boolean> requestPermissions(NearbyPermission... permissions)Asks for the runtime permissions ranging needs.
public static AsyncResource<RangingSession> prepareSession(RangingRole role)Allocates a ranging session and, with it, the local token to publish to the peer.

Inherited methods

Method details

isSupported

public static boolean isSupported()

true when this port and this device can range at all.

This answers for the hardware, not for whether a peer is nearby. It is the query to hide a feature on; use getAvailability() to tell a user why a supported feature is not working right now.

getAvailability

public static NearbyAvailability getAvailability()
How usable ranging is at this moment, which is a different question from isSupported(): a phone with a U1 chip whose owner denied the permission is supported and unavailable.

Returns

the current availability, never null

getCapabilities

public static RangingCapabilities getCapabilities()
What this device can actually measure. Never null: where ranging is absent this is RangingCapabilities.UNSUPPORTED, whose every query is false.

Returns

the capabilities of the local device

requestPermissions

public static AsyncResource<Boolean> requestPermissions(NearbyPermission... permissions)

Asks for the runtime permissions ranging needs.

Safe to call on every platform: a port with nothing to ask for resolves true without showing anything.

Parameters

permissions NearbyPermission...
what the app intends to do

Returns

resolves true when every requested permission is granted

prepareSession

public static AsyncResource<RangingSession> prepareSession(RangingRole role)
Allocates a ranging session and, with it, the local token to publish to the peer. The session is not ranging yet – call RangingSession.start once the peer’s token arrives.

Parameters

role RangingRole
which end of the session this device is. Ignored on platforms that negotiate roles themselves, but pick one anyway: Android needs exactly one controller.

Returns

resolves with the prepared session, or fails with a NearbyException