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 altri tipi 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:
Rilevamento di 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 iscrive a uno o più servizi ed entra nel raggio d'azione del Wi-Fi del publisher, l'utente iscritto riceve una notifica che lo informa 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 subscriber.
Crea 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à effettive 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, come 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 qualsiasi configurazione del percorso dei dati nell'ambito di una sessione di discovery
di publisher o subscriber. Per determinare se un dispositivo supporta la modalità di comunicazione istantanea, 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 perché il servizio è stato interrotto o è fuori portata. - La configurazione dei percorsi dei 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 responder (server) può essere configurato per accettare qualsiasi peer, ovvero non deve conoscere in anticipo l'indirizzo MAC dell'initiator. Questa modifica velocizza l'attivazione del percorso dati e consente più collegamenti 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 dei dati, sessioni di pubblicazione e sessioni di iscrizione disponibili. In questo modo, l'app può determinare se sono disponibili risorse sufficienti per eseguire la sua funzionalità target.
Configurazione iniziale
Per configurare l'app in modo che utilizzi il rilevamento e il networking Wi-Fi Aware, segui questi passaggi:
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" />Controlla se il dispositivo supporta Wi-Fi Aware con l'API
PackageManager:Kotlin
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)Java
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);Controlla se Wi-Fi Aware è disponibile. Wi-Fi Aware potrebbe essere presente sul dispositivo, ma potrebbe non essere 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 il tethering. Per verificare se Wi-Fi Aware è disponibile, chiama
isAvailable().La disponibilità di Wi-Fi Aware può cambiare in qualsiasi momento. Registra un
BroadcastReceiverper ricevereACTION_WIFI_AWARE_STATE_CHANGED, che viene inviato ogni volta che la disponibilità cambia. Quando la tua app riceve l'intent di trasmissione, ignora tutte le sessioni esistenti (supponi che il servizio Wi-Fi Aware sia stato interrotto), quindi controlla lo stato di disponibilità e modifica il comportamento dell'app 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 saperne di più, consulta Trasmissioni.
Ottieni una sessione
Per iniziare a utilizzare Wi-Fi Aware, la tua app deve ottenere un WifiAwareSession chiamando attach(). Questo metodo esegue queste 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 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 successive della sessione. Un'app può utilizzare la sessione per
pubblicare un servizio o iscriversi 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. Questo potrebbe essere utile in scenari complessi, ma in genere dovrebbe essere evitato.
Pubblica un servizio
Per rendere un servizio rilevabile, chiama il metodo publish(), che accetta i seguenti parametri:
PublishConfigspecifica il nome del servizio e altre proprietà di configurazione, come 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 app in abbonamento corrispondenti entrano nel raggio d'azione 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 di callback onMessageReceived(). Puoi utilizzare l'argomento PeerHandle di questo metodo per inviare un messaggio all'abbonato o creare una connessione con l'abbonato.
Per interrompere la pubblicazione del servizio, chiama DiscoverySession.close(). Le sessioni di discovery sono associate al relativo WifiAwareSession principale. Se la sessione principale viene chiusa, vengono chiuse anche le sessioni di discovery 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().
Abbonati a un servizio
Per abbonarti a un servizio, chiama il metodo subscribe(), che accetta i seguenti parametri:
SubscribeConfigspecifica il nome del servizio a cui iscriversi e altre proprietà di configurazione, come 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 iscrizione 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 iscrizione in qualsiasi momento chiamando updateSubscribe() nella sessione di rilevamento.
A questo punto, la tua sottoscrizione attende che i publisher corrispondenti entrino nel raggio d'azione del Wi-Fi. In questo caso, il sistema esegue il metodo di callback onServiceDiscovered(). Puoi utilizzare l'argomento PeerHandle di questo callback per inviare un messaggio o creare una connessione con l'editore.
Per annullare l'iscrizione a un servizio, chiama DiscoverySession.close(). Le sessioni di discovery sono associate al relativo WifiAwareSession principale. Se la sessione principale viene chiusa, vengono chiuse anche le sessioni di discovery 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().
Invia un messaggio
Per inviare un messaggio a un altro dispositivo, sono necessari i seguenti oggetti:
DiscoverySession. Questo oggetto ti consente di chiamaresendMessage(). La tua app riceve unDiscoverySessionpubblicando un servizio o iscrivendosi a un servizio.L'
PeerHandledell'altro dispositivo, per instradare il messaggio. La tua app riceve ilPeerHandledi 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 riceve il
PeerHandledell'abbonato dal callbackonMessageReceived(). - La tua app si abbona a un servizio. Poi, quando rileva un publisher corrispondente, l'app riceve il
PeerHandledel publisher dal callbackonServiceDiscovered().
- La tua app pubblica un servizio e riceve un messaggio da un sottoscrittore.
La tua app riceve il
Per inviare un messaggio, chiama il numero sendMessage(). Potrebbero quindi verificarsi i seguenti callback:
- Quando il messaggio viene ricevuto correttamente dal peer, il sistema chiama il callback
onMessageSendSucceeded()nell'app di invio. - Quando il peer riceve un messaggio, il sistema chiama il callback
onMessageReceived()nell'app ricevente.
Sebbene l'PeerHandle sia necessario per comunicare con i peer, non devi fare affidamento su di esso come identificatore permanente dei peer. L'app può utilizzare identificatori di livello superiore, 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() non influisce sulla scoperta.
L'incorporamento 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 il networking client-server tra due dispositivi Wi-Fi Aware.
Per configurare la connessione client-server:
Utilizza il rilevamento Wi-Fi Aware per pubblicare un servizio e iscriverti a un servizio.
Dopo che il sottoscrittore ha rilevato il publisher, invia un messaggio dal sottoscrittore al publisher.
Avvia un
ServerSocketsul dispositivo publisher e imposta o ottieni la sua porta:Kotlin
val ss = ServerSocket(0) val port = ss.localPortJava
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();Utilizza
ConnectivityManagerper richiedere una rete Wi-Fi Aware sul publisher utilizzando unWifiAwareNetworkSpecifier, specificando la sessione di rilevamento e l'PeerHandledel 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) { ... } }; connMgr.requestNetwork(myNetworkRequest, callback);Dopo che l'editore ha richiesto una rete, invia un messaggio all'abbonato.
Dopo che il publisher riceve il messaggio dal publisher, richiedi una rete Wi-Fi Aware sul publisher 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.Dopo aver chiamato il metodo
onAvailable()sul sottoscrittore, è disponibile un oggettoNetworkcon cui puoi aprire unSocketper comunicare conServerSocketsul publisher, ma devi conoscere l'indirizzo IPv6 e la porta diServerSocket. Puoi ottenerli dall'oggettoNetworkCapabilitiesfornito nel callbackonCapabilitiesChanged():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);Quando hai terminato con la connessione di rete, chiama
unregisterNetworkCallback().
Rilevamento dei peer e rilevamento basato sulla posizione
Un dispositivo con funzionalità di geolocalizzazione 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 il rilevamento diretto della distanza da un peer Wi-Fi Aware utilizzando il suo indirizzo MAC o il suo PeerHandle.
Il rilevamento Wi-Fi Aware può essere limitato solo ai servizi all'interno di un determinato recinto virtuale. Ad esempio, puoi configurare un geofence 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 geofencing, sia il publisher che l'abbonato devono passare all'azione:
Il publisher deve attivare l'assegnazione di intervalli sul servizio pubblicato utilizzando
setRangingEnabled(true).Se il publisher non attiva il rilevamento della distanza, tutti i vincoli di geofencing 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()esetMaxDistanceMm().Per entrambi i valori, una distanza non specificata non implica alcun limite. Se specifichi solo la distanza massima, la distanza minima è 0. Se specifichi solo la distanza minima, non viene applicata alcuna distanza massima.
Quando viene rilevato un servizio peer all'interno di un geofence, viene attivato il callback onServiceDiscoveredWithinRange, che fornisce la distanza misurata dal peer. L'API Wi-Fi RTT diretta può quindi essere chiamata, se necessario, per misurare la distanza in un secondo momento.