Descripción general del reconocimiento de Wi-Fi

Las funciones de Wi-Fi Aware permiten que los dispositivos que ejecutan Android 8.0 (nivel de API 26) y versiones posteriores se descubran y se conecten directamente entre sí sin ningún otro tipo de conectividad entre ellos. Wi-Fi Aware también se conoce como Neighbor Awareness Networking (NAN).

Las redes de Wi-Fi Aware funcionan formando clústeres con dispositivos cercanos o creando un clúster nuevo si el dispositivo es el primero en un área. Este comportamiento de agrupamiento en clústeres se aplica a todo el dispositivo y lo administra el servicio del sistema de Wi-Fi Aware. Las apps no tienen control sobre el comportamiento de agrupamiento en clústeres. Las apps usan las APIs de Wi-Fi Aware para comunicarse con el servicio del sistema de Wi-Fi Aware, que administra el hardware de Wi-Fi Aware en el dispositivo.

Las API de reconocimiento de Wi-Fi permiten que las apps realicen las siguientes operaciones:

  • Descubrir otros dispositivos: La API tiene un mecanismo para encontrar otros dispositivos cercanos. El proceso comienza cuando un dispositivo publica uno o más servicios detectables. Luego, cuando un dispositivo se suscribe a uno o más servicios y entra en el rango de Wi-Fi del publicador, el suscriptor recibe una notificación de que se descubrió un publicador coincidente. Después de que el suscriptor descubre un publicador, puede enviar un mensaje corto o establecer una conexión de red con el dispositivo descubierto. Los dispositivos pueden ser simultáneamente publicadores y suscriptores.

  • Crea una conexión de red: Después de que dos dispositivos se descubran entre sí, pueden crear una conexión de red Wi-Fi Aware bidireccional sin un punto de acceso.

Las conexiones de red de Wi-Fi Aware admiten tasas de capacidad de procesamiento más altas en distancias más largas que las conexiones Bluetooth. Estos tipos de conexiones son útiles para las apps que comparten grandes cantidades de datos entre usuarios, como las apps para compartir fotos.

Mejoras en Android 13 (nivel de API 33)

En los dispositivos que ejecutan Android 13 (nivel de API 33) y versiones posteriores que admiten el modo de comunicación instantánea, las apps pueden usar los métodos PublishConfig.Builder.setInstantCommunicationModeEnabled() y SubscribeConfig.Builder.setInstantCommunicationModeEnabled() para habilitar o inhabilitar el modo de comunicación instantánea para una sesión de descubrimiento de publicador o suscriptor. El modo de comunicación instantánea acelera el intercambio de mensajes, el descubrimiento de servicios y cualquier ruta de datos configurada como parte de una sesión de descubrimiento de publicador o suscriptor. Para determinar si un dispositivo admite el modo de comunicación instantánea, usa el método isInstantCommunicationModeSupported().

Mejoras de Android 12 (nivel de API 31)

Android 12 (nivel de API 31) agrega algunas mejoras a Wi-Fi Aware:

  • En los dispositivos que ejecutan Android 12 (nivel de API 31) o versiones posteriores, puedes usar la devolución de llamada onServiceLost() para recibir una alerta cuando tu app haya perdido un servicio descubierto debido a que se detuvo o se movió fuera del alcance.
  • Se simplificó la configuración de las rutas de acceso de datos de Wi-Fi Aware. Las versiones anteriores usaban mensajes de L2 para proporcionar la dirección MAC del iniciador, lo que generaba latencia. En los dispositivos que ejecutan Android 12 y versiones posteriores, el respondedor (servidor) se puede configurar para que acepte cualquier par, es decir, no necesita conocer la dirección MAC del iniciador de antemano. Esto acelera la activación de la ruta de acceso de datos y habilita varios vínculos de punto a punto con solo una solicitud de red.
  • Las apps que se ejecutan en Android 12 o versiones posteriores pueden usar el método WifiAwareManager.getAvailableAwareResources() para obtener la cantidad de rutas de datos, sesiones de publicación y sesiones de suscripción disponibles actualmente. Esto puede ayudar a la app a determinar si hay suficientes recursos disponibles para ejecutar la funcionalidad deseada.

Configuración inicial

Para configurar tu app de modo que use el descubrimiento y las redes de Wi-Fi Aware, sigue estos pasos:

  1. Solicita los siguientes permisos en el manifiesto de tu app:

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                     <!-- If your app derives location information from
                          Wi-Fi APIs, don't include the "usesPermissionFlags"
                          attribute. -->
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                     <!-- If any feature in your app relies on precise location
                          information, don't include the "maxSdkVersion"
                          attribute. -->
                     android:maxSdkVersion="32" />
  2. Verifica si el dispositivo admite Wi-Fi Aware con la API de PackageManager, como se muestra a continuación:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
  3. Comprueba si el reconocimiento de Wi-Fi está disponible actualmente. Es posible que Wi-Fi Aware exista en el dispositivo, pero que no esté disponible en ese momento porque el usuario inhabilitó la conexión Wi-Fi o la ubicación. Según las capacidades de hardware y firmware, es posible que algunos dispositivos no admitan Wi-Fi Aware si se usan Wi-Fi Direct, SoftAP o la conexión mediante dispositivo móvil. Para verificar si Wi-Fi Aware está disponible, llama a isAvailable().

    La disponibilidad del reconocimiento de Wi-Fi puede cambiar en cualquier momento. Tu app debe registrar un BroadcastReceiver para recibir ACTION_WIFI_AWARE_STATE_CHANGED, que se envía cada vez que cambia la disponibilidad. Cuando tu app reciba el intent de transmisión, deberá descartar todas las sesiones existentes (suponiendo que se interrumpió el servicio de Wi-Fi Aware), verificar el estado actual de disponibilidad y ajustar su comportamiento según corresponda. Por ejemplo:

    Kotlin

    val wifiAwareManager = context.getSystemService(Context.WIFI_AWARE_SERVICE) as WifiAwareManager?
    val filter = IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED)
    val myReceiver = object : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            // discard current sessions
            if (wifiAwareManager?.isAvailable) {
                ...
            } else {
                ...
            }
        }
    }
    context.registerReceiver(myReceiver, filter)

    Java

    WifiAwareManager wifiAwareManager = 
            (WifiAwareManager)context.getSystemService(Context.WIFI_AWARE_SERVICE)
    IntentFilter filter =
            new IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
    BroadcastReceiver myReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // discard current sessions
            if (wifiAwareManager.isAvailable()) {
                ...
            } else {
                ...
            }
        }
    };
    context.registerReceiver(myReceiver, filter);

Para obtener más información, consulta Transmisiones.

Obtén una sesión

Para comenzar a usar Wi-Fi Aware, tu app debe obtener un objeto WifiAwareSession llamando a attach(). Este método hace lo siguiente:

  • Activa el hardware de reconocimiento de Wi-Fi.
  • Se une a un clúster de reconocimiento de Wi-Fi o forma uno.
  • Crea una sesión de Wi-Fi Aware con un espacio de nombres único que actúa como contenedor para todas las sesiones de descubrimiento creadas en él.

Si la app se conecta correctamente, el sistema ejecuta la devolución de llamada onAttached(). Esta devolución de llamada proporciona un objeto WifiAwareSession que tu app debería usar para todas las operaciones de sesión posteriores. Una app puede usar la sesión para publicar un servicio o suscribirse a un servicio.

Tu app debería llamar a attach() solo una vez. Si tu app llama a attach() varias veces, recibe una sesión diferente para cada llamada, cada una con su propio espacio de nombres. Esto puede ser útil en situaciones complejas, pero, en general, debería evitarse.

Publica un servicio

Para que un servicio sea detectable, llama al método publish(), que toma los siguientes parámetros:

  • PublishConfig especifica el nombre del servicio y otras propiedades de configuración, como el filtro de coincidencia.
  • DiscoverySessionCallback especifica las acciones que se deben ejecutar cuando se producen eventos, como cuando el suscriptor recibe un mensaje.

Por ejemplo:

Kotlin

val config: PublishConfig = PublishConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.publish(config, object : DiscoverySessionCallback() {

    override fun onPublishStarted(session: PublishDiscoverySession) {
        ...
    }

    override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
        ...
    }
})

Java

PublishConfig config = new PublishConfig.Builder()
    .setServiceName(“Aware_File_Share_Service_Name”)
    .build();

awareSession.publish(config, new DiscoverySessionCallback() {
    @Override
    public void onPublishStarted(PublishDiscoverySession session) {
        ...
    }
    @Override
    public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
        ...
    }
}, null);

Si la publicación se realiza correctamente, se llama al método de devolución de llamada onPublishStarted().

Después de la publicación, cuando los dispositivos que ejecutan apps de suscriptor coincidentes se mueven al rango de Wi-Fi del dispositivo de publicación, los suscriptores descubren el servicio. Cuando un suscriptor descubre un publicador, este no recibe una notificación; sin embargo, si el suscriptor envía un mensaje al publicador, este recibe una notificación. Cuando eso sucede, se llama al método de devolución de llamada onMessageReceived(). Puedes usar el argumento PeerHandle de este método para enviar un mensaje de vuelta al suscriptor o crear una conexión con él.

Para dejar de publicar el servicio, llama a DiscoverySession.close(). Las sesiones de descubrimiento se asocian con su WifiAwareSession superior. Si se cierra la sesión superior, también se cierran las sesiones de descubrimiento asociadas. Si bien los objetos descartados también se cierran, el sistema no garantiza cuándo se cierran las sesiones fuera del alcance, por lo que te recomendamos que llames de forma explícita a los métodos close().

Suscríbete a un servicio

Para suscribirte a un servicio, llama al método subscribe(), que toma los siguientes parámetros:

  • SubscribeConfig especifica el nombre del servicio al que se suscribirá y otras propiedades de configuración, como el filtro de coincidencia.
  • DiscoverySessionCallback especifica las acciones que se deben ejecutar cuando se producen eventos, como cuando se descubre un publicador.

Por ejemplo:

Kotlin

val config: SubscribeConfig = SubscribeConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.subscribe(config, object : DiscoverySessionCallback() {

    override fun onSubscribeStarted(session: SubscribeDiscoverySession) {
        ...
    }

    override fun onServiceDiscovered(
            peerHandle: PeerHandle,
            serviceSpecificInfo: ByteArray,
            matchFilter: List<ByteArray>
    ) {
        ...
    }
}, null)

Java

SubscribeConfig config = new SubscribeConfig.Builder()
    .setServiceName("Aware_File_Share_Service_Name")
    .build();

awareSession.subscribe(config, new DiscoverySessionCallback() {
    @Override
    public void onSubscribeStarted(SubscribeDiscoverySession session) {
        ...
    }

    @Override
    public void onServiceDiscovered(PeerHandle peerHandle,
            byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
        ...
    }
}, null);

Si la operación de suscripción se realiza correctamente, el sistema llama a la devolución de llamada onSubscribeStarted() en tu app. Como puedes usar el argumento SubscribeDiscoverySession en la devolución de llamada para comunicarte con un publicador después de que tu app haya descubierto uno, debes guardar esta referencia. Puedes actualizar la sesión de suscripción en cualquier momento llamando a updateSubscribe() en la sesión de descubrimiento.

En este punto, tu suscripción espera a que los publicadores coincidentes entren en el rango de Wi-Fi. Cuando esto sucede, el sistema ejecuta el método de devolución de llamada onServiceDiscovered(). Puedes usar el argumento PeerHandle de esta devolución de llamada para enviar un mensaje o crear una conexión con ese publicador.

Para dejar de suscribirte a un servicio, llama a DiscoverySession.close(). Las sesiones de descubrimiento se asocian con su WifiAwareSession superior. Si se cierra la sesión superior, también se cierran las sesiones de descubrimiento asociadas. Si bien los objetos descartados también se cierran, el sistema no garantiza cuándo se cierran las sesiones fuera del alcance, por lo que te recomendamos que llames de forma explícita a los métodos close().

Enviar un mensaje

Para enviar un mensaje a otro dispositivo, necesitas los siguientes objetos:

Para enviar un mensaje, llama a sendMessage(). Luego, pueden ocurrir las siguientes devoluciones de llamada:

  • Cuando el par recibe el mensaje correctamente, el sistema llama a la devolución de llamada onMessageSendSucceeded() en la app de envío.
  • Cuando el par recibe un mensaje, el sistema llama a la devolución de llamada onMessageReceived() en la app receptora.

Aunque se requiere el PeerHandle para comunicarse con los pares, no debes confiar en él como un identificador permanente de los pares. La aplicación puede usar identificadores de nivel superior, incorporados en el servicio de descubrimiento o en mensajes posteriores. Puedes incorporar un identificador en el servicio de descubrimiento con el método setMatchFilter() o setServiceSpecificInfo() de PublishConfig o SubscribeConfig. El método setMatchFilter() afecta el descubrimiento, mientras que el método setServiceSpecificInfo() no lo hace.

Incorporar un identificador en un mensaje implica modificar el array de bytes del mensaje para incluir un identificador (por ejemplo, como el primer par de bytes).

Crear una conexión

El reconocimiento de Wi-Fi admite la red cliente-servidor entre dos dispositivos con esa función.

Para configurar la conexión cliente-servidor:

  1. Usa el descubrimiento de Wi-Fi Aware para publicar un servicio (en el servidor) y suscribirte a un servicio (en el cliente).

  2. Una vez que el suscriptor descubra al publicador, envía un mensaje del suscriptor al publicador.

  3. Inicia un ServerSocket en el dispositivo publicador y configura u obtén su puerto:

    Kotlin

    val ss = ServerSocket(0)
    val port = ss.localPort

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. Usa ConnectivityManager para solicitar una red Wi-Fi Aware en el publicador con un WifiAwareNetworkSpecifier, especificando la sesión de descubrimiento y la PeerHandle del suscriptor, que obtuviste del mensaje que transmitió el suscriptor:

    Kotlin

    val networkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build()
    val myNetworkRequest = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build()
    val callback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            ...
        }
    
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
            ...
        }
    
        override fun onLost(network: Network) {
            ...
        }
    }
    
    connMgr.requestNetwork(myNetworkRequest, callback);

    Java

    NetworkSpecifier networkSpecifier = new WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build();
    NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build();
    ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            ...
        }
    
        @Override
        public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
            ...
        }
    
        @Override
        public void onLost(Network network) {
            ...
        }
    };
    
    ConnectivityManager connMgr.requestNetwork(myNetworkRequest, callback);
  5. Una vez que el publicador solicita una red, debe enviar un mensaje al suscriptor.

  6. Una vez que el suscriptor reciba el mensaje del publicador, solicita una red Wi-Fi Aware en el suscriptor con el mismo método que en el publicador. No especifiques un puerto cuando crees el NetworkSpecifier. Se llama a los métodos de devolución de llamada adecuados cuando la conexión de red está disponible, cambia o se pierde.

  7. Una vez que se llama al método onAvailable() en el suscriptor, hay un objeto Network disponible con el que puedes abrir un Socket para comunicarte con el ServerSocket en el publicador, pero debes conocer la dirección IPv6 y el puerto del ServerSocket. Puedes obtenerlos del objeto NetworkCapabilities proporcionado en la devolución de llamada onCapabilitiesChanged():

    Kotlin

    val peerAwareInfo = networkCapabilities.transportInfo as WifiAwareNetworkInfo
    val peerIpv6 = peerAwareInfo.peerIpv6Addr
    val peerPort = peerAwareInfo.port
    ...
    val socket = network.getSocketFactory().createSocket(peerIpv6, peerPort)

    Java

    WifiAwareNetworkInfo peerAwareInfo = (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo();
    Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr();
    int peerPort = peerAwareInfo.getPort();
    ...
    Socket socket = network.getSocketFactory().createSocket(peerIpv6, peerPort);
  8. Cuando termines con la conexión de red, llama a unregisterNetworkCallback().

Rango de app similares y descubrimiento del conocimiento de la ubicación

Un dispositivo con capacidades de ubicación de Wi-Fi RTT puede medir directamente la distancia a los pares y usar esta información para restringir el descubrimiento de servicios de Wi-Fi Aware.

La API de Wi-Fi RTT permite realizar mediciones de distancia directas a un par de Wi-Fi Aware con su dirección MAC o su PeerHandle.

El descubrimiento de Wi-Fi Aware se puede restringir para que solo descubra servicios dentro de un perímetro virtual específico. Por ejemplo, puedes configurar un geovallado que permita descubrir un dispositivo que publique un servicio "Aware_File_Share_Service_Name" que no esté a menos de 3 metros (especificado como 3,000 mm) ni a más de 10 metros (especificado como 10,000 mm).

Para habilitar el geovallado, tanto el publicador como el suscriptor deben tomar medidas:

  • El publicador debe habilitar la medición de distancia en el servicio publicado con setRangingEnabled(true).

    Si el publicador no habilita la medición de distancia, se ignorarán las restricciones de geovalla que especifique el suscriptor y se realizará el descubrimiento normal, sin tener en cuenta la distancia.

  • El suscriptor debe especificar una geovalla con alguna combinación de setMinDistanceMm y setMaxDistanceMm.

    Para cualquier valor, si no se especifica una distancia, esto implica que no hay un límite. Si solo especificas la distancia máxima, se implica una distancia mínima de 0. Especificar solo la distancia mínima implica que no hay un máximo.

Cuando se descubre un servicio de par dentro de un geovallado, se activa la devolución de llamada onServiceDiscoveredWithinRange, que proporciona la distancia medida al par. Luego, se puede llamar a la API de Wi-Fi RTT directa según sea necesario para medir la distancia en momentos posteriores.