Localização de Wi-Fi: alcance com RTT

Você pode usar o recurso de localização de Wi-Fi fornecido pela API Wi-Fi RTT (Round-Trip-Time) para medir a distância dos pontos de acesso e dos dispositivos Wi-Fi Aware próximos e compatíveis com RTT.

Ao medir a distância de até três ou mais pontos de acesso, será possível usar um algoritmo de multilateração para estimar a posição do dispositivo que melhor se encaixa nessas medidas. Normalmente, o resultado é preciso a uma distância de 1 a 2 metros.

Com esse nível de precisão, é possível criar serviços aprimorados baseados na localização, como a navegação interna, o controle de voz sem ambiguidades (por exemplo, "acenda esta luz") e informações baseadas na localização (como "este produto tem ofertas especiais?”).

O dispositivo que faz a solicitação não precisa se conectar aos pontos de acesso para medir a distância com o Wi-Fi RTT. Para manter a privacidade, somente o dispositivo solicitante pode determinar a distância até o ponto de acesso. Os pontos de acesso não têm essa informação. As operações de Wi-Fi RTT são ilimitadas para aplicativos em primeiro plano, mas não para os em segundo plano.

O Wi-Fi RTT e os recursos de Fine-Time-Measurement (FTM) são especificados pelo padrão IEEE 802.11mc. O Wi-Fi RTT exige o uso da medição de tempo precisa fornecida pelo FTM, já que ele calcula a distância medindo o tempo que um pacote leva para fazer uma viagem de ida e volta entre dois dispositivos e depois multiplica esse tempo pela velocidade da luz.

Diferenças de implementação baseadas na versão Android

O Wi-Fi RTT foi introduzido no Android 9 (API de nível 28). Para usar este protocolo na identificação da posição de dispositivos que executam o Android 9 usando a multilateração, você precisará ter acesso aos dados de localização do ponto de acesso (AP) predeterminados no seu aplicativo. Você pode decidir a melhor forma de armazenar e recuperar esses dados.

Em dispositivos que executam Android 10 (nível de API 29) e versões superiores, os dados da localização do ponto de acesso podem ser representados por objetos ResponderLocation, que incluem latitude, longitude e altitude. Para os pontos de acesso do Wi-Fi RTT compatíveis com informações de configuração da localização/relatório cívico de localização (dados de LCI/LCR), o protocolo retornará um objeto ResponderLocation durante o processo de alcance.

Esse recurso permite que os aplicativos consultem diretamente os pontos de acesso para solicitar a posição deles, em vez de precisar armazenar essas informações antecipadamente. Dessa forma, seu aplicativo poderá encontrar pontos de acesso e determinar as posições relacionadas, mesmo que esses pontos sejam desconhecidos. Por exemplo, quando um usuário entra em um novo prédio.

Requisitos

  • O hardware do dispositivo que faz a solicitação de alcance precisa implementar o padrão de FTM 802.11mc.
  • O dispositivo que faz a solicitação de alcance precisa executar o Android 9 (API de nível 28) ou versão posterior.
  • O dispositivo que faz a solicitação de alcance precisa ter a verificação de Wi-Fi e os serviços de localização ativados (em Configurações > Localização).
  • O aplicativo que faz a solicitação de alcance precisa ter a permissão ACCESS_FINE_LOCATION.
  • O ponto de acesso precisa implementar o padrão de FTM IEEE 802.11mc.

Configuração

Se você quiser configurar o aplicativo para usar o Wi-Fi RTT, siga estas etapas:

1. Solicite permissões

Solicite as seguintes permissões no manifesto do seu aplicativo:

<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" />

A permissão ACCESS_FINE_LOCATION é perigosa. Por isso, você precisa solicitá-la no tempo de execução toda vez que o usuário quiser realizar uma operação de verificação de RTT. Seu aplicativo precisará solicitar a permissão do usuário caso ela ainda não tenha sido concedida. Para saber mais sobre permissões do tempo de execução, consulte Solicitar permissões do aplicativo.

2. Verifique se o dispositivo é compatível com Wi-Fi RTT

Para verificar se o dispositivo é compatível com Wi-Fi RTT, use a API PackageManager:

Kotlin

context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)

Java

context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT);

3. Verifique se o Wi-Fi RTT está disponível

O Wi-Fi RTT pode estar presente no dispositivo, mas não estar disponível no momento, caso o Wi-Fi seja desativado pelo usuário. Dependendo dos recursos de hardware e firmware, alguns dispositivos podem não ser compatíveis com Wi-Fi RTT se o SoftAP ou o tethering estiverem em uso. Para verificar se o Wi-Fi RTT está disponível no momento, chame isAvailable().

A disponibilidade do Wi-Fi RTT pode mudar a qualquer momento. Seu aplicativo deve fazer registro de um BroadcastReceiver para receber ACTION_WIFI_RTT_STATE_CHANGED, que é enviado quando há mudanças na disponibilidade. Ao receber o intent de transmissão, seu aplicativo deverá verificar o estado atual da disponibilidade e ajustar o próprio comportamento de acordo com essa informação.

Por exemplo:

Kotlin

val filter = IntentFilter(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED)
val myReceiver = object: BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (wifiRttManager.isAvailable) {
            …
        } else {
            …
        }
    }
}
context.registerReceiver(myReceiver, filter)

Java

IntentFilter filter =
    new IntentFilter(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
BroadcastReceiver myReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (wifiRttManager.isAvailable()) {
            …
        } else {
            …
        }
    }
};
context.registerReceiver(myReceiver, filter);

Para saber mais, consulte Transmissões.

Criar uma solicitação de alcance

Uma solicitação de alcance (RangingRequest) é criada com a especificação de uma lista de pontos de acesso ou pontos do Wi-Fi Aware. É possível especificar vários pontos de acesso ou pontos do Wi-Fi Aware em uma única solicitação. As distâncias para todos os dispositivos são medidas e retornadas.

Por exemplo, uma solicitação pode usar o método addAccessPoint() para especificar um ponto de acesso e fazer a medição da distância correspondente.

Kotlin

val req: RangingRequest = RangingRequest.Builder().run {
    addAccessPoint(ap1ScanResult)
    addAccessPoint(ap2ScanResult)
    build()
}

Java

RangingRequest.Builder builder = new RangingRequest.Builder();
builder.addAccessPoint(ap1ScanResult);
builder.addAccessPoint(ap2ScanResult);

RangingRequest req = builder.build();

É possível identificar um ponto de acesso pelo objeto ScanResult, que pode ser obtido com a chamada de WifiManager.getScanResults(). Você pode usar addAccessPoints(List) para adicionar vários pontos de acesso em um lote.

Da mesma forma, uma solicitação de alcance pode adicionar um ponto do Wi-Fi Aware usando o endereço MAC ou o PeerHandle, com os métodos addWifiAwarePeer(MacAddress peer) e addWifiAwarePeer(PeerHandle peer), respectivamente. Para saber mais sobre como descobrir pontos do Wi-Fi Aware, consulte a documentação relacionada.

Solicitar o alcance

Um aplicativo envia uma solicitação de alcance usando o método WifiRttManager.startRanging() e fornecendo as seguintes informações: um RangingRequest para especificar a operação, um Executor para especificar o contexto do callback e um RangingResultCallback para receber os resultados.

Por exemplo:

Kotlin

val mgr = context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as WifiRttManager
val request: RangingRequest = myRequest
mgr.startRanging(request, executor, object : RangingResultCallback() {

    override fun onRangingResults(results: List<RangingResult>) { … }

    override fun onRangingFailure(code: Int) { … }
})

Java

WifiRttManager mgr =
      (WifiRttManager) Context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE);

RangingRequest request ...;
mgr.startRanging(request, executor, new RangingResultCallback() {

  @Override
  public void onRangingFailure(int code) { … }

  @Override
  public void onRangingResults(List<RangingResult> results) { … }
});

A operação de alcance é executada de forma assíncrona, e os resultados são retornados em um dos callbacks de RangingResultCallback:

  • Se a operação falhar, o callback onRangingFailure será acionado com o código de status descrito em RangingResultCallback. Esse erro ocorrerá caso o serviço não possa executar uma operação de alcance no momento. Por exemplo, o Wi-Fi está desativado, o aplicativo ultrapassou o limite de solicitações de alcance ou devido a um problema de permissão.
  • Quando a operação de alcance for concluída, o callback onRangingResults será acionado com uma lista de resultados por solicitação. A ordem dos resultados não corresponderá necessariamente à das solicitações. A operação de alcance talvez seja concluída, mas cada resultado ainda poderá indicar uma falha nessa medição específica.

Interpretar os resultados de alcance

Cada um dos resultados retornados pelo callback onRangingResults é especificado pelo objeto RangingResult. Em cada solicitação, faça o seguinte:

1. Identifique a solicitação

Identifique a solicitação com base nas informações fornecidas ao criar o RangingRequest: na maioria das vezes, é um endereço MAC fornecido no ScanResult que identifica um ponto de acesso. O endereço MAC pode ser obtido a partir do resultado de alcance usando o método getMacAddress().

A lista de resultados de alcance pode estar em ordem diferente dos pontos (pontos de acesso) especificados na solicitação. Por isso, use o endereço MAC para identificar o ponto, não a ordem dos resultados.

2. Determine se cada medição foi concluída

Para determinar se uma medição foi concluída, use o método getStatus(). Qualquer valor diferente de STATUS_SUCCESS indicará que houve uma falha. Uma falha significa que todos os outros campos desse resultado (exceto a identificação da solicitação acima) são inválidos, e o método get* gerará um erro com uma exceção IllegalStateException .

3. Veja os resultados para cada medição concluída

Para cada medição concluída, você poderá recuperar valores de resultado com os respectivos métodos get: