Os recursos do Wi-Fi Aware permitem que dispositivos com o Android 8.0 (nível 26 da API) e versões mais recentes se descubram e se conectem diretamente sem qualquer outro tipo de conectividade. O Wi-Fi Aware também é conhecido como Rede de Reconhecimento de Vizinhos (NAN, na sigla em inglês).
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 do Wi-Fi Aware para se comunicar com o serviço do sistema, 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 um ou mais serviços detectáveis. Em seguida, quando um dispositivo se inscreve em um ou mais serviços e entra no intervalo de Wi-Fi do editor, o assinante recebe uma notificação informando 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.
Crie 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 são compatíveis com taxas de capacidade mais altas em distâncias mais longas do que as conexões Bluetooth. Esses tipos de conexões 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 com 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 um editor ou assinante. O modo de comunicação instantânea acelera a troca de mensagens,
a descoberta de serviços e qualquer caminho de dados configurado como parte de uma sessão de descoberta
de um editor ou assinante. Para determinar se um dispositivo oferece suporte ao modo de 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 callback
onServiceLost()
para receber alertas quando o app perder um serviço descoberto devido à interrupção ou saída do serviço. - 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 introduziu a latência. Em dispositivos com o Android 12 e versões mais recentes, o participante (servidor) pode ser configurado para aceitar qualquer peering, ou seja, não precisa saber o endereço MAC do iniciador antecipadamente. Isso acelera a distribuição do caminho de dados e permite várias vinculações ponto a ponto com apenas uma solicitação de rede.
- 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 disponíveis, sessões de publicação e sessões de assinatura. Isso pode ajudar o app a determinar se há recursos disponíveis suficientes para executar a funcionalidade desejada.
Configuração inicial
Para configurar seu app para usar a descoberta e a rede do Wi-Fi Aware, siga estas etapas:
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" />
Confira 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);
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, SoftAP ou tethering estiver em uso. Para conferir se o Wi-Fi Aware está disponível no momento, chame
isAvailable()
.A disponibilidade do Wi-Fi Aware pode mudar a qualquer momento. Seu app precisa registrar um
BroadcastReceiver
para receberACTION_WIFI_AWARE_STATE_CHANGED
, que é enviado sempre que a disponibilidade muda. Quando o app recebe a intent de transmissão, ele precisa descartar todas as sessões atuais (suponha que o serviço Wi-Fi Aware tenha sido interrompido), conferir o estado atual de disponibilidade e ajustar o comportamento de acordo. Por 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 ter 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 atua como contêiner para todas as sessões de descoberta criadas dentro dela.
Se o app for anexado corretamente, o sistema executará o
callback onAttached()
.
Esse callback fornece um objeto WifiAwareSession
que o app precisa usar para todas as outras operações da sessão. Um app pode usar a
sessão para publicar ou
assinar um serviço.
Seu app precisa chamar
attach()
apenas uma vez. Se
o app chamar attach()
várias vezes, ele 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 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 assinatura correspondentes passam para o
intervalo de Wi-Fi do dispositivo de publicação, os assinantes descobrem o serviço. Quando
um assinante descobre um editor, ele não recebe uma
notificação. No entanto, se o assinante enviar uma mensagem a ele, o editor 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 à WifiAwareSession
mãe. Se a sessão mãe for encerrada, as sessões de descoberta associadas também serão encerradas. Embora os objetos
descartados também sejam fechados, o sistema não garante quando as sessões
fora do escopo serão encerradas. Por isso, recomendamos que você chame explicitamente os métodos
close()
.
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 em que você quer se inscrever e outras propriedades de configuração, como filtro de correspondência. DiscoverySessionCallback
: especifica as ações a serem executadas quando ocorrem 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 assinatura for bem-sucedida, o sistema chamará o callback
onSubscribeStarted()
no app. Como você pode 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 assinatura a qualquer momento
chamando
updateSubscribe()
na sessão de descoberta.
Neste ponto, sua assinatura aguarda editores correspondentes entrarem no
intervalo de 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 o editor.
Para interromper a assinatura de um serviço, chame
DiscoverySession.close()
.
As sessões de descoberta são associadas à WifiAwareSession
mãe. Se a sessão mãe for encerrada, as sessões de descoberta associadas também serão encerradas. Embora os objetos
descartados também sejam fechados, o sistema não garante quando as sessões
fora do escopo serão encerradas. 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:
DiscoverySession
. Esse objeto permite chamarsendMessage()
. Seu app recebe umDiscoverySession
ao publicar ou assinar um serviço.o
PeerHandle
do outro dispositivo, para encaminhar a mensagem. Seu app recebe oPeerHandle
de outro dispositivo de duas maneiras:- Seu app publica um serviço e recebe uma mensagem de um inscrito.
O app recebe o
PeerHandle
do assinante do callbackonMessageReceived()
. - Seu app é inscrito em um serviço. Em seguida, ao descobrir um editor
correspondente, o app recebe o
PeerHandle
do editor do callbackonServiceDiscovered()
.
- Seu app publica um serviço e recebe uma mensagem de um inscrito.
O app recebe o
Para enviar uma mensagem, chame
sendMessage()
. Os
callbacks abaixo podem ocorrer:
- Quando a mensagem é recebida pelo app semelhante, o sistema chama o callback
onMessageSendSucceeded()
no app de envio. - Quando o dispositivo semelhante recebe uma mensagem, o sistema chama o callback
onMessageReceived()
no app recebido.
Embora o PeerHandle
seja necessário para se comunicar com pares, não
use ele como um identificador permanente de pares. Identificadores de nível superior podem ser
usados pelo aplicativo, incorporados no próprio serviço de descoberta ou em
mensagens subsequentes. É possível 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 modificação da matriz de bytes da mensagem para incluir um identificador (por exemplo, como 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:
Use a descoberta do Wi-Fi Aware para publicar um serviço (no servidor) e inscrever-se em um serviço (no cliente).
Depois que o assinante descobrir o editor, envie uma mensagem do assinante para o editor.
Inicie um
ServerSocket
no dispositivo do editor e defina ou receba a porta:Kotlin
val ss = ServerSocket(0) val port = ss.localPort
Java
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();
Use o
ConnectivityManager
para solicitar uma rede Wi-Fi Aware no editor usando umWifiAwareNetworkSpecifier
, especificando a sessão de descoberta e oPeerHandle
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);
Depois que o editor solicita uma rede, ele precisa enviar uma mensagem ao assinante.
Depois que o assinante receber a mensagem do editor, solicite uma rede Wi-Fi Aware nele usando o mesmo método usado no 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.Depois que o método
onAvailable()
é chamado no assinante, um objetoNetwork
fica disponível, com que é possível abrir umSocket
para se comunicar com oServerSocket
no editor, mas é necessário saber o endereço IPv6 e a porta doServerSocket
. Consiga essas informações do objetoNetworkCapabilities
fornecido no 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 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 RTT de Wi-Fi pode medir diretamente a distância entre dispositivos semelhantes 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 até um ponto Wi-Fi Aware usando o endereço MAC ou o PeerHandle.
A descoberta do Wi-Fi Aware pode ser restrita a descobrir apenas serviços dentro de uma
fronteira geográfica virtual específica. Por exemplo, você pode configurar uma fronteira geográfica virtual que permita a descoberta
de um dispositivo que publica um serviço "Aware_File_Share_Service_Name"
a menos de
3 metros (especificados como 3.000 mm) e não mais do que 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 definição de alcance no serviço publicado usando setRangingEnabled(true).
Se o editor não ativar o alcance, todas 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. Se apenas a distância máxima for especificada, a distância mínima será 0. Só especificar a distância mínima não implica um máximo.
Quando um serviço de peering é descoberto em uma fronteira geográfica virtual, o callback onServiceDiscoveredWithinRange é acionado, informando a distância medida ao dispositivo. A API Wi-Fi RTT direta pode ser chamada conforme necessário para medir a distância em momentos posteriores.