Panoramica di Wi-Fi Aware

Le funzionalità Wi-Fi Aware consentono ai dispositivi con Android 8.0 (livello API 26) e versioni successive di rilevarsi e connettersi direttamente tra loro senza alcun altro tipo di connettività. Wi-Fi Aware è noto anche come Neighbor Awareness Networking (NAN).

Il networking Wi-Fi Aware funziona formando cluster con i dispositivi vicini o creando un nuovo cluster se il dispositivo è il primo in un'area. Questo comportamento di clustering si applica all'intero dispositivo ed è gestito dal servizio di sistema Wi-Fi aware. Le app non hanno alcun controllo sul comportamento di clustering. Le app utilizzano le API Wi-Fi Aware per comunicare con il servizio di sistema Wi-Fi Aware, che gestisce l'hardware Wi-Fi Aware sul dispositivo.

Le API Wi-Fi Aware consentono alle app di eseguire le seguenti operazioni:

  • Scoprire altri dispositivi:l'API dispone di un meccanismo per trovare altri dispositivi nelle vicinanze. Il processo inizia quando un dispositivo pubblica uno o più servizi rilevabili. Quando un dispositivo si abbona a uno o più servizi e entra nel raggio d'azione del Wi-Fi del publisher, l'abbonato riceve una notifica che lo informa che è stato rilevato un publisher corrispondente. Dopo aver rilevato un publisher, l'abbonato può inviare un breve messaggio o stabilire una connessione di rete con il dispositivo rilevato. I dispositivi possono essere contemporaneamente publisher e iscritti.

  • Creare una connessione di rete: dopo che due dispositivi si sono scoperti tra loro, possono creare una connessione di rete Wi-Fi Aware bidirezionale senza un punto di accesso.

Le connessioni di rete Wi-Fi Aware supportano velocità in uscita più elevate su distanze più lunghe rispetto alle connessioni Bluetooth. Questi tipi di connessioni sono utili per le app che condividono grandi quantità di dati tra gli utenti, ad esempio le app di condivisione di foto.

Miglioramenti di Android 13 (livello API 33)

Sui dispositivi con Android 13 (livello API 33) e versioni successive che supportano la modalità di comunicazione istantanea, le app possono utilizzare i metodi PublishConfig.Builder.setInstantCommunicationModeEnabled() e SubscribeConfig.Builder.setInstantCommunicationModeEnabled() per attivare o disattivare la modalità di comunicazione istantanea per una sessione di scoperta di publisher o abbonati. La modalità di comunicazione istantanea accelera lo scambio di messaggi, la scoperta dei servizi e qualsiasi percorso dati configurato nell'ambito di una sessione di scoperta di publisher o abbonati. Per determinare se un dispositivo supporta la modalità di comunicazione immediata, utilizza il metodo isInstantCommunicationModeSupported().

Miglioramenti di Android 12 (livello API 31)

Android 12 (livello API 31) aggiunge alcuni miglioramenti a Wi-Fi Aware:

  • Sui dispositivi con Android 12 (livello API 31) o versioni successive, puoi utilizzare il callback onServiceLost() per ricevere un avviso quando la tua app ha perso un servizio rilevato a causa dell'interruzione o del superamento del raggio d'azione del servizio.
  • La configurazione dei percorsi dati Wi-Fi Aware è stata semplificata. Le versioni precedenti utilizzavano la messaggistica L2 per fornire l'indirizzo MAC dell'iniziatore, il che introduceva la latenza. Sui dispositivi con Android 12 e versioni successive, il rispondente (server) può essere configurato per accettare qualsiasi peer, ovvero non deve conoscere in anticipo l'indirizzo MAC dell'iniziatore. In questo modo, viene accelerato il riattivazione del percorso dati e vengono abilitati più link point-to-point con una sola richiesta di rete.
  • Le app in esecuzione su Android 12 o versioni successive possono utilizzare il metodo WifiAwareManager.getAvailableAwareResources() per ottenere il numero di percorsi di dati attualmente disponibili, pubblicare sessioni e iscriversi alle sessioni. In questo modo l'app può stabilire se sono disponibili risorse sufficienti per eseguire la funzionalità desiderata.

Configurazione iniziale

Per configurare l'app in modo che utilizzi la rete e il rilevamento Wi-Fi Aware, svolgi i seguenti passaggi:

  1. Richiedi le seguenti autorizzazioni nel file manifest dell'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 se il dispositivo supporta Wi-Fi Aware con l'PackageManager API, come mostrato di seguito:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
  3. Controlla se Wi-Fi Aware è attualmente disponibile. La funzionalità Wi-Fi Aware potrebbe essere presente sul dispositivo, ma potrebbe non essere attualmente disponibile perché l'utente ha disattivato il Wi-Fi o la posizione. A seconda delle funzionalità hardware e del firmware, alcuni dispositivi potrebbero non supportare la tecnologia Wi-Fi Aware se sono in uso Wi-Fi Direct, SoftAP o tethering. Per verificare se Wi-Fi Aware è attualmente disponibile, chiama isAvailable().

    La disponibilità di Wi-Fi Aware può cambiare in qualsiasi momento. L'app deve registrare un BroadcastReceiver per ricevere ACTION_WIFI_AWARE_STATE_CHANGED, che viene inviato ogni volta che la disponibilità cambia. Quando l'app riceve l'intent di trasmissione, deve ignorare tutte le sessioni esistenti (si presume che il servizio Wi-Fi Aware sia stato interrotto), quindi controllare lo stato attuale della disponibilità e modificare il relativo comportamento di conseguenza. Ad esempio:

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

Per ulteriori informazioni, consulta la sezione Trasmissioni.

Ottenere una sessione

Per iniziare a utilizzare Wi-Fi Aware, la tua app deve ottenere un WifiAwareSession chiamando attach(). Questo metodo esegue le seguenti operazioni:

  • Attiva l'hardware Wi-Fi Aware.
  • Si unisce a un cluster Wi-Fi Aware o ne forma uno.
  • Crea una sessione Wi-Fi Aware con uno spazio dei nomi univoco che funge da contenitore per tutte le sessioni di rilevamento create al suo interno.

Se l'app si connette correttamente, il sistema esegue il callback onAttached(). Questo callback fornisce un oggetto WifiAwareSession che la tua app deve utilizzare per tutte le ulteriori operazioni della sessione. Un'app può utilizzare la sessione per pubblicare un servizio o abbonarsi a un servizio.

L'app deve chiamare attach() una sola volta. Se la tua app chiama attach() più volte, riceve una sessione diversa per ogni chiamata, ciascuna con il proprio spazio dei nomi. Questa opzione potrebbe essere utile in scenari complessi, ma in genere dovrebbe essere evitata.

Pubblicare un servizio

Per rendere rilevabile un servizio, chiama il metodo publish(), che accetta i seguenti parametri:

  • PublishConfig specifica il nome del servizio e altre proprietà di configurazione, ad esempio il filtro di corrispondenza.
  • DiscoverySessionCallback specifica le azioni da eseguire quando si verificano eventi, ad esempio quando l'abbonato riceve un messaggio.

Ecco un esempio:

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

Se la pubblicazione va a buon fine, viene chiamato il metodo callback onPublishStarted().

Dopo la pubblicazione, quando i dispositivi su cui sono in esecuzione le app degli abbonati corrispondenti entrano nel raggio d'azione del Wi-Fi del dispositivo di pubblicazione, gli abbonati scoprono il servizio. Quando un sottoscrittore scopre un publisher, quest'ultimo non riceve una notifica. Tuttavia, se il sottoscrittore invia un messaggio al publisher, quest'ultimo riceve una notifica. In questo caso, viene chiamato il metodo callback onMessageReceived(). Puoi utilizzare l'argomento PeerHandle di questo metodo per inviare un messaggio all'abbonato o per creare una connessione con lui.

Per interrompere la pubblicazione del servizio, chiama DiscoverySession.close(). Le sessioni di scoperta sono associate al relativo elemento principale WifiAwareSession. Se la sessione principale viene chiusa, vengono chiuse anche le sessioni di rilevamento associate. Anche se gli oggetti eliminati vengono chiusi, il sistema non garantisce quando vengono chiuse le sessioni fuori ambito, pertanto ti consigliamo di chiamare esplicitamente i metodi close().

Abbonarsi a un servizio

Per abbonarti a un servizio, chiama il metodo subscribe(), che accetta i seguenti parametri:

  • SubscribeConfig specifica il nome del servizio a cui iscriversi e altre proprietà di configurazione, ad esempio il filtro di corrispondenza.
  • DiscoverySessionCallback specifica le azioni da eseguire quando si verificano eventi, ad esempio quando viene rilevato un publisher.

Ecco un esempio:

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

Se l'operazione di sottoscrizione va a buon fine, il sistema chiama il callback onSubscribeStarted() nella tua app. Poiché puoi utilizzare l'argomento onSubscribeStarted() nel callback per comunicare con un publisher dopo che la tua app ne ha individuato uno, dovresti salvare questo riferimento.SubscribeDiscoverySession Puoi aggiornare la sessione di sottoscrizione in qualsiasi momento chiamando updateSubscribe() nella sessione di scoperta.

A questo punto, l'abbonamento attende che i publisher corrispondenti entrino nel raggio d'azione del Wi-Fi. In questo caso, il sistema esegue il metodo callback onServiceDiscovered(). Puoi utilizzare l'argomento PeerHandle di questo callback per inviare un messaggio o creare una connessione con il publisher.

Per annullare l'abbonamento a un servizio, chiama DiscoverySession.close(). Le sessioni di scoperta sono associate al relativo elemento principale WifiAwareSession. Se la sessione principale viene chiusa, vengono chiuse anche le sessioni di rilevamento associate. Anche se gli oggetti eliminati vengono chiusi, il sistema non garantisce quando vengono chiuse le sessioni fuori ambito, pertanto ti consigliamo di chiamare esplicitamente i metodi close().

Inviare un messaggio

Per inviare un messaggio a un altro dispositivo, sono necessari i seguenti oggetti:

Per inviare un messaggio, chiama sendMessage(). Potrebbero quindi verificarsi i seguenti callback:

  • Quando il messaggio viene ricevuto correttamente dal peer, il sistema chiama il callbackonMessageSendSucceeded() nell'app di invio.
  • Quando il peer riceve un messaggio, il sistema chiama il callbackonMessageReceived() nell'app di ricezione.

Anche se PeerHandle è necessario per comunicare con i peer, non devi fare affidamento su questo valore come identificatore permanente dei peer. Gli identificatori di livello superiore possono essere utilizzati dall'applicazione, incorporati nel servizio di discovery stesso o nei messaggi successivi. Puoi incorporare un identificatore nel servizio di discovery con il metodo setMatchFilter() o setServiceSpecificInfo() di PublishConfig o SubscribeConfig. Il metodo setMatchFilter() influisce sulla scoperta, mentre il metodo setServiceSpecificInfo() no.

L'inserimento di un identificatore in un messaggio implica la modifica dell'array di byte del messaggio per includere un identificatore (ad esempio, come prima coppia di byte).

Crea una connessione

Wi-Fi Aware supporta la rete client-server tra due dispositivi Wi-Fi Aware.

Per configurare la connessione client-server:

  1. Utilizza la scoperta Wi-Fi Aware per pubblicare un servizio (sul server) e abbonarti a un servizio (sul client).

  2. Una volta che l'abbonato ha trovato il publisher, invia un messaggio dall'abbonato al publisher.

  3. Avvia un ServerSocket sul dispositivo del publisher e imposta o ottieni la relativa porta:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. Utilizza ConnectivityManager per richiedere una rete Wi-Fi Aware sul publisher utilizzando un WifiAwareNetworkSpecifier, specificando la sessione di rilevamento e il PeerHandle dell'abbonato, che hai ottenuto dal messaggio trasmesso dall'abbonato:

    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 volta che il publisher richiede una rete, deve inviare un messaggio all'abbonato.

  6. Una volta che l'abbonato riceve il messaggio dal publisher, richiedi una rete Wi-Fi consapevole sull'abbonato utilizzando lo stesso metodo utilizzato per il publisher. Non specificare una porta quando crei il NetworkSpecifier. I metodi di callback appropriati vengono chiamati quando la connessione di rete è disponibile, modificata o persa.

  7. Una volta chiamato il metodo onAvailable() sull'abbonato, è disponibile un oggetto Network con cui puoi aprire un Socket per comunicare con il ServerSocket sull'editore, ma devi conoscere l'indirizzo IPv6 e la porta del ServerSocket. Puoi recuperarli dall'oggetto NetworkCapabilities fornito nel callback 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. Al termine della connessione di rete, chiama unregisterNetworkCallback().

Ranging peer e rilevamento basato sulla posizione

Un dispositivo con funzionalità di localizzazione RTT Wi-Fi può misurare direttamente la distanza dai peer e utilizzare queste informazioni per limitare il rilevamento dei servizi Wi-Fi Aware.

L'API Wi-Fi RTT consente il rilevamento diretto di un peer Wi-Fi Aware utilizzando il suo indirizzo MAC o il suo PeerHandle.

La rilevamento Wi-Fi Aware può essere limitata in modo da rilevare solo i servizi all'interno di un determinato recinto virtuale. Ad esempio, puoi configurare un recinto virtuale che consenta il rilevamento di un dispositivo che pubblica un servizio "Aware_File_Share_Service_Name" non più vicino di 3 metri (specificato come 3000 mm) e non più lontano di 10 metri (specificato come 10000 mm).

Per attivare il geofencing, sia l'editore sia l'abbonato devono intervenire:

  • L'editore deve attivare il rilevamento di intervalli sul servizio pubblicato utilizzando setRangingEnabled(true).

    Se l'editore non attiva la misurazione della distanza, tutti i vincoli dei recinti virtuali specificati dall'abbonato vengono ignorati e viene eseguita la normale rilevabilità, ignorando la distanza.

  • L'abbonato deve specificare un recinto virtuale utilizzando una combinazione di setMinDistanceMm e setMaxDistanceMm.

    Per entrambi i valori, una distanza non specificata implica che non esiste alcun limite. Se specifichi solo la distanza massima, si presume una distanza minima pari a 0. Se viene specificata solo la distanza minima, non è prevista una distanza massima.

Quando viene rilevato un servizio peer all'interno di un recinto virtuale, viene attivato il callback onServiceDiscoveredWithinRange che fornisce la distanza misurata dal peer. L'API RTT Wi-Fi diretta può quindi essere chiamata in base alle necessità per misurare la distanza in un secondo momento.