Por que eu devo ler este artigo:

Cell ID e Wi-Fi – Será apresentado através de um exemplo prático como trabalhar com as duas estratégias. Essa discussão é importante uma vez que muitas aplicações atuais fazem uso de informações de localização, mas nem sempre precisamos do nível de detalhes disponibilizado pelo GPS ou temos disponível o serviço, seja por estarmos em áreas internas ou em situações de tempo ruim.

Um número muito grande de aplicativos fazem uso de alguma forma de geolocalização dos usuários. Os dados são geralmente oriundos do GPS ou A-GPS, acrônimos para Global Position System e Assisted Global Position System. O hardware necessário para receber essas informações está presente na quase totalidade dos aparelhos, já que se trata de uma característica muito comum nos smartphones.

Como ponto positivo dessa forma de geolocalização, temos a precisão muito grande. É a melhor comparada a outros métodos. O sistema de posicionamento por satélites é tão eficaz que foi “copiado” pelo sistema europeu Galileo, o russo Glonass e o chinês Compass.

Porém, existem alguns pontos negativos. O GPS consome muita bateria do aparelho e, em algumas situações, o aplicativo não necessita saber exatamente em qual rua a pessoa está, basta saber a cidade e o bairro. O nível de exatidão definido em poucas dezenas de metros do GPS é dispensável nesse caso. Além disso, o GPS tem alguns pontos de sombra, como túneis, mata extremamente densa, ambiente indoor, além de ter sua qualidade afetada quando o tempo está ruim, como em tempestades e chuvas torrenciais.

Não estamos dizendo que o GPS/A-GPS é ruim, apenas que existem alternativas e, para alguns tipos de aplicativos, até melhores que a famosa triangulação por antenas.

Neste artigo vamos tratar de duas formas muito usuais. Uma delas é através do Cell ID (identificação de uma célula gerada por uma estação rádio base) e a outra é via o ponto de acesso no qual o smartphone está conectado para receber sinal de uma rede Wi-Fi. Em ambos os casos será usada a API do Google Maps Geolocation.

O que é Cell ID?

Sempre que seu smartphone está recebendo e fazendo ligações e/ou lhe permite navegar na internet, isso significa que seu aparelho está na área de cobertura de uma estação rádio base da sua operadora. Cada antena tem um raio de alcance. Com o conjunto de diversas antenas espalhadas pelas cidades, temos uma topologia semelhante a células, por isso o nome telefone celular.

Toda célula criada por uma antena possui um identificador único e está fisicamente fixada em um lugar na terra. Essa posição é definida pela sua latitude e longitude.

A aplicação proposta

A aplicação proposta será composta de apenas duas telas. A primeira mostrará as informações da Cell ID na qual o smartphone está ligado e, caso o aparelho também esteja em uma rede Wi-Fi, mostrará o SSID e o Mac Address da mesma. Veja na Figura 1 a tela inicial.

Tela inicial da aplicação
Figura 1. Tela inicial da aplicação

Ao clicar nas opções de Buscar Posição, a latitude e longitude aparecerão em uma TextView localizada embaixo do texto “Resultado”. Na Figura 2 é ilustrado o momento em que o usuário busca a posição e o resultado é mostrado no local citado.

Informação de latitude e longitude sendo exibida ao usuário
Figura 2. Informação de latitude e longitude sendo exibida ao usuário

Depois de buscar a posição, o usuário pode clicar no botão Ver no Mapa. Nesse caso, a aplicação abre a tela mostrada na Figura 3.

Visualização no mapa da informação de geoposicionamento
Figura 3. Visualização no mapa da informação de geoposicionamento

Construção dos layouts

O activity_main representa o layout da tela principal. Seu conteúdo pode ser observado na Listagem 1, que contém, basicamente, a definição dos elementos que serão apresentados na interface do aplicativo.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"     
android:layout_width="match_parent"
android:layout_height="match_parent" 
android:paddingLeft="@dimen/activity_horizontal_margin"       
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin" 
tools:context=".MainActivity">
 <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="About CellID" />
 <TextView android:text="-"
    android:id="@+id/txtMnc"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
 <TextView android:text="-"
    android:id="@+id/txtMcc"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
 <TextView android:text="-"
    android:id="@+id/txtLac"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
 <TextView android:text="-"
    android:id="@+id/txtCid"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
 <Button
    android:onClick="getPositionByCellId"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Buscar Posiçao"/>
 <View
    android:layout_marginTop="20dp"
    android:layout_marginBottom="15dp"
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="#000000"/>
 <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="About Wi-Fi"
    android:id="@+id/textView2" />
 <TextView
    android:text="-"
    android:id="@+id/txtSSID"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
 <TextView
    android:text="-"
    android:id="@+id/txtMacAddress"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
 <Button
    android:onClick="getPositionByWiFi"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Buscar Posiçao"/>
 <View
    android:layout_marginTop="20dp"
    android:layout_marginBottom="15dp"
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="#000000"/>
 <TextView
    android:textStyle="bold"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="Resultado" />
 <TextView
    android:text="-"
    android:id="@+id/txtLatLng"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
 <Button
    android:onClick="seeInMaps"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Ver no mapa"/>
</LinearLayout>
Listagem 1. Arquivo activity_main.xml

A tela do mapa é exatamente igual àquela gerada automaticamente pelo Android Studio quando uma Google Map Activity é criada. Observe agora a Listagem 2, que contém a definição dos itens apresentados na tela do mapa.


<fragment xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"     
  android:layout_width="match_parent"
  android:layout_height="match_parent" android:id="@+id/map"   
  android:name="com.google.android.gms.maps.SupportMapFragment" />
Listagem 2. Definição dos itens de tela na activity_maps.xml

Recuperando informações de Cell ID e Wi-Fi

Antes de recuperar as informações, é necessário configurar algumas permissões de usuário no arquivo AndroidManifest.xml conforme apresentado na Listagem 3. Na lista de permissões podemos encontrar:

  • ACCESS_COARSE_LOCATION: todo aplicativo LBS (Location Based System) precisa informar essa permissão de usuário ou a ACCESS_FINE_LOCATION. A diferença é que no primeiro caso faremos uso de métodos aproximados de localização, geralmente baseados na rede de telefonia celular. No segundo caso (FINE_LOCATION), temos métodos de geolocalização mais precisos, como GPS (Global Postition System) e A_GPS (Assisted-Global Position System);
  • INTERNET: cria a permissão de usuário indicando e possibilitando o uso da internet por essa aplicação. Mesmo que a aplicação não faça ou receba requisições pela rede, o carregamento dos mapas do Google Maps requer essa permissão;
  • ACCESS_NETWORK_STATE: permite que a aplicação acesse informações sobre a rede do dispositivo adjacente;
  • ACCESS_WIFI_STATE: permite que a aplicação acesse informações sobre a rede sem fio do dispositivo adjacente;
  • WRITE_EXTERNAL_STORAGE: permite que a aplicação escreva nas mídias externas, como o cartão SD_CARD;
  • READ_GSERVICES: a Location API faz parte do Google Play Services, sendo assim, a presença da permissão para acesso a esses serviços se faz necessária.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Listagem 3. Definição de permissões do usuário

Já o código apresentado na Listagem 4 está inserido dentro do método onCreate, que faz parte do ciclo de vida da Activity. Para acessar dados de geoposicionamento através de Cell ID, é usada a classe TelephonyManager, que pode ser instanciada através do método getSystemService. Como pode ser visto, esse é um serviço do próprio sistema operacional Android.

A TelephonyManager é um dos serviços do próprio sistema operacional Android disponíveis para o desenvolvedor. Ela fornece informações sobre os serviços de telefonia presentes no dispositivo. Devido ao fato do método de geolocalização que estamos buscando ser relacionado à operadora de telefonia celular, ela se torna de vital importância.

No teste lógico IF, que está sendo feito no código (linha 2), nos certificamos de que estamos trabalhando com um dispositivo que está em uma rede GSM. Dessa forma, as informações desejadas são:

  • LAC: location area code. O LAC compreende um número binário formado de 16 bits que identifica a área de localização de um telefone celular/smartphone dentro de uma rede móvel GSM;
  • CID: Cell ID;
  • MCC: mobile country code. Esse código é único para cada país. Para o Brasil o MCC é 724;
  • MNC: mobile network code. Esse código é específico para cada operadora.

Na linha 3 é chamado o método getCellLocation e passado seu retorno para a variável da classe GsmCellLocation. Com essa variável em mãos, é possível fazer chamadas aos métodos getLac(), getCid() e getNetworkOperator(). Uma observação é importante no último método citado: os três primeiros números referem-se ao MCC e os três últimos ao MNC.

Na linha 16 começa a lógica para buscar os dados da rede Wi-Fi, caso o smartphone esteja conectado a uma. Inicialmente é recuperada a instância de WifiManager, que também é um serviço do sistema operacional. Com essa classe em mãos, o método getConnectionInfo é acionado trazendo como resposta uma instância da classe WifiInfo.

Se o WifiInfo não estiver nulo, significa que o aparelho está conectado em uma rede Wi-Fi e podemos recuperar seu SSID (na linha 21) e seu Mac Address (linha 20).


...
final TelephonyManager t = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
if (t.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
  final GsmCellLocation location = (GsmCellLocation)t.getCellLocation();
  if (location != null) {
      lac = location.getLac();
      cid = location.getCid();

      txtLac.setText("Lac: " + lac);
      txtCid.setText("Cid: " + cid);
      networkOperator = t.getNetworkOperator();
      txtMcc.setText("MCC: " + networkOperator.substring(0,3));//MCCMNC
      txtMnc.setText("MNC: " + networkOperator.substring(3));//MCCMNC
   }
 }

 WifiManager mainWifi = (WifiManager)getSystemService(Context.WIFI_SERVICE);
 WifiInfo currentWifi = mainWifi.getConnectionInfo();
 if(currentWifi != null)
 {
   macAddress = currentWifi.getMacAddress();
   txtSSID.setText(currentWifi.getSSID());
   txtMacAddress.setText(macAddress);
 }
...
Listagem 4. Código para recuperar informações de Cell ID e Wi-Fi

Georreferenciando o aparelho com a Google Maps Geolocation API

Para uma leitura mais detalhada sobre a API, indicamos a leitura do seguinte documento: The Google Maps Geolocation API (ver seção Links).

No uso da API é utilizado o formato JSON tanto no envio da requisição quanto na resposta. Esse fato foi mais um motivo para utilizarmos o framework Retrofit para as requisições HTTP. Sendo assim, foram criadas duas classes Java para espelhar a resposta da API do Google, que se assemelha com a Listagem 5.

O framework Retrofit objetiva facilitar o trabalho com requisições HTTP dentro de uma aplicação Android. A mesma já faz todo o trabalho em uma nova Thread. Através do uso de Annotations e Gson (biblioteca do Google para parser automático de objetos JSON em objetos Java e vice-versa), é possível indicar uma estrutura de APIs que estão em serviços web e passar todo o trabalho restante para o framework Retrofit.


{
  "location": {
    "lat": 51.0,
    "lng": -0.1
  },
  "accuracy": 1200.4
}
Listagem 5. Exemplo de retorno da API

A Listagem 6 mostra as duas classes que espelham o JSON apresentado na Listagem 5.


public class Location {
   double lat;
   double lng;
}
 
public class CellId {
   float accuracy;
   Location location;
}
Listagem 6. Classe Location

O framework Retrofit também exige a criação de uma interface que, através do uso de annotations próprios da biblioteca, indica os endpoints que serão usados pelas requisições HTTP. Um endpoint é o endereço de um serviço disponibilizado na internet. No nosso caso, a URL completa seria https://www.googleapis.com/geolocation/v1/geolocate e o endpoint correspondente seria /geolocation/v1/geolocate.

Veja na Listagem 7 como ficou a nossa interface. Uma interface na linguagem Java apenas define métodos abstratos e públicos, que deverão ser implementados por uma classe que forneça uma implementação válida desses mesmos métodos. A anotação @POST é da própria Retrofit e indica que será usado o método POST do protocolo HTTP.

Também existem as anotações @Body e @Query da Retrofit. A primeira define o corpo da requisição HTTP e a segunda adiciona um parâmetro key na URL de forma dinâmica. Finalmente, o último parâmetro desse método é uma instância da classe Callback. Ela será acionada no momento que a requisição acabar seu trabalho, seja ela de forma errônea ou com sucesso.


public interface CellIdService {
 
   @POST("/geolocation/v1/geolocate")
   void geolocate(
   @Body String body, 
   @Query("key") String key, 
   Callback<CellId> callback);
}
Listagem 7. Interface CellIDService

Na classe MainActivity foram codificados os dois métodos que respondem ao clique nos botões da interface gráfica e indicam que o usuário deseja saber a localização através dos dados do Cell ID ou do Wi-Fi. Inicialmente, vamos analisar o método getPositionByWiFi. Observe a Listagem 8.

Logo na primeira linha é configurado o endpoint para o RestAdapter, da biblioteca do Retrofit. Ele é necessário para, na linha 3, criar a instância da interface que mostramos anteriormente na Listagem 7. Com a variável service, basta chamar o método geolocate e passar os parâmetros configurados na interface e que são exigidos pela API do Google Geolocation.

No primeiro parâmetro temos o JSON que envia o único valor obrigatório, o macAddress. O segundo parâmetro é a api key, que pode ser criada no Google Console API´s (ver seção Links). E, por fim, temos um Callback que nos avisará quando a requisição foi feita e quando já houve uma resposta do servidor, seja ela de sucesso ou erro.

Na linha 7 é codificado o método de callback no caso de sucesso. Nesse caso, basta recuperar o dado de latitude e longitude, que já estão prontos na instância da classe CellId. O Retrofit é o responsável por essa facilidade na manipulação das informações. Essa facilidade existe porque o framework usa a estrutura da classe CellID (mostrada na Listagem 6) e faz um parser automático com o JSON recebido como resposta da requisição. Ou seja, populando todos os atributos da classe e entregando uma instância da classe toda inicializada.


public void getPositionByWiFi(View view){
  RestAdapter retrofit = new RestAdapter.Builder()
    .setEndpoint("https://www.googleapis.com")
    .build();

  CellIdService service = retrofit.create(CellIdService.class);
  service.geolocate("{\n" + "  \"macAddress\": " + macAddress +
    "}", "AIzdfDef-x8ItqL7ab12qSMYChlRARwrXuyCqny0", new Callback<CellId>() {

      @Override
      public void success(CellId cellId, Response response) {
        latitude = cellId.location.lat;
        longitude = cellId.location.lng;
        txtLatLng.setText(latitude + ", " + longitude);
       }

       @Override
       public void failure(RetrofitError error) {
          Log.e("TESTE", "ERRO: " + error.getMessage());
       }
   });
}
Listagem 8. Método getPositionByWiFi

Na Listagem 9 veremos o método getPositionByCellId. O leitor perceberá que a semelhança é grande. Na verdade, a única diferença é nos parâmetros passados para o método geolocate. E, para ser mais exato ainda, só o primeiro parâmetro (o JSON) é alterado. A diferença é que nesse caso enviamos os dados da Cell ID e não do Wi-Fi.


public void getPositionByCellId(View view){
   RestAdapter retrofit = new RestAdapter.Builder()
     .setEndpoint("https://www.googleapis.com")
     .build();
 
   CellIdService service = retrofit.create(CellIdService.class);
   service.geolocate("{\n" +
     "  \"cellTowers\": [\n" +
     "    {\n" +
     "      \"cellId\": "+cid+",\n" +
     "      \"locationAreaCode\": "+lac+",\n" +
     "      \"mobileCountryCode\": "+networkOperator.substring(0, 3)+",\n" +
     "      \"mobileNetworkCode\": "+networkOperator.substring(3)+"\n" +
     "    }\n" +
     "  ]\n" +
     "}", "AIzdfDef-x8ItqL7ab12qSMYChlRARwrXuyCqny0", new Callback<CellId>() {
 
       @Override
       public void success(CellId cellId, Response response) {
         txtLatLng.setText(cellId.location.lat + ", " + cellId.location.lng);
       }
 
       @Override
       public void failure(RetrofitError error) {
         Log.e("TESTE", "ERRO: " + error.getMessage());
       }
   });
}
Listagem 9. Método getPositionByCellId

Mostrando a localização no Mapa

Para finalizar o artigo basta apenas mostrar como foi feito o processo de apresentar a localização do usuário, segundo a Cell ID ou Wi-Fi, no Google Maps. Inicialmente, na classe MainActivity, foi criado o método seeInMapsque responde à ação de clique no botão com o rótulo “Ver no mapa”. Veja no trecho de código da Listagem 10.

 
public void seeInMaps(View v){
   Intent intent = new Intent(this, MapsActivity.class);
   intent.putExtra("latitude", latitude);
   intent.putExtra("longitude", longitude);
   startActivity(intent);
}
Listagem 10. Método seeInMaps

Na classe MapsActivity, que já foi gerada automaticamente pelo Android Studio, foram feitas pequenas alterações para mover o mapa para o ponto da latitude e longitude recebidos e, além disso, colocar um marcador no ponto geolocalizado exato para a percepção do usuário ficar mais fácil.

Veja na Listagem 11 como ficou o método onCreate da classe MapsActivity. Depois de receber os dados de latitude e longitude via extras da Intent, a linha 12 movimenta o visualizador até o ponto indicado e já configura o nível de zoom para 17. Com isso, a aproximação facilita a visualização dos nomes das ruas. E, finalmente, na linha 14 e 15 é criado um MarkerOption que é inserido no mapa e transformado em Marker (muitas vezes também chamado de POI - point of interest).


protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_maps);
   setUpMapIfNeeded();

   Bundle extras = getIntent().getExtras();
   double latitude = extras.getDouble("latitude");
   double longitude = extras.getDouble("longitude");

   LatLng latLng = new LatLng(latitude, longitude);
 
   mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 17));
 
   MarkerOptions mOpt = new MarkerOptions().title("Você está aqui").position(latLng);
   mMap.addMarker(mOpt);
 }
Listagem 11. Método onCreate da classe MapsActivity

Este artigo demonstrou que é fácil inserir alternativas em uma aplicação LBS (Location Based System). O GPS é o meio mais preciso de geolocalizar um ponto na esfera terrestre, porém, nem sempre é o indicado. Sendo assim, usar Wi-Fi e Cell ID é uma boa alternativa para desenvolvedores Android.

Por fim, resta dizer que a Google Maps Geolocation API tornou essa possibilidade ainda mais real. Antes dela já existiam projetos, como o Open CellID, que permitia isso, mas utilizar um framework próprio do Google Play Service permite que o projeto fique mais limpo e mais integrado com as ferramentas Android.

Confira também