Panoramica di Wi-Fi Aware

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

Il networking Wi-Fi Aware funziona formando cluster con 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 usano 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:

  • Scopri altri dispositivi: l'API ha 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 ed entra nell'intervallo Wi-Fi dell'editore, l'abbonato riceve una notifica che indica che è stato rilevato un editore corrispondente. Dopo aver scoperto un editore, l'abbonato può inviare un breve messaggio o stabilire una connessione di rete con il dispositivo rilevato. I dispositivi possono essere contemporaneamente sia publisher sia abbonati.

  • Crea una connessione di rete: dopo che due dispositivi si sono rilevati l'uno con l'altro, 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 velocità effettiva 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 per la condivisione di foto.

Miglioramenti ad Android 12 (livello API 31)

Android 12 (livello API 31) aggiunge alcuni miglioramenti al 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'arresto o dello spostamento del servizio fuori intervallo.
  • La configurazione dei percorsi dati di Wi-Fi Aware è stata semplificata. Le versioni precedenti utilizzavano la messaggistica L2 per fornire l'indirizzo MAC dell'iniziatore, il che comportava la 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. Ciò accelera la generazione dei percorsi dati e consente più link point-to-point con una sola richiesta di rete.
  • Le app eseguite 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 sottoscrivere sessioni. Ciò può aiutare l'app a determinare se sono disponibili risorse sufficienti per eseguire la funzionalità desiderata.

Configurazione iniziale

Per configurare l'app per l'utilizzo del rilevamento e della rete di Wi-Fi Aware, svolgi i seguenti passaggi:

  1. Richiedi le autorizzazioni seguenti 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 il 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);
    
  3. Verifica se il Wi-Fi Aware è attualmente disponibile. Il Wi-Fi Aware potrebbe essere presente sul dispositivo, ma potrebbe non essere attualmente disponibile perché l'utente ha disattivato il Wi-Fi o la geolocalizzazione. A seconda delle funzionalità hardware e firmware, alcuni dispositivi potrebbero non supportare il Wi-Fi Aware se è in uso il Wi-Fi Direct, SoftAP o il tethering. Per controllare se il servizio Wi-Fi Aware è attualmente disponibile, chiama il numero isAvailable().

    La disponibilità di Wi-Fi Aware può cambiare in qualsiasi momento. La tua 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 (supponendo che il servizio Wi-Fi Aware sia stato interrotto), quindi controllare lo stato attuale di disponibilità e regolarne il comportamento di conseguenza. Ecco alcuni esempi:

    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, vedi Trasmissioni.

Ottenere una sessione

Per iniziare a utilizzare Wi-Fi Aware, la tua app deve ottenere un WifiAwareSession chiamando il attach(). Questo metodo consente di:

  • Attiva l'hardware Wi-Fi Aware.
  • Unisce o forma un cluster Wi-Fi Aware.
  • 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 collega correttamente, il sistema esegue il callback onAttached(). Questo callback fornisce un oggetto WifiAwareSession che l'app deve utilizzare per tutte le ulteriori operazioni della sessione. Un'app può utilizzare la sessione per pubblicare un servizio o sottoscrivere un servizio.

L'app deve chiamare attach() una sola volta. Se l'app chiama attach() più volte, l'app riceve una sessione diversa per ogni chiamata, ognuna con il proprio spazio dei nomi. Questo può essere utile in scenari complessi, ma in genere dovrebbe essere evitato.

Pubblicare un servizio

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

  • PublishConfig specifica il nome del servizio e altre proprietà di configurazione, come il filtro di corrispondenza.
  • DiscoverySessionCallback specifica 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 ha esito positivo, viene chiamato il metodo di callback onPublishStarted().

Dopo la pubblicazione, quando i dispositivi che eseguono app degli abbonati corrispondenti passano nel raggio d'azione del Wi-Fi del dispositivo di pubblicazione, gli abbonati scoprono il servizio. Quando un sottoscrittore rileva un editore, l'editore non riceve una notifica. Se il sottoscrittore invia un messaggio all'editore, tuttavia, l'editore riceve una notifica. In questo caso, viene richiamato il metodo di callback onMessageReceived(). Puoi utilizzare l'argomento PeerHandle di questo metodo per inviare un messaggio al sottoscrittore o creare una connessione al sottoscrittore.

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

Abbonarsi a un servizio

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

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

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 abbonamento ha esito positivo, il sistema chiama il callback onSubscribeStarted() nella tua app. Poiché puoi utilizzare l'argomento SubscribeDiscoverySession nel callback per comunicare con un publisher dopo che l'app ne ha individuato uno, devi salvare questo riferimento. Puoi aggiornare la sessione di abbonamento in qualsiasi momento chiamando updateSubscribe() durante la sessione di rilevamento.

A questo punto, l'abbonamento attende che gli editori corrispondenti entrino nel raggio del Wi-Fi. In questi casi, 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 quel publisher.

Per interrompere l'abbonamento a un servizio, chiama DiscoverySession.close(). Le sessioni di rilevamento sono associate al relativo elemento padre WifiAwareSession. Se la sessione principale è chiusa, vengono chiuse anche le relative sessioni di rilevamento associate. Anche se gli oggetti eliminati vengono chiusi, il sistema non garantisce la chiusura delle sessioni fuori ambito, perciò ti consigliamo di chiamare esplicitamente i metodi close().

Inviare un messaggio

Per inviare un messaggio a un altro dispositivo, devi disporre dei seguenti oggetti:

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 di ricezione.

Sebbene PeerHandle sia necessario per comunicare con i peer, non dovresti farlo come identificatore permanente dei peer. Gli identificatori di livello superiore possono essere utilizzati dall'applicazione incorporata nel servizio di rilevamento stesso o nei messaggi successivi. Puoi incorporare un identificatore nel servizio di rilevamento con il metodo setMatchFilter() o setServiceSpecificInfo() 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 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:

  1. Utilizza il rilevamento Wi-Fi Aware per pubblicare un servizio (sul server) e abbonarsi a un servizio (sul client).

  2. Una volta che il sottoscrittore rileva il publisher, invia un messaggio dal sottoscrittore al publisher.

  3. Avvia un elemento ServerSocket sul dispositivo dell'editore e imposta o recupera la 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 sull'editore utilizzando 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. Quando l'editore richiede una rete, deve inviare un messaggio al sottoscrittore.

  6. Quando l'abbonato riceve il messaggio dall'editore, richiedi una rete Wi-Fi Aware sull'abbonato utilizzando lo stesso metodo utilizzato per l'editore. 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.

  7. Dopo che il metodo onAvailable() viene chiamato sull'abbonato, è disponibile un oggetto Network con il quale puoi aprire una Socket per comunicare con ServerSocket sul publisher, ma devi conoscere l'indirizzo IPv6 e la porta di ServerSocket. Puoi ottenere questi dati 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 il numero unregisterNetworkCallback().

Gestione 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 rilevamento del servizio Wi-Fi Aware.

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

Il rilevamento di Wi-Fi Aware può essere limitato a 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" che non superi i 3 metri di distanza (indicati come 3000 mm) e non oltre 10 metri (indicati come 10.000 mm).

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

  • L'editore deve abilitare il raggio d'azione sul servizio pubblicato utilizzando setRangingEnabled(true).

    Se il publisher non abilita il rilevamento, tutti i vincoli di recinto virtuale specificati dal sottoscrittore vengono ignorati e viene eseguito il rilevamento normale, 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 non implica alcun limite. Specificare solo la distanza massima implica una distanza minima pari a 0. Specificare solo la distanza minima non implica alcun valore massimo.

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