Visão geral do Wi-Fi Aware

Os recursos do Wi-Fi Aware permitem que dispositivos com o Android 8.0 (nível 26 da API) e versões mais recentes descubram e se conectem diretamente uns aos outros sem nenhum outro tipo de conectividade entre eles. O Wi-Fi Aware também é conhecido como Neighbor Awareness Networking (NAN).

A rede Wi-Fi Aware funciona formando clusters com dispositivos vizinhos ou criando um novo cluster se o dispositivo for o primeiro em uma área. Esse comportamento de clustering se aplica a todo o dispositivo e é gerenciado pelo serviço do sistema Wi-Fi Aware. Os apps não têm controle sobre o comportamento de clustering. Os apps usam as APIs Wi-Fi Aware para se comunicar com o serviço do sistema Wi-Fi Aware, que gerencia o hardware Wi-Fi Aware no dispositivo.

As APIs do Wi-Fi Aware permitem que os apps realizem as seguintes operações:

  • Descobrir outros dispositivos:a API tem um mecanismo para encontrar outros dispositivos por perto. O processo começa quando um dispositivo publica um ou mais serviços detectáveis. Em seguida, quando um dispositivo se inscreve em um ou mais serviços e entra no alcance do Wi-Fi do editor, o assinante recebe uma notificação de que um editor correspondente foi descoberto. Depois que o assinante descobre um editor, ele pode enviar uma mensagem curta ou estabelecer uma conexão de rede com o dispositivo descoberto. Os dispositivos podem ser editores e inscritos ao mesmo tempo.

  • Criar uma conexão de rede:depois que dois dispositivos se descobrirem, eles poderão criar uma conexão de rede Wi-Fi Aware bidirecional sem um ponto de acesso.

As conexões de rede Wi-Fi Aware oferecem suporte a taxas de capacidade mais altas em distâncias maiores do que as conexões Bluetooth. Esses tipos de conexão são úteis para apps que compartilham grandes quantidades de dados entre usuários, como apps de compartilhamento de fotos.

Melhorias no Android 13 (nível 33 da API)

Em dispositivos com o Android 13 (nível 33 da API) e versões mais recentes que oferecem suporte ao modo de comunicação instantânea, os apps podem usar os métodos PublishConfig.Builder.setInstantCommunicationModeEnabled() e SubscribeConfig.Builder.setInstantCommunicationModeEnabled() para ativar ou desativar o modo de comunicação instantânea para uma sessão de descoberta de editor ou assinante. O modo de comunicação instantânea acelera a troca de mensagens, a descoberta de serviços e qualquer configuração de caminho de dados como parte de uma sessão de descoberta de editor ou assinante. Para determinar se um dispositivo é compatível com o modo de comunicação instantânea, use o método isInstantCommunicationModeSupported().

Melhorias no Android 12 (nível 31 da API)

O Android 12 (nível 31 da API) adiciona algumas melhorias ao Wi-Fi Aware:

  • Em dispositivos com o Android 12 (nível 31 da API) ou versões mais recentes, use o callback onServiceLost() para receber um alerta quando o app perder um serviço descoberto devido à interrupção ou ao deslocamento para fora do alcance.
  • A configuração dos caminhos de dados do Wi-Fi Aware foi simplificada. As versões anteriores usavam mensagens L2 para fornecer o endereço MAC do iniciador, o que introduzia latência. Em dispositivos com o Android 12 e versões mais recentes, o respondente (servidor) pode ser configurado para aceitar qualquer par, ou seja, ele não precisa saber o endereço MAC do iniciador com antecedência. Isso acelera a ativação do caminho de dados e permite vários links ponto a ponto com apenas uma solicitação de rede.
  • Os apps executados no Android 12 ou versões mais recentes podem usar o método WifiAwareManager.getAvailableAwareResources() para conferir o número de caminhos de dados, sessões de publicação e sessões de assinatura disponíveis no momento. Isso pode ajudar o app a determinar se há recursos disponíveis suficientes para executar a funcionalidade desejada.

Configuração inicial

Para configurar o app para usar a descoberta e a rede do Wi-Fi Aware, siga estas etapas:

  1. Solicite as seguintes permissões no manifesto do 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. Verifique se o dispositivo é compatível com o Wi-Fi Aware usando a API PackageManager, conforme mostrado abaixo:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
  3. Verifique se o Wi-Fi Aware está disponível. O Wi-Fi Aware pode existir no dispositivo, mas pode não estar disponível no momento porque o usuário desativou o Wi-Fi ou a localização. Dependendo dos recursos de hardware e firmware, alguns dispositivos podem não oferecer suporte ao Wi-Fi Aware se o Wi-Fi Direct, o SoftAP ou o tethering estiverem em uso. Para verificar se o Wi-Fi Aware está disponível, chame isAvailable().

    A disponibilidade do Wi-Fi Aware pode mudar a qualquer momento. Seu app precisa registrar um BroadcastReceiver para receber ACTION_WIFI_AWARE_STATE_CHANGED, que é enviado sempre que a disponibilidade muda. Quando o app receber a intent de transmissão, ele precisará descartar todas as sessões atuais (suponha que o serviço Wi-Fi Aware foi interrompido), verificar o estado atual de disponibilidade e ajustar o comportamento de acordo. Exemplo:

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

Para mais informações, consulte Visão geral de transmissões.

Conseguir uma sessão

Para começar a usar o Wi-Fi Aware, seu app precisa receber um WifiAwareSession chamando attach(). Esse método faz o seguinte:

  • ativa o hardware do Wi-Fi Aware;
  • une ou forma um cluster do Wi-Fi Aware;
  • Cria uma sessão do Wi-Fi Aware com um namespace exclusivo que funciona como um contêiner para todas as sessões de descoberta criadas nele.

Se o app for anexado, o sistema vai executar o callback onAttached(). Esse callback fornece um objeto WifiAwareSession que o app precisa usar para todas as outras operações de sessão. Um app pode usar a sessão para publicar um serviço ou se inscrever em um serviço.

O app precisa chamar attach() apenas uma vez. Se o app chamar attach() várias vezes, ele vai receber uma sessão diferente para cada chamada, cada uma com o próprio namespace. Isso pode ser útil em cenários complexos, mas geralmente deve ser evitado.

Publicar um serviço

Para tornar um serviço detectável, chame o método publish(), que usa os seguintes parâmetros:

  • PublishConfig especifica o nome do serviço e outras propriedades de configuração, como o filtro de correspondência.
  • DiscoverySessionCallback especifica as ações a serem executadas quando ocorrem eventos, como quando o assinante recebe uma mensagem.

Veja um exemplo:

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 a publicação for bem-sucedida, o método de callback onPublishStarted() será chamado.

Após a publicação, quando os dispositivos que executam apps de assinantes correspondentes entram no alcance do Wi-Fi do dispositivo de publicação, os assinantes descobrem o serviço. Quando um assinante descobre um editor, o editor não recebe uma notificação. No entanto, se o assinante enviar uma mensagem ao editor, ele receberá uma notificação. Quando isso acontece, o método de callback onMessageReceived() é chamado. Use o argumento PeerHandle desse método para enviar uma mensagem de volta ao assinante ou criar uma conexão com ele.

Para interromper a publicação do serviço, chame DiscoverySession.close(). As sessões de descoberta são associadas ao WifiAwareSession mãe. Se a sessão pai for fechada, as sessões de descoberta associadas também serão fechadas. Embora os objetos descartados também sejam fechados, o sistema não garante quando as sessões fora do escopo são fechadas. Por isso, recomendamos que você chame explicitamente os métodos close().

Inscrever-se em um serviço

Para assinar um serviço, chame o método subscribe(), que usa os seguintes parâmetros:

  • SubscribeConfig especifica o nome do serviço a ser assinado e outras propriedades de configuração, como o filtro de correspondência.
  • DiscoverySessionCallback especifica as ações a serem executadas quando eventos ocorrem, como quando um editor é descoberto.

Veja um exemplo:

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 a operação de inscrição for bem-sucedida, o sistema vai chamar o callback onSubscribeStarted() no app. Como é possível usar o argumento SubscribeDiscoverySession no callback para se comunicar com um editor depois que o app descobrir um, salve essa referência. É possível atualizar a sessão de inscrição a qualquer momento chamando updateSubscribe() na sessão de descoberta.

Nesse ponto, sua assinatura aguarda que os editores correspondentes entrem no alcance do Wi-Fi. Quando isso acontece, o sistema executa o método de callback onServiceDiscovered(). Use o argumento PeerHandle desse callback para enviar uma mensagem ou criar uma conexão com esse editor.

Para cancelar a inscrição em um serviço, chame DiscoverySession.close(). As sessões de descoberta são associadas ao WifiAwareSession mãe. Se a sessão pai for fechada, as sessões de descoberta associadas também serão fechadas. Embora os objetos descartados também sejam fechados, o sistema não garante quando as sessões fora do escopo são fechadas. Por isso, recomendamos que você chame explicitamente os métodos close().

Enviar uma mensagem

Para enviar uma mensagem para outro dispositivo, você precisa dos seguintes objetos:

Para enviar uma mensagem, chame sendMessage(). Os callbacks a seguir podem ocorrer:

  • Quando a mensagem é recebida pelo app de destino, o sistema chama o callback onMessageSendSucceeded() no app remetente.
  • Quando o app de destino recebe uma mensagem, o sistema chama o callback onMessageReceived() no app receptor.

Embora o PeerHandle seja necessário para se comunicar com os pares, não o use como um identificador permanente de pares. Os identificadores de nível mais alto podem ser usados pelo aplicativo, incorporados ao próprio serviço de descoberta ou em mensagens subsequentes. É possível incorporar um identificador no serviço de descoberta com o método setMatchFilter() ou setServiceSpecificInfo() de PublishConfig ou SubscribeConfig. O método setMatchFilter() afeta a descoberta, enquanto o método setServiceSpecificInfo() não.

Incorporar um identificador em uma mensagem implica modificar a matriz de bytes da mensagem para incluir um identificador (por exemplo, como os primeiros bytes).

Crie uma conexão

O Wi-Fi Aware é compatível com a rede cliente-servidor entre dois dispositivos Wi-Fi Aware.

Para configurar a conexão cliente-servidor:

  1. Use a descoberta do Wi-Fi Aware para publicar um serviço (no servidor) e assinar um serviço (no cliente).

  2. Depois que o assinante descobrir o editor, envie uma mensagem do assinante para o editor.

  3. Inicie um ServerSocket no dispositivo do editor e defina ou receba a porta dele:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. Use o ConnectivityManager para solicitar uma rede Wi-Fi Aware no editor usando um WifiAwareNetworkSpecifier, especificando a sessão de descoberta e o PeerHandle do assinante, que você recebeu da mensagem transmitida pelo assinante:

    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. Depois que o publisher solicitar uma rede, ele precisará enviar uma mensagem ao assinante.

  6. Depois que o assinante receber a mensagem do editor, solicite uma rede Wi-Fi Aware no assinante usando o mesmo método do editor. Não especifique uma porta ao criar o NetworkSpecifier. Os métodos de callback apropriados são chamados quando a conexão de rede está disponível, é alterada ou é perdida.

  7. Depois que o método onAvailable() é chamado no assinante, um objeto Network fica disponível para que você possa abrir um Socket para se comunicar com o ServerSocket no editor, mas é necessário saber o endereço IPv6 e a porta do ServerSocket. Você os recebe do objeto NetworkCapabilities fornecido no 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. Quando terminar de usar a conexão de rede, chame unregisterNetworkCallback().

Definir o alcance de dispositivos semelhantes e da descoberta com reconhecimento de local

Um dispositivo com recursos de localização de RTT do Wi-Fi pode medir diretamente a distância até os pares e usar essas informações para restringir a descoberta de serviços do Wi-Fi Aware.

A API Wi-Fi RTT permite o alcance direto de um par Wi-Fi Aware usando o endereço MAC ou o PeerHandle.

A descoberta do Wi-Fi Aware pode ser restrita para descobrir apenas serviços dentro de uma fronteira geográfica virtual específica. Por exemplo, é possível configurar uma fronteira geográfica virtual que permita a descoberta de um dispositivo que publica um serviço "Aware_File_Share_Service_Name" a uma distância entre 3 metros (especificados como 3.000 mm) e 10 metros (especificados como 10.000 mm).

Para ativar a fronteira geográfica virtual, algumas ações são necessárias, tanto por parte do editor como do inscrito:

  • O editor precisa ativar a medição de distância no serviço publicado usando setRangingEnabled(true).

    Se o editor não ativar a medição, as restrições de fronteira geográfica virtual especificadas pelo assinante serão ignoradas, e a descoberta normal será realizada, ignorando a distância.

  • O assinante precisa especificar uma fronteira geográfica virtual usando uma combinação de setMinDistanceMm e setMaxDistanceMm.

    Para qualquer um dos valores, uma distância não especificada implica ausência de limite. Especificar apenas a distância máxima implica uma distância mínima de 0. Especificar apenas a distância mínima implica que não há um máximo.

Quando um serviço de peering é descoberto em uma fronteira geográfica virtual, o callback onServiceDiscoveredWithinRange é acionado, o que fornece a distância medida até o peering. A API Wi-Fi RTT direta pode ser chamada conforme necessário para medir a distância em momentos posteriores.