Rango entre dispositivos

Android 16 introduce el módulo Ranging, que proporciona una interfaz unificada y estandarizada para el rango preciso entre dispositivos. Puedes usar esta superficie de API para medir la distancia y la posición de dispositivos similares sin necesidad de controlar cada tecnología de medición de forma individual.

El módulo de Rango admite las siguientes tecnologías:

Capacidades y disponibilidades de rango

La clase RangingManager proporciona a las apps información sobre las tecnologías de medición de distancia admitidas por el dispositivo local, así como la disponibilidad y las capacidades de cada tecnología. Las apps pueden registrarse para recibir un objeto Callback y recibir actualizaciones sobre cualquier cambio en la disponibilidad o las capacidades de las tecnologías compatibles.

Roles del dispositivo

Un dispositivo que participa en una sesión de medición de distancia debe ser un iniciador o un respondedor. El dispositivo iniciador comienza la sesión de medición de distancia con uno o más dispositivos de respuesta. Un dispositivo de respuesta responde a las solicitudes de rango de un solo iniciador a la vez. Puedes especificar el rol de un dispositivo determinado en una sesión de rango con la clase RangingPreference.

Tipos de sesiones de rango

Cuando se inicia una sesión de medición de distancia entre dispositivos, a menudo es necesario establecer un transporte de datos fuera de banda (OOB) para intercambiar parámetros de la sesión.

El módulo Ranging puede controlar las negociaciones fuera de banda por ti, pero también admite implementaciones personalizadas fuera de banda.

Figura 1. Flujo OOB para tipos de sesión.

Implementación predeterminada de OOB

En este tipo de sesión (RANGING_SESSION_OOB), el módulo de Ranging controla las negociaciones fuera de banda para iniciar una sesión de medición de distancia. Selecciona los parámetros adecuados según las preferencias de rango proporcionadas por la app y usa las tecnologías apropiadas según lo que admiten ambos dispositivos. Este tipo de sesión usa un OOB specification estandarizado.

El módulo de Ranging solo define el formato y la secuencia de datos OOB que se usarán para interactuar con un dispositivo par. No controla la detección de dispositivos de pares ni el establecimiento de conexiones.

Implementación personalizada de OOB

En este tipo de sesión (RANGING_SESSION_RAW), la app omite el flujo OOB del módulo de Ranging y controla su propia negociación y parámetros OOB. Esto significa que la app debe determinar qué tecnologías admite el dispositivo par, negociar los parámetros de rango y comenzar la sesión de rango.

Preferencias de rango

Usa un objeto RangingPreference para especificar los parámetros seleccionados de una sesión de rango. Se incluye lo siguiente:

  • Rol del dispositivo. Indica si el dispositivo será el iniciador o el respondedor.
  • Configuración de rango. Un objeto RangingConfig especifica el tipo de sesión de rango y otros parámetros necesarios para iniciar una sesión de rango.
  • Configuración de la sesión. Un objeto SessionConfig especifica los parámetros que se deben aplicar en la sesión de medición de distancia, como el límite de medición, la fusión de sensores, la configuración de la zona geográfica y mucho más.

Permiso de rango

El módulo de Ranging requiere un nuevo permiso unificado (android.permission.RANGING) para acceder a todas las tecnologías de rango actuales y futuras. Este permiso se encuentra en la lista de NEARBY_DEVICES_PERMISSIONS.

<uses-permission android:name="android.permission.RANGING" />

Restricciones y limitaciones

El módulo de rango puede restringir el rango por varios motivos, incluidos los siguientes:

  • Las apps de terceros solo pueden realizar la medición en segundo plano con banda ultraancha y solo en dispositivos compatibles. No se permite la medición en segundo plano con otras tecnologías.
  • No se permite el rango cuando se alcanza la cantidad máxima de sesiones de rango simultáneas por dispositivo.
  • Es posible que se restrinja el rango debido a problemas de estado del sistema, como la batería, el rendimiento o la memoria.

El módulo de rango también tiene las siguientes limitaciones conocidas:

  • El módulo de Ranging solo admite la entrega de datos de rango a dispositivos pares para banda ultraancha. En el caso de otras tecnologías, el módulo de Rango solo entrega datos de rango al dispositivo iniciador.
  • El módulo de Ranging solo admite la adición dinámica de dispositivos en el modo de rango sin procesar y solo para banda ultraancha.
  • El módulo Ranging no admite sesiones de banda ultraancha de uno a varios para implementaciones OOB predeterminadas. Si pasas varios identificadores de dispositivos, el módulo crea una sesión uno a uno para cada dispositivo par que admita banda ultraancha.

Cómo realizar una sesión de medición de distancia

Para realizar una sesión de medición con el módulo Ranging, sigue estos pasos:

  1. Verifica que todos los dispositivos ejecuten Android 16 o versiones posteriores.
  2. Solicita el permiso android.permission.RANGING en el manifiesto de la app.
  3. Evaluar las capacidades y la disponibilidad de las tecnologías de rango
  4. Descubre un dispositivo par para operaciones de rango.
  5. Establece una conexión para un intercambio fuera de banda, usando cualquiera de los tipos de sesión que se describen en Tipos de sesión de medición de distancia.
  6. Inicia el proceso de medición y adquiere datos de medición de forma continua.
  7. Finaliza la sesión de rango.

En el siguiente ejemplo de código, se muestran estos pasos para el rol de iniciador y el rol de respuesta.

Kotlin

class RangingApp {

    // Starts a ranging session on the initiator side.
    fun startRangingInitiator(
        context: Context,
        deviceHandle: DeviceHandle,
        executor: Executor,
        callback: RangingSessionCallback
    ) {

        // Get the RangingManager which is the entry point for ranging module.
        val manager = context.getSystemService(RangingManager::class.java)

        // Create a new RangingSession using the provided executor and callback.
        val session = manager.createRangingSession(executor, callback)

        // Create an OobInitiatorRangingConfig, which specifies the ranging parameters for
        // the initiator role.
        val config = OobInitiatorRangingConfig.Builder()
            .setFastestRangingInterval(Duration.ofMillis(100))
            .setSlowestRangingInterval(Duration.ofMillis(5000))
            .setRangingMode(RANGING_MODE_AUTO)
            .setSecurityLevel(SECURITY_LEVEL_BASIC)
            .addDeviceHandle(deviceHandle)
            .build()

        // Create a RangingPreference, which specifies the role (initiator) and
        // configuration for the ranging session.
        val preference =
            RangingPreference.Builder(DEVICE_ROLE_INITIATOR, config).build()

        // Start ranging session.
        session.start(preference)

        // If successful, the ranging data will be sent through callback#onResults

        // Stop ranging session
        session.stop()
    }

    // Starts a ranging session on the responder side.
    fun startRangingResponder(
        context: Context,
        deviceHandle: DeviceHandle,
        executor: Executor,
        callback: RangingSessionCallback
    ) {

        // Get the RangingManager which is the entry point for ranging module.
        val manager = context.getSystemService(RangingManager::class.java)

        // Create a new RangingSession using the provided executor and callback.
        val session = manager.createRangingSession(executor, callback)

        // Create an OobResponderRangingConfig, which specifies the ranging parameters for
        // the responder role.
        val config = OobResponderRangingConfig.Builder(deviceHandle).build()

        // Create a RangingPreference, which specifies the role (responder) and
        // configuration for the ranging session.
        val preference =
            RangingPreference.Builder(DEVICE_ROLE_RESPONDER, config).build()

        // Start the ranging session.
        session.start(preference)

        // Stop the ranging session
        session.stop()
    }
}

Java

public class RangingApp {

    // Starts a ranging session on the initiator side.
    void startRangingInitiator(Context context, DeviceHandle deviceHandle, Executor executor, RangingSessionCallback callback) {

        // Get the RangingManager which is the entry point for ranging module.
        RangingManager manager = context.getSystemService(RangingManager.class);

        // Create a new RangingSession using the provided executor and callback.
        RangingSession session = manager.createRangingSession(executor, callback);

        // Create an OobInitiatorRangingConfig, which specifies the ranging parameters for
        // the initiator role.
        OobInitiatorRangingConfig config = new OobInitiatorRangingConfig.Builder()
                .setFastestRangingInterval(Duration.ofMillis(100))
                .setSlowestRangingInterval(Duration.ofMillis(5000))
                .setRangingMode(RANGING_MODE_AUTO)
                .setSecurityLevel(SECURITY_LEVEL_BASIC)
                .addDeviceHandle(deviceHandle)
                .build();

        // Create a RangingPreference, which specifies the role (initiator) and
        // configuration for the ranging session.
        RangingPreference preference =
                new RangingPreference.Builder(DEVICE_ROLE_INITIATOR, config).build();

        // Start ranging session.
        session.start(preference);

        // If successful, the ranging data will be sent through callback#onResults

        // Stop ranging session
        session.stop();

    }

    // Starts a ranging session on the responder side.
    void startRangingResponder(Context context,  DeviceHandle deviceHandle, Executor executor, RangingSessionCallback callback) {

        // Get the RangingManager which is the entry point for ranging module.
        RangingManager manager = context.getSystemService(RangingManager.class);

        // Create a new RangingSession using the provided executor and callback.
        RangingSession session = manager.createRangingSession(executor, callback);

        // Create an OobResponderRangingConfig, which specifies the ranging parameters for
        // the responder role.
        OobResponderRangingConfig config = new OobResponderRangingConfig.Builder(  deviceHandle).build();

        // Create a RangingPreference, which specifies the role (responder) and
        // configuration for the ranging session.
        RangingPreference preference =
                new RangingPreference.Builder(DEVICE_ROLE_RESPONDER, config).build();

        // Start the ranging session.
        session.start(preference);

        // Stop the ranging session
        session.stop();
    }
}

Interoperabilidad de UWB con dispositivos iOS

El módulo de rango admite la interoperabilidad con dispositivos iOS que usan banda ultraancha (UWB). Para realizar el rango con un dispositivo iOS, debes usar una implementación personalizada de OOB y configurar la sesión de rango con parámetros específicos que coincidan con el Protocolo de accesorios de interacción cercana de Apple. Consulta la app de ejemplo como referencia.

Cuando crees el objeto RangingPreference, usa RawRangingDevice y UwbRangingParams para especificar la configuración. Los siguientes parámetros son fundamentales para la interoperabilidad con iOS:

  • ID de configuración: Usa UwbRangingParams.CONFIG_UNICAST_DS_TWR.
  • Session Key Info: Proporciona un array de bytes que contiene el ID del proveedor y el IV de STS estático.
  • Canal complejo: Establece el número de canal y el índice de preámbulo para que coincidan con la configuración del dispositivo iOS.

En el siguiente fragmento de código, se muestra cómo crear un objeto RangingPreference para un dispositivo iniciador que realiza un rango con un dispositivo de respuesta de iOS:

Kotlin

// Create UwbRangingParams with iOS-specific configuration
val uwbRangingParams = UwbRangingParams.Builder(
    sessionId,
    UwbRangingParams.CONFIG_UNICAST_DS_TWR,
    sourceAddress,
    destinationAddress
)
    .setComplexChannel(
        UwbComplexChannel.Builder()
            .setChannel(channelNumber)
            .setPreambleIndex(preambleIndex)
            .build()
    )
    .setRangingUpdateRate(updateRate)
    .setSessionKeyInfo(sessionKeyInfo) // Vendor ID + STS IV
    .setSlotDuration(slotDuration)
    .build()

// Create RawRangingDevice
val rawRangingDevice = RawRangingDevice.Builder()
    .setRangingDevice(RangingDevice.Builder().build())
    .setUwbRangingParams(uwbRangingParams)
    .build()

// Create SessionConfig
val sessionConfig = SessionConfig.Builder()
    .setAngleOfArrivalNeeded(true)
    .setDataNotificationConfig(DataNotificationConfig.Builder()
        .setNotificationConfigType(DataNotificationConfig.NOTIFICATION_CONFIG_ENABLE)
        .build())
    .build()

// Create RangingPreference for the initiator
val preference = RangingPreference.Builder(
    RangingPreference.DEVICE_ROLE_INITIATOR,
    RawInitiatorRangingConfig.Builder()
        .addRawRangingDevice(rawRangingDevice)
        .build()
)
    .setSessionConfig(sessionConfig)
    .build()

Java

// Create UwbRangingParams with iOS-specific configuration
UwbRangingParams uwbRangingParams = new UwbRangingParams.Builder(
        sessionId,
        UwbRangingParams.CONFIG_UNICAST_DS_TWR,
        sourceAddress,
        destinationAddress)
        .setComplexChannel(new UwbComplexChannel.Builder()
                .setChannel(channelNumber)
                .setPreambleIndex(preambleIndex)
                .build())
        .setRangingUpdateRate(updateRate)
        .setSessionKeyInfo(sessionKeyInfo) // Vendor ID + STS IV
        .setSlotDuration(slotDuration)
        .build();

// Create RawRangingDevice
RawRangingDevice rawRangingDevice = new RawRangingDevice.Builder()
        .setRangingDevice(new RangingDevice.Builder().build())
        .setUwbRangingParams(uwbRangingParams)
        .build();

// Create SessionConfig
SessionConfig sessionConfig = new SessionConfig.Builder()
        .setAngleOfArrivalNeeded(true)
        .setDataNotificationConfig(new DataNotificationConfig.Builder()
                .setNotificationConfigType(DataNotificationConfig.NOTIFICATION_CONFIG_ENABLE)
                .build())
        .build();

// Create RangingPreference for the initiator
RangingPreference preference = new RangingPreference.Builder(
        RangingPreference.DEVICE_ROLE_INITIATOR,
        new RawInitiatorRangingConfig.Builder()
                .addRawRangingDevice(rawRangingDevice)
                .build())
        .setSessionConfig(sessionConfig)
        .build();

API de UWB Downlink-TDoA

Para obtener información sobre la API de DL-TDoA de UWB, consulta Nuevas funciones de Android 17.

App de ejemplo

Para ver un ejemplo de extremo a extremo sobre cómo usar el módulo de Ranging, consulta la app de ejemplo en AOSP. Esta app de ejemplo abarca todas las tecnologías de medición de distancia compatibles con el módulo de Ranging y, además, incluye flujos para ambos tipos de sesiones admitidos.

Interoperabilidad de UWB con dispositivos iOS

La app de ejemplo para Android admite el inicio de una sesión de medición de UWB con la app de ejemplo para iOS.

Figura 2: Uso de UWB en iOS y Android