Visão geral do Wi-Fi Aware

Os recursos do Wi-Fi Aware permitem que dispositivos com o Android 8.0 (API de nível 26) e versões mais recentes descubram e se conectem diretamente uns aos outros sem qualquer outro tipo de conectividade entre eles. O Wi-Fi Aware também é conhecido como Reconhecimento do vizinho Rede (NAN, na sigla em inglês).

O 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 cluster 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 cluster. Os apps usam as APIs Wi-Fi Aware para se comunicar com o serviço do sistema Wi-Fi Aware, que gerencia o hardware do 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 outro. ou mais serviços detectáveis. Então, quando um dispositivo assinar um ou mais e entra no alcance do Wi-Fi do editor, o assinante recebe notificação de que um editor correspondente foi descoberto. Após o o assinante descobre um editor, ele pode enviar um Short 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 descobrirem cada um eles podem criar uma conexão de rede Wi-Fi Aware bidirecional sem um ponto de acesso.

As conexões de rede Wi-Fi Aware são compatíveis com taxas de transferência mais altas em distâncias maiores do que as conexões Bluetooth. Esses tipos de conexão são úteis para apps que compartilham quantidades de dados entre usuários, como apps de compartilhamento de fotos.

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

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

Melhorias do 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, é possível usar o onServiceLost() que seja alertado quando o app perder um serviço descoberto por causa da interrupção ou transferência de um serviço 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 causava latência. Em dispositivos com o Android 12 e versões mais recentes, o recurso de resposta (servidor) pode ser configurado para aceitar qualquer peer. Ou seja, não é necessário saber o endereço MAC do iniciador antecipadamente. Isso acelera a inicialização do caminho de dados e permite várias conexões de ponto a ponto com apenas uma solicitação de rede.
  • Os apps executados no Android 12 ou mais recente podem usar o método WifiAwareManager.getAvailableAwareResources() para receber o número de caminhos de dados, sessões de publicação e de inscrição disponíveis no momento. Isso pode ajudar o app a determinar se há recursos suficientes disponíveis para executar a funcionalidade desejada.

Configuração inicial

Para configurar seu app para usar a descoberta e as redes 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 com 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 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 ser compatíveis com o Wi-Fi Aware se o Wi-Fi Direct, SoftAP ou tethering estiverem em uso. Para verificar se o Wi-Fi Aware está disponível no momento, ligue isAvailable():

    A disponibilidade do Wi-Fi Aware pode mudar a qualquer momento. Seu app precisa registre um BroadcastReceiver para receber ACTION_WIFI_AWARE_STATE_CHANGED, que é enviado sempre que a disponibilidade muda. Quando o app receber o transmissão, ele deve descartar todas as sessões existentes (suponha que o serviço Wi-Fi Aware foi interrompido), depois verifique estado atual de disponibilidade e ajustar seu 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 conseguir uma 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 atua como um contêiner para todas as sessões de descoberta criadas nele.

Se o app for anexado corretamente, o sistema executará o callback onAttached(). Esse callback disponibiliza 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.

Seu app deve chamar attach() apenas uma vez. Se seu app chama attach() várias vezes, o aplicativo recebe uma sessão diferente para cada chamada, cada uma com um namespace próprio. Isso pode ser útil em cenários complexos, devem ser evitados.

Publicar um serviço

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

  • PublishConfig especifica o nome do serviço e outras propriedades de configuração, como filtro de correspondência.
  • DiscoverySessionCallback especifica ações a serem executadas quando ocorrerem 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.

Depois da publicação, quando os dispositivos com apps inscritos correspondentes forem movidos para o alcance de Wi-Fi do dispositivo editor, os inscritos vão descobrir o serviço. Quando um assinante descobre um editor, este não recebe uma notificação Porém, se o assinante enviar uma mensagem ao editor, o editor recebe uma notificação. Quando isso acontece, o método de callback onMessageReceived() é chamado. Você pode usar o argumento PeerHandle deste método para envie 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 estão associadas ao familiar responsável WifiAwareSession: Se a sessão mãe for encerrada, as sessões de descoberta associadas também serão. Embora os objetos descartados também sejam encerrados, o sistema não garante o momento em que as sessões fora do escopo são encerradas. Portanto, recomendamos que você chame os métodos close() explicitamente.

Inscrever-se em um serviço

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

  • SubscribeConfig especifica o nome do serviço no qual você quer se inscrever e outras propriedades de configuração, como filtro de correspondência.
  • DiscoverySessionCallback especifica ações a serem executadas quando ocorrerem eventos, 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 chamará o callback onSubscribeStarted() no seu app. Como você pode usar o argumento SubscribeDiscoverySession no callback para se comunicar com um editor depois que seu app descobrir um, é recomendado salvar essa referência. Você pode atualizar a sessão de inscrição a qualquer momento chamando updateSubscribe() na sessão de descoberta.

Neste ponto, sua assinatura aguarda a chegada dos editores correspondentes Alcance do Wi-Fi. Quando isso acontece, o sistema executa onServiceDiscovered() método de callback. Você pode usar o PeerHandle deste retorno de chamada para enviar uma mensagem ou criar uma conexão com esse editor.

Para cancelar a assinatura de um serviço, chame DiscoverySession.close(). As sessões de descoberta estão associadas ao familiar responsável WifiAwareSession: Se a sessão principal for fechada, as sessões de descoberta associadas também serão encerradas. Descartado objetos também são fechados, o sistema não garante quando estão fora do escopo sessões são encerradas, por isso recomendamos que você chame explicitamente o método close() métodos.

Enviar uma mensagem

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

Para enviar uma mensagem, ligue sendMessage(): Os seguintes callbacks podem ocorrer:

  • Quando a mensagem é recebida pelo dispositivo semelhante, o sistema chama o callback onMessageSendSucceeded() no app remetente.
  • Quando o dispositivo semelhante recebe uma mensagem, o sistema chama o callback onMessageReceived() no app destinatário.

Embora o PeerHandle seja necessário para se comunicar com dispositivos semelhantes, não os use como um identificador permanente. Identificadores de nível superior podem ser usados pelo aplicativo, incorporados no próprio serviço de descoberta ou em mensagens subsequentes. Você pode incorporar um identificador no serviço de descoberta com os métodos setMatchFilter() ou setServiceSpecificInfo() de PublishConfig ou SubscribeConfig. O método setMatchFilter() afeta a descoberta, enquanto o método setServiceSpecificInfo() não a afeta.

A incorporação de um identificador em uma mensagem implica a mudança da matriz de bytes da mensagem para incluir um identificador (por exemplo, os primeiros bytes).

Criar 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 (na servidor) e assinar um serviço (no cliente).

  2. Quando o assinante descobre o editor, enviar uma mensagem do assinante para o editor.

  3. Inicie um ServerSocket no dispositivo editor e defina ou consiga a porta relacionada:

    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 inscrito, que você recebeu na mensagem transmitida pelo inscrito:

    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 editor solicitar uma rede, ele vai enviar uma mensagem para o assinante.

  6. Quando o inscrito receber a mensagem do editor, solicite uma rede Wi-Fi Aware no inscrito usando o mesmo método do editor. O que fazer não especificar uma porta ao criar NetworkSpecifier Os métodos de callback adequados são chamados quando a conexão de rede está disponível, alterada ou perdida.

  7. Quando o método onAvailable() é chamado no assinante, um objeto Network fica disponível, com o qual você pode abrir um Socket para se comunicar com o ServerSocket no editor. No entanto, é necessário saber o endereço IPv6 e a porta do ServerSocket. Para conseguir essas informações 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 você terminar com a conexão de rede, chame unregisterNetworkCallback()

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

Um dispositivo com localização por Wi-Fi RTT podem medir a distância diretamente para outros pares e usar essa informação para restringem a descoberta de serviços do Wi-Fi Aware.

A API Wi-Fi RTT permite alcance direto até um ponto Wi-Fi Aware usando Endereço MAC ou o PeerHandle.

A descoberta do Wi-Fi Aware pode ser limitada a descobrir apenas os serviços dentro de uma fronteira geográfica virtual específica. Por exemplo, configure uma fronteira geográfica virtual para de um dispositivo que publica um serviço "Aware_File_Share_Service_Name" que não está mais próximo de 3 metros (especificados como 3.000 mm) e não mais do que 10 metros (especificado 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 distribuição no serviço publicado usando setRangingEnabled(true).

    Se o editor não ativar o alcance, as restrições da fronteira geográfica virtual especificados pelo assinante são ignorados e uma descoberta normal é executada, 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. Se apenas a distância máxima for especificada, a distância mínima será 0. Se apenas a distância mínima for especificada, não haverá um valor máximo.

Quando um serviço de peering é descoberto dentro de uma fronteira geográfica virtual, o onServiceDiscoveredWithinRange (em inglês) é acionado, o que informa a distância medida ao outro ponto. A API Wi-Fi RTT direta pode ser chamada conforme necessário para medir a distância posteriormente.