Visão geral do Wi-Fi Aware

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

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 do Wi-Fi Aware para falar com o serviço do sistema, 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. Então, quando o dispositivo é inscrito em um ou mais serviços e entra no alcance do Wi-Fi do editor, o autor da inscrição recebe uma notificação de que um editor correspondente foi encontrado. Depois que o inscrito descobre um editor, o assinante 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 detectam, 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 mais longas do que as conexões Bluetooth. Esses tipos de conexões são úteis para os apps que compartilham grandes quantidades de dados entre usuários, como apps de compartilhamento de fotos.

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.ACCESS_FINE_LOCATION" />
        
  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 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 poderão ser incompatíveis com o Wi-Fi Aware se o Wi-Fi Direct, SoftAP ou tethering estiver em uso. Para verificar 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 deve registrar um BroadcastReceiver para receber ACTION_WIFI_AWARE_STATE_CHANGED, que é enviado sempre que a disponibilidade muda. Quando o app recebe o intent de transmissão, ele deve descartar todas as sessões existentes (suponha que o serviço do Wi-Fi Aware tenha sido interrompido), verificar o estado atual da disponibilidade e ajustar o comportamento conforme necessário. 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 conseguir uma WifiAwareSession chamando attach(). Esse método:

  • 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 age 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 deve usar para todas as outras operações de sessão. O app pode usar a sessão para publicar ou inscrever-se um serviço.

O app deve 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, em geral, 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 ocorrer corretamente, 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 descobrirão o serviço. Quando um inscrito descobre um editor, o editor não recebe uma notificação. No entanto, se o inscrito envia 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 desse método para enviar uma mensagem de volta para o inscrito ou criar uma conexão com ele.

Para deixar de publicar o 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. 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 as ações a serem executadas quando ocorrem eventos, como quando um editor é detectado.

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 ocorrer corretamente, 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 descobriu 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 momento, a inscrição aguarda editores correspondentes entrarem no alcance do Wi-Fi. Quando isso acontece, o sistema executa o método de callback onServiceDiscovered(). Você pode usar o argumento PeerHandle desse callback para enviar uma mensagem ou criar uma conexão com o editor.

Para deixar de assinar 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. 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.

Enviar uma mensagem

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

Para enviar uma mensagem, chame 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 (no servidor) e inscrever-se em um serviço (no cliente).

  2. Quando o inscrito descobrir o editor, envie uma mensagem do inscrito 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(networkRequest, 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(networkRequest, callback);
        
  5. Solicite uma rede Wi-Fi Aware no inscrito usando o mesmo método do editor. Não especifique uma porta ao criar o NetworkSpecifier. Os métodos de callback adequados são chamados quando a conexão de rede fica disponível, é alterada ou perdida.

  6. 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);
        
  7. Quando você 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 definir o alcance direto até um dispositivo semelhante com Wi-Fi Aware por meio do endereço MAC ou do 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, você pode configurar uma fronteira geográfica virtual que permita a descoberta de um dispositivo responsável pela publicação de um serviço "Aware_File_Share_Service_Name" a no mínimo 3 metros de distância (especificada como 3.000 mm) e no máximo 10 metros (especificada 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 da fronteira geográfica virtual especificadas pelo assinante serão ignoradas, e a descoberta normal será realizada, ignorando a distância.

  • O inscrito 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 semelhante é descoberto dentro de uma fronteira geográfica virtual, o callback onServiceDiscoveredWithinRange é acionado, o que informa a distância medida ao semelhante. A API Wi-Fi RTT direta pode ser chamada conforme necessário para medir a distância posteriormente.