Présentation de Wi-Fi Aware

Les fonctionnalités Wi-Fi Aware permettent aux appareils équipés d'Android 8.0 (niveau d'API 26) ou version ultérieure de se détecter les uns les autres et de s'y connecter directement, sans aucun autre type de connectivité entre eux. Wi-Fi Aware est également connu sous le nom de Neighbor Awareness Networking (NAN).

La mise en réseau Wi-Fi Aware consiste à former des clusters avec des appareils voisins ou à créer un cluster si l'appareil est le premier d'une zone. Ce comportement de clustering s'applique à l'ensemble de l'appareil et est géré par le service système Wi-Fi Aware. Les applications n'ont aucun contrôle sur le comportement du clustering. Les applications utilisent les API Wi-Fi Aware pour communiquer avec le service système Wi-Fi Aware, qui gère le matériel Wi-Fi Aware sur l'appareil.

Les API Wi-Fi Aware permettent aux applications d'effectuer les opérations suivantes:

  • Découvrir d'autres appareils:l'API dispose d'un mécanisme permettant de détecter d'autres appareils à proximité. Le processus commence lorsqu'un appareil publie un ou plusieurs services visibles. Ensuite, lorsqu'un appareil s'abonne à un ou plusieurs services et entre dans la plage Wi-Fi de l'éditeur, l'abonné reçoit une notification indiquant qu'un éditeur correspondant a été découvert. Une fois que l'abonné a découvert un éditeur, il peut soit envoyer un court message, soit établir une connexion réseau avec l'appareil découvert. Les appareils peuvent être à la fois des éditeurs et des abonnés.

  • Créez une connexion réseau:une fois que deux appareils ont découvert l'un de l'autre, ils peuvent créer une connexion réseau Wi-Fi Aware bidirectionnelle sans point d'accès.

Les connexions réseau Wi-Fi Aware acceptent des débits plus élevés sur de plus longues distances que les connexions Bluetooth. Ces types de connexions sont utiles pour les applications qui partagent de grandes quantités de données entre les utilisateurs, telles que les applications de partage de photos.

Améliorations apportées à Android 12 (niveau d'API 31)

Android 12 (niveau d'API 31) apporte quelques améliorations à Wi-Fi Aware:

  • Sur les appareils équipés d'Android 12 (niveau d'API 31) ou version ultérieure, vous pouvez utiliser le rappel onServiceLost() pour être averti lorsque votre application perd un service détecté en raison de l'arrêt ou du déplacement du service.
  • La configuration des chemins de données Wi-Fi Aware a été simplifiée. Les versions antérieures utilisaient la messagerie L2 pour fournir l'adresse MAC de l'initiateur, ce qui a introduit une latence. Sur les appareils équipés d'Android 12 ou version ultérieure, le répondeur (serveur) peut être configuré pour accepter n'importe quel pair. Autrement dit, il n'a pas besoin de connaître à l'avance l'adresse MAC de l'initiateur. Cela accélère le lancement des chemins de données et active plusieurs liaisons point à point avec une seule requête réseau.
  • Les applications exécutées sur Android 12 ou version ultérieure peuvent utiliser la méthode WifiAwareManager.getAvailableAwareResources() pour obtenir le nombre de chemins de données actuellement disponibles, de sessions de publication et de sessions d'abonnement. Cela peut aider l'application à déterminer si suffisamment de ressources sont disponibles pour exécuter la fonctionnalité souhaitée.

Configuration initiale

Pour configurer votre application afin d'utiliser la détection et la mise en réseau Wi-Fi Aware, procédez comme suit:

  1. Demandez les autorisations suivantes dans le fichier manifeste de votre application:

    <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. Vérifiez si l'appareil est compatible avec le Wi-Fi Aware à l'aide de l'API PackageManager, comme indiqué ci-dessous:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
    

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
    
  3. Vérifiez si le service Wi-Fi Aware est actuellement disponible. Le service Wi-Fi Aware est peut-être présent sur l'appareil, mais il n'est peut-être pas disponible actuellement, car l'utilisateur a désactivé le Wi-Fi ou la localisation. En fonction de leurs capacités matérielles et de micrologiciel, certains appareils peuvent ne pas être compatibles avec Wi-Fi Aware si vous utilisez le Wi-Fi Direct, SoftAP ou le partage de connexion. Pour vérifier si le Wi-Fi Aware est actuellement disponible, appelez isAvailable().

    La disponibilité du service Wi-Fi Aware peut changer à tout moment. Votre application doit enregistrer un BroadcastReceiver pour recevoir ACTION_WIFI_AWARE_STATE_CHANGED, qui est envoyé chaque fois que la disponibilité change. Lorsque votre application reçoit l'intent de diffusion, elle doit supprimer toutes les sessions existantes (en supposant que le service Wi-Fi Aware a été interrompu), puis vérifier l'état actuel de la disponibilité et ajuster son comportement en conséquence. Par exemple :

    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);
    

Pour en savoir plus, consultez la section Diffusions.

Obtenir une session

Pour commencer à utiliser Wi-Fi Aware, votre application doit obtenir un WifiAwareSession en appelant attach(). Cette méthode effectue les opérations suivantes:

  • Active le matériel Wi-Fi Aware.
  • Rejoint ou forme un cluster Wi-Fi Aware.
  • Crée une session Wi-Fi Aware avec un espace de noms unique qui sert de conteneur pour toutes les sessions de découverte créées dans cet espace.

Si l'application est correctement associée, le système exécute le rappel onAttached(). Ce rappel fournit un objet WifiAwareSession que votre application doit utiliser pour toutes les autres opérations de session. Une application peut utiliser la session pour publier un service ou s'abonner à un service.

Votre application ne doit appeler attach() qu'une seule fois. Si votre application appelle attach() plusieurs fois, elle reçoit une session différente pour chaque appel, chacune avec son propre espace de noms. Cela peut être utile dans des scénarios complexes, mais doit généralement être évitée.

Publier un service

Pour rendre un service visible, appelez la méthode publish(), qui utilise les paramètres suivants:

  • PublishConfig spécifie le nom du service et d'autres propriétés de configuration, telles que le filtre de correspondance.
  • DiscoverySessionCallback spécifie les actions à exécuter lorsque des événements se produisent, par exemple lorsque l'abonné reçoit un message.

Exemple :

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 publication aboutit, la méthode de rappel onPublishStarted() est appelée.

Après la publication, lorsque les appareils exécutant des applications d'abonnés correspondantes se déplacent dans la portée Wi-Fi de l'appareil de publication, les abonnés découvrent le service. Lorsqu'un abonné découvre un éditeur, celui-ci ne reçoit pas de notification. Toutefois, s'il envoie un message à l'éditeur, celui-ci reçoit une notification. Dans ce cas, la méthode de rappel onMessageReceived() est appelée. Vous pouvez utiliser l'argument PeerHandle de cette méthode pour renvoyer un message à l'abonné ou créer une connexion avec lui.

Pour arrêter de publier le service, appelez DiscoverySession.close(). Les sessions de découverte sont associées à leur WifiAwareSession parent. Si la session parente est fermée, les sessions de découverte associées le sont également. Bien que les objets supprimés soient également fermés, le système ne garantit pas que les sessions hors champ d'application seront fermées. Nous vous recommandons donc d'appeler explicitement les méthodes close().

S'abonner à un service

Pour vous abonner à un service, appelez la méthode subscribe(), qui utilise les paramètres suivants:

  • SubscribeConfig spécifie le nom du service auquel s'abonner et d'autres propriétés de configuration, telles que le filtre de correspondance.
  • DiscoverySessionCallback spécifie les actions à exécuter lorsque des événements se produisent, par exemple lorsqu'un éditeur est découvert.

Exemple :

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 l'opération d'abonnement réussit, le système appelle le rappel onSubscribeStarted() dans votre application. Étant donné que vous pouvez utiliser l'argument SubscribeDiscoverySession dans le rappel pour communiquer avec un éditeur une fois que votre application en a découvert un, vous devez enregistrer cette référence. Vous pouvez mettre à jour la session d'abonnement à tout moment en appelant updateSubscribe() sur la session de découverte.

À ce stade, votre abonnement attend que les éditeurs correspondants soient à portée du réseau Wi-Fi. Dans ce cas, le système exécute la méthode de rappel onServiceDiscovered(). Vous pouvez utiliser l'argument PeerHandle de ce rappel pour envoyer un message ou créer une connexion avec cet éditeur.

Pour cesser de vous abonner à un service, appelez DiscoverySession.close(). Les sessions de découverte sont associées à leur WifiAwareSession parent. Si la session parente est fermée, les sessions de découverte associées le sont également. Bien que les objets supprimés soient également fermés, le système ne garantit pas que les sessions hors champ d'application seront fermées. Nous vous recommandons donc d'appeler explicitement les méthodes close().

Envoie un message

Pour envoyer un message à un autre appareil, vous avez besoin des objets suivants:

Pour envoyer un message, appelez sendMessage(). Les rappels suivants peuvent ensuite se produire:

  • Une fois le message reçu par le pair, le système appelle le rappel onMessageSendSucceeded() dans l'application d'envoi.
  • Lorsque le pair reçoit un message, le système appelle le rappel onMessageReceived() dans l'application de réception.

Bien que le PeerHandle soit requis pour communiquer avec des pairs, vous ne devez pas vous en servir comme identifiant permanent des pairs. Les identifiants de niveau supérieur peuvent être utilisés par l'application intégrée au service de découverte lui-même ou dans les messages suivants. Vous pouvez intégrer un identifiant dans le service de découverte à l'aide de la méthode setMatchFilter() ou setServiceSpecificInfo() de PublishConfig ou SubscribeConfig. La méthode setMatchFilter() affecte la découverte, tandis que la méthode setServiceSpecificInfo() n'affecte pas la découverte.

L'intégration d'un identifiant dans un message implique de modifier le tableau d'octets du message pour inclure un identifiant (par exemple, pour les premiers octets).

Créer une connexion

Wi-Fi Aware permet la mise en réseau client-serveur entre deux appareils Wi-Fi Aware.

Pour configurer la connexion client-serveur:

  1. Utilisez la détection Wi-Fi Aware pour publier un service (sur le serveur) et vous abonner à un service (sur le client).

  2. Une fois que l'abonné a découvert l'éditeur, envoyez un message de l'abonné à l'éditeur.

  3. Démarrez un ServerSocket sur l'appareil de l'éditeur, puis définissez ou obtenez son port:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
    
  4. Utilisez le ConnectivityManager pour demander un réseau Wi-Fi Aware sur l'éditeur à l'aide d'un WifiAwareNetworkSpecifier, en spécifiant la session de découverte et le PeerHandle de l'abonné, que vous avez obtenus à partir du message transmis par l'abonné:

    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. Une fois que l'éditeur demande un réseau, il doit envoyer un message à l'abonné.

  6. Une fois que l'abonné a reçu le message de l'éditeur, demandez la connexion d'un réseau Wi-Fi Aware à l'abonné en utilisant la même méthode que sur l'éditeur. Ne spécifiez pas de port lorsque vous créez le NetworkSpecifier. Les méthodes de rappel appropriées sont appelées lorsque la connexion réseau est disponible, modifiée ou perdue.

  7. Une fois la méthode onAvailable() appelée sur l'abonné, un objet Network vous permet d'ouvrir un Socket pour communiquer avec ServerSocket sur l'éditeur, mais vous devez connaître l'adresse IPv6 et le port de ServerSocket. Vous les obtenez à partir de l'objet NetworkCapabilities fourni dans le rappel 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. Une fois la connexion réseau terminée, appelez unregisterNetworkCallback().

Diversité des applications similaires et détection de la position

Un appareil doté de fonctionnalités de localisation Wi-Fi DAR peut mesurer directement la distance par rapport aux pairs et utiliser ces informations pour limiter la détection de services Wi-Fi Aware.

L'API Wi-Fi DAR permet une portée directe à un pair Wi-Fi Aware à l'aide de son adresse MAC ou de son PeerHandle.

La détection Wi-Fi Aware peut être limitée à la découverte des services dans une zone de géorepérage particulière. Par exemple, vous pouvez configurer une zone de géorepérage permettant la découverte d'un appareil publiant un service "Aware_File_Share_Service_Name" dont la longueur n'est pas inférieure à 3 mètres (3 000 mm) et à 10 mètres (10 000 mm).

Pour activer le géorepérage, l'éditeur et l'abonné doivent prendre les mesures suivantes:

  • L'éditeur doit activer la plage sur le service publié à l'aide de setRangingEnabled(true).

    Si l'éditeur n'active pas la mesure des distances, toutes les contraintes de zone de géorepérage spécifiées par l'abonné sont ignorées et la découverte normale est effectuée, en ignorant la distance.

  • L'abonné doit spécifier une zone de géorepérage en combinant setMinDistanceMm et setMaxDistanceMm.

    Pour l'une ou l'autre de ces valeurs, une distance non spécifiée n'implique aucune limite. Si vous spécifiez uniquement la distance maximale, la distance minimale est de 0. Si vous spécifiez uniquement la distance minimale, il n'y a pas de maximum.

Lorsqu'un service pair est détecté dans une zone de géorepérage, le rappel onServiceDiscoveredWithinRange est déclenché, ce qui fournit la distance mesurée par rapport au pair. L'API Direct Wi-Fi DAR peut ensuite être appelée si nécessaire pour mesurer la distance ultérieurement.