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).
La rete 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:
Rilevare altri dispositivi: l'API ha un meccanismo per trovare altri dispositivi nelle vicinanze. La procedura inizia quando un dispositivo pubblica uno o più servizi rilevabili. Poi, quando un dispositivo sottoscrive uno o più servizi ed entra nella portata Wi-Fi del publisher, il sottoscrittore riceve una notifica che indica che è stato rilevato un publisher corrispondente. Dopo che il sottoscrittore ha rilevato un publisher, può inviare un breve messaggio o stabilire una connessione di rete con il dispositivo rilevato. I dispositivi possono essere contemporaneamente publisher e sottoscrittori.
Creare una connessione di rete: dopo che due dispositivi si sono rilevati a vicenda, possono creare una connessione di rete Wi-Fi Aware bidirezionale senza un punto di accesso.
Le connessioni di rete Wi-Fi Aware supportano velocità di trasmissione più elevate su distanze maggiori 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 rilevamento di publisher o sottoscrittori. La modalità di comunicazione istantanea velocizza lo scambio di messaggi, il Service Discovery e la configurazione di qualsiasi percorso dati nell'ambito di una sessione di rilevamento di publisher o sottoscrittori. Per determinare se un dispositivo supporta la modalità di comunicazione istantanea, utilizza il isInstantCommunicationModeSupported() metodo.
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
onServiceLost()callback per ricevere un avviso quando la tua app ha perso un servizio rilevato perché il servizio è stato interrotto o si è spostato fuori dalla portata. - 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 latenza. Sui dispositivi con Android 12 e versioni successive, il risponditore (server) può essere configurato per accettare qualsiasi peer, ovvero non deve conoscere in anticipo l'indirizzo MAC dell'iniziatore. In questo modo, l'attivazione del percorso dati è più rapida e consente di creare più link point-to-point con una sola richiesta di rete.
- Le app in esecuzione su Android 12 o versioni successive possono utilizzare il
WifiAwareManager.getAvailableAwareResources()metodo per ottenere il numero di percorsi dati, sessioni di pubblicazione e sessioni di sottoscrizione attualmente disponibili. In questo modo, l'app può determinare se sono disponibili risorse sufficienti per eseguire la funzionalità desiderata.
Configurazione iniziale
Per configurare l'app in modo che utilizzi il rilevamento e la rete Wi-Fi Aware, procedi nel seguente modo:
Richiedi le seguenti autorizzazioni nel 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" />
Verifica se il dispositivo supporta Wi-Fi Aware con l'API
PackageManager, come mostrato di seguito:Kotlin
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
Java
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
Verifica se Wi-Fi Aware è attualmente disponibile. 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 firmware, alcuni dispositivi potrebbero non supportare 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. La tua app deve registrare un
BroadcastReceiverper ricevereACTION_WIFI_AWARE_STATE_CHANGED, che viene inviato ogni volta che la disponibilità cambia. Quando l'app riceve l'intent di trasmissione, deve eliminare tutte le sessioni esistenti (supponendo che il servizio Wi-Fi Aware sia stato interrotto), quindi controllare lo stato attuale della disponibilità e modificare il suo 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 Trasmissioni.
Ottenere una sessione
Per iniziare a utilizzare Wi-Fi Aware, la tua app deve ottenere un oggetto WifiAwareSession chiamando attach(). Questo metodo esegue le seguenti operazioni:
- Attiva l'hardware Wi-Fi Aware.
- Partecipa a un cluster Wi-Fi Aware o ne crea 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 viene collegata correttamente, il sistema esegue il callback onAttached().
Questo callback fornisce un oggetto WifiAwareSession che la tua app deve utilizzare per tutte le operazioni di sessione successive. Un'app può utilizzare la
sessione per pubblicare un servizio o
sottoscrivere un servizio.
La tua app deve chiamare attach() una sola volta. Se la tua app chiama attach() più volte, riceve una sessione diversa per ogni chiamata, ognuna con il proprio spazio dei nomi. Questa operazione potrebbe essere utile in scenari complessi, ma in genere è consigliabile evitarla.
Pubblicare un servizio
Per rendere rilevabile un servizio, chiama il metodo publish(), che accetta i seguenti parametri:
PublishConfigspecifica il nome del servizio e altre proprietà di configurazione, ad esempio il filtro di corrispondenza.DiscoverySessionCallbackspecifica le azioni da eseguire quando si verificano eventi, ad esempio quando il sottoscrittore 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 di callback onPublishStarted().
Dopo la pubblicazione, quando i dispositivi che eseguono le app di sottoscrizione corrispondenti entrano nella portata Wi-Fi del dispositivo di pubblicazione, i sottoscrittori rilevano il servizio. Quando un sottoscrittore rileva un publisher, il publisher non riceve una notifica; tuttavia, se il sottoscrittore invia un messaggio al publisher, il publisher riceve una notifica. In questo caso, viene chiamato il metodo di callback onMessageReceived(). Puoi utilizzare l'
PeerHandle argomento di questo metodo per
inviare un messaggio al sottoscrittore o
creare una connessione.
Per interrompere la pubblicazione del servizio, chiama DiscoverySession.close().
Le sessioni di rilevamento sono associate al relativo WifiAwareSession padre. Se la sessione padre viene chiusa, vengono chiuse anche le sessioni di rilevamento associate. Sebbene anche gli oggetti eliminati vengano chiusi, il sistema non garantisce quando vengono chiuse le sessioni fuori ambito, pertanto ti consigliamo di chiamare esplicitamente i metodi close().
Sottoscrivere un servizio
Per sottoscrivere un servizio, chiama il metodo subscribe(), che accetta i seguenti parametri:
-
SubscribeConfigspecifica il nome del servizio da sottoscrivere e altre proprietà di configurazione, ad esempio il filtro di corrispondenza. DiscoverySessionCallbackspecifica 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 SubscribeDiscoverySession nel callback per comunicare con un publisher dopo che la tua app ne ha rilevato uno, devi salvare questo riferimento. Puoi aggiornare la sessione di sottoscrizione in qualsiasi momento chiamando updateSubscribe() nella sessione di rilevamento.
A questo punto, la sottoscrizione attende che i publisher corrispondenti entrino nella portata Wi-Fi. Quando ciò accade, il sistema esegue il
onServiceDiscovered()
metodo di callback. Puoi utilizzare l'PeerHandle
argomento di questo callback per inviare un messaggio o
creare una connessione a quel publisher.
Per interrompere la sottoscrizione di un servizio, chiama DiscoverySession.close().
Le sessioni di rilevamento sono associate al relativo WifiAwareSession padre. Se la sessione padre viene chiusa, vengono chiuse anche le sessioni di rilevamento associate. Sebbene anche gli oggetti eliminati vengano 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:
Un oggetto
DiscoverySession. Questo oggetto ti consente di chiamaresendMessage(). La tua app ottiene unDiscoverySessionpubblicando un servizio o sottoscrivendo a un servizio.L'oggetto
PeerHandledell'altro dispositivo, per instradare il messaggio. La tua app ottiene l'oggettoPeerHandledi un altro dispositivo in uno dei due modi seguenti:- La tua app pubblica un servizio e riceve un messaggio da un sottoscrittore.
La tua app ottiene l'oggetto
PeerHandledel sottoscrittore dal callbackonMessageReceived(). - La tua app sottoscrive un servizio. Poi, quando rileva un publisher corrispondente, la tua app ottiene l'oggetto
del publisher dal
onServiceDiscovered()callback.PeerHandle
- La tua app pubblica un servizio e riceve un messaggio da un sottoscrittore.
La tua app ottiene l'oggetto
Per inviare un messaggio, chiama sendMessage(). Potrebbero verificarsi i seguenti callback:
- Quando il messaggio viene ricevuto correttamente dal peer, il sistema chiama il callback
onMessageSendSucceeded()nell'app mittente. - Quando il peer riceve un messaggio, il sistema chiama il callback
onMessageReceived()nell'app ricevente.
Sebbene l'oggetto PeerHandle sia necessario per comunicare con i peer, non devi considerarlo un identificatore permanente dei peer. L'applicazione può utilizzare identificatori di livello superiore, incorporati nel servizio di rilevamento stesso o nei messaggi successivi. Puoi incorporare un identificatore nel servizio di rilevamento con
il
setMatchFilter()
o
setServiceSpecificInfo()
metodo di PublishConfig o
SubscribeConfig. Il metodo setMatchFilter() influisce sul rilevamento, mentre il metodo setServiceSpecificInfo() non influisce sul rilevamento.
L'incorporamento di un identificatore in un messaggio implica la modifica dell'array di byte del messaggio per includere un identificatore (ad esempio, come i primi due byte).
Creare una connessione
Wi-Fi Aware supporta la rete client-server tra due dispositivi Wi-Fi Aware.
Per configurare la connessione client-server:
Utilizza il rilevamento Wi-Fi Aware per pubblicare un servizio (sul server) e sottoscrivere un servizio (sul client).
Una volta che il sottoscrittore ha rilevato il publisher, invia un messaggio dal sottoscrittore al publisher.
Avvia un
ServerSocketsul 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();
Utilizza
ConnectivityManagerper richiedere una rete Wi-Fi Aware sul publisher utilizzando un oggettoWifiAwareNetworkSpecifier, specificando la sessione di rilevamento e l'oggettoPeerHandledel sottoscrittore, che hai ottenuto dal messaggio trasmesso dal sottoscrittore: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);
Una volta che il publisher richiede una rete, deve inviare un messaggio al sottoscrittore.
Una volta che il sottoscrittore riceve il messaggio dal publisher, richiede una rete Wi-Fi Aware sul sottoscrittore utilizzando lo stesso metodo del publisher. Non specificare una porta durante la creazione di
NetworkSpecifier. I metodi di callback appropriati vengono chiamati quando la connessione di rete è disponibile, modificata o persa.Una volta chiamato il metodo
onAvailable()sul sottoscrittore, è disponibile un oggettoNetworkcon cui puoi aprire un oggettoSocketper comunicare con l'oggettoServerSocketsul publisher, ma devi conoscere l'indirizzo IPv6 e la porta dell'oggettoServerSocket. Questi valori vengono ottenuti dall'NetworkCapabilitiesoggetto fornito nelonCapabilitiesChanged()callback: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);
Al termine della connessione di rete, chiama
unregisterNetworkCallback().
Rilevamento dei peer e rilevamento basato sulla posizione
Un dispositivo con funzionalità di localizzazione Wi-Fi RTT può misurare direttamente la distanza dai peer e utilizzare queste informazioni per limitare il Service Discovery Wi-Fi Aware.
L'API Wi-Fi RTT consente di misurare direttamente la distanza da un peer Wi-Fi Aware utilizzando il relativo indirizzo MAC o il suo oggetto PeerHandle.
Il rilevamento Wi-Fi Aware può essere limitato al rilevamento dei servizi all'interno di un particolare 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" a una distanza non
inferiore a 3 metri (specificata come 3000 mm) e non superiore a 10 metri
(specificata come 10.000 mm).
Per attivare il recinto virtuale, sia il publisher che il sottoscrittore devono intraprendere un'azione:
Il publisher deve attivare la misurazione della distanza sul servizio pubblicato utilizzando setRangingEnabled(true).
Se il publisher non attiva la misurazione della distanza, i vincoli del recinto virtuale specificati dal sottoscrittore vengono ignorati e viene eseguito il rilevamento normale, ignorando la distanza.
Il sottoscrittore deve specificare un recinto virtuale utilizzando una combinazione di setMinDistanceMm e setMaxDistanceMm.
Per entrambi i valori, una distanza non specificata implica nessun limite. Se viene specificata solo la distanza massima, la distanza minima è 0. Se viene specificata solo la distanza minima, non esiste 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 Wi-Fi RTT diretta può essere chiamata in base alle esigenze per misurare la distanza in un secondo momento.