This Tutorial was inspired by the awesome, but almost uncommented davanum Apache Blog.
Poor mans GPS - Cell(Tower)ID / Location Area Code -Lookup
What is this: This tutorial shows how to approximate your current location in the world using the CellID ( CellTowerID ) and LAC ( Location Area Code ), by requesting data, from a 'secret' Google-API.
What you will learn:
- Reading out the CellID ( CellTowerID ) and LAC ( Location Area Code ) of your current Phone-Cell.
- Setting up a HTTP-Connection
- Posting Requests to that HTTP-Connection
- Retrieve Latitude/Longitude from a 'secret' Google-API usign CellID and LAC.
- Displaying a ProgressBar
- Many more...
Difficulty: 3 of 5

What it will look like:
Input (loaded automatically)
searching
found




Description:
0.) As always the first thoughts go to the GUI-Design using XML. As you all know a bit about XML-Layouting :arrow: from :arrow: other :arrow: Tutorials, we will handle this part a bit shorter here.
What we want to achieve is a layout like the first of the three pictures above. We achieve it with the following xml-code:
Using xml Syntax Highlighting
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <!-- Input-Stuff -->
- <TextView ... >
- <EditText ... />
- <TextView ... />
- <EditText ... />
- <!-- The Buttons next to each other -->
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- >
- <Button ... />
- <Button ...>
- <requestFocus/> <!-- Didn't know that this tag can be placed here <img src="http://www.anddev.org/images/smilies/smile.png" alt=":)" title="Smile" /> -->
- </Button>
- </LinearLayout>
- </LinearLayout>
Parsed in 0.003 seconds, using GeSHi 1.0.8.4
1.) Lets have a look at the well commented OnCreate(...)-method.
Using java Syntax Highlighting
- // ...
- protected PhoneStateIntentReceiver myPhoneStateReceiver;
- protected int myCellID = -1;
- protected int myLAC = -1;
- // ...
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- this.setContentView(R.layout.main);
- /* Set up the PhoneStateIntentReceiver
- * and register it to notify us on changes
- * to the Phone's ServiceState. */
- this.myPhoneStateReceiver = new PhoneStateIntentReceiver(this,
- this.myServiceStateHandler);
- this.myPhoneStateReceiver.notifyServiceState(MY_NOTIFICATION_ID);
- this.myPhoneStateReceiver.registerIntent();
- // Save references to the EditTexts
- this.myEditLac = (EditText) findViewById(R.id.edit_lac);
- this.myEditCid = (EditText) findViewById(R.id.edit_cellid);
- // Set the OnClickListeners
- this.myCmdUpdate = (Button) findViewById(R.id.cmd_update);
- this.myCmdUpdate.setOnClickListener(this.cmdUpdateListener);
- this.myCmdLocateMe = (Button) findViewById(R.id.cmd_locateme);
- this.myCmdLocateMe.setOnClickListener(this.cmdLocateListener);
- }
Parsed in 0.032 seconds, using GeSHi 1.0.8.4
2.) Lets digg a bit deeper to the myServiceStateHandler, we used above. When the ServiceState of our Phone changed, it will read out the CellID and the LAC and set it to the EditTexts, at least once.(Constant updates, when the constant CONTINUOUS_UPDATING == true).
Using java Syntax Highlighting
- /** Notification Handler for changed PhoneState 'events' */
- Handler myServiceStateHandler = new Handler() {
- private boolean firstUpdate = true;
- // @Override
- public void handleMessage(Message msg) {
- /* Recognize our message based on the what-ID*/
- switch (msg.what) {
- case MY_NOTIFICATION_ID:
- ServiceState sState = myPhoneStateReceiver
- .getServiceState();
- /* Save the CellID and the LAC */
- CellIDToLatLong.this.myCellID = sState.getCid();
- CellIDToLatLong.this.myLAC = sState.getLac();
- /* At least one refresh, then continuous refreshes, if set */
- if (firstUpdate || CONTINUOUS_UPDATING == true) {
- firstUpdate = false;
- // Update the CellID and LAC EditTexts in the GUI
- updateTextFields();
- }
- return;
- }
- }
- };
Parsed in 0.033 seconds, using GeSHi 1.0.8.4
3.)Now the both OnClickListeners:
The cmdUpdateListener is pretty easy, as it just updates the Edittexts.
Using java Syntax Highlighting
- protected OnClickListener cmdUpdateListener = new OnClickListener() {
- // @Override
- public void onClick(View arg0) {
- // Update the CellID and LAC EditTexts in the GUI
- updateTextFields();
- }
Parsed in 0.034 seconds, using GeSHi 1.0.8.4
The cmdLocateListener as its name already says is a bit more complex.
To pretend that this task it an extremely complex operation we display a ProgressDialog that is shown for 5 seconds, before any work is done 

At first it creates the ProgressDialog, then diggs into a Thread (Remember doing extensive work in the GUI Thread is
Using java Syntax Highlighting
- protected OnClickListener cmdLocateListener = new OnClickListener() {
- // @Override
- public void onClick(View arg0) {
- /* Show a progress-bar */
- myProgressDialog = ProgressDialog.show(CellIDToLatLong.this,
- "Please wait...", "Doing Extreme Calculations...", true);
- new Thread() {
- public void run() {
- try {
- /* Pretend this is really complex <img src="http://www.anddev.org/images/smilies/wink.png" alt=";)" title="Wink" /> */
- sleep(5000);
- /* Parse the values from the EditTexts
- * and try to locate ourselves */
- int cellid = Integer.parseInt(myEditCid.getText().toString());
- int lac = Integer.parseInt(myEditLac.getText().toString());
- tryToLoate(cellid, lac);
- } catch (NumberFormatException nfe) {
- // Crap was typed <img src="http://www.anddev.org/images/smilies/wink.png" alt=";)" title="Wink" />
- } catch (Exception e) {
- Log.e("LocateMe", e.toString(), e);
- }
- myProgressDialog.dismiss();
- }
- }.start();
- }
- };
Parsed in 0.038 seconds, using GeSHi 1.0.8.4
4.)The most interesting part in this Tutorial is definitely the part, where the actual locating takes place. This happens in the tryToLocate(...)-method:
The first thing to be done is to set up a HTTP-Connection to communicate with a 'hidden' Google-API.
Using java Syntax Highlighting
- public void tryToLoate(int aCellID, int aLAC) throws Exception {
- // Create a connection to some 'hidden' Google-API
- String baseURL = "http://www.google.com/glm/mmap";
- // Setup the connection
- HttpURL httpURL = new HttpURL(baseURL);
- HostConfiguration host = new HostConfiguration();
- host.setHost(httpURL.getHost(), httpURL.getPort());
- HttpConnection connection = connectionManager.getConnection(host);
Parsed in 0.037 seconds, using GeSHi 1.0.8.4
Now we open that Connection and send some data, using a RequestEntity, to it:
Using java Syntax Highlighting
- try{
- // Open it
- connection.open();
- // Post (send) data to the connection
- PostMethod postMethod = new PostMethod(baseURL);
- /* MyCellIDRequestEntity will provides
- * the request-content to send */
- postMethod.setRequestEntity(new MyCellIDRequestEntity(aCellID, aLAC));
- postMethod.execute(new HttpState(), connection);
Parsed in 0.037 seconds, using GeSHi 1.0.8.4
Having executed the Request, we want to grab what was responded by the Google-Service.
:idea: Note the data returned (also the data we sent) is highly proprietary 
Read some bytes until we reach the """errorCode""":
Using java Syntax Highlighting
- InputStream response = postMethod.getResponseBodyAsStream();
- DataInputStream dis = new DataInputStream(response);
- // Read some prior data
- dis.readShort();
- dis.readByte();
- // Read the error-code
- int errorCode = dis.readInt();
Parsed in 0.036 seconds, using GeSHi 1.0.8.4
If the """errorCode""" was Zero (0), there will be the lat/long waiting for us. Having read them, we start the System-Map-Activity with the lat/long we just read out:
Using java Syntax Highlighting
- if (errorCode == 0) {
- double lat = (double) dis.readInt() / 1000000D;
- double lng = (double) dis.readInt() / 1000000D;
- // Read the rest of the data
- dis.readInt();
- dis.readInt();
- dis.readUTF();
- /* Create a geo-ContentURI containing the
- * lat/long-values and start the Map-Application */
- ContentURI geoURI = ContentURI.create("geo:" + lat + "," + lng);
- Intent mapViewIntent = new Intent(
- android.content.Intent.VIEW_ACTION, geoURI);
- startActivity(mapViewIntent);
Parsed in 0.037 seconds, using GeSHi 1.0.8.4
If the """errorCode""" was not Zero (0), we display a "Sorry"-Notification:
Using java Syntax Highlighting
- } else {
- /* If the return code was not
- * valid or indicated an error,
- * we display a Sorry-Notification */
- NotificationManager nm = (NotificationManager)
- getSystemService(NOTIFICATION_SERVICE);
- nm.notifyWithText(100, "Could not find lat/long information",
- NotificationManager.LENGTH_SHORT, null);
- }
Parsed in 0.036 seconds, using GeSHi 1.0.8.4
And finally ensure, that the connection gets closed
Using java Syntax Highlighting
- }finally{
- connection.close();
- }
- }
Parsed in 0.036 seconds, using GeSHi 1.0.8.4
5.) The MyCellIDRequestEntity.java is the last thing to be looked at. Its writeRequest(...)-method writes some data to an OutputStream. In our case this is very specific and highly proprietary data, as we are going to pretend to be a Sony Ericsson-K750. Have a look:
Using java Syntax Highlighting
- /** Pretend to be a French Sony Ericsson-K750 that
- * wants to receive its lat/long-values =)
- * The data written is highly proprietary !!! */
- public void writeRequest(OutputStream outputStream) throws IOException {
- DataOutputStream os = new DataOutputStream(outputStream);
- os.writeShort(21);
- os.writeLong(0);
- os.writeUTF("fr");
- os.writeUTF("Sony_Ericsson-K750");
- os.writeUTF("1.3.1");
- os.writeUTF("Web");
- os.writeByte(27);
- os.writeInt(0); os.writeInt(0); os.writeInt(3);
- os.writeUTF("");
- os.writeInt(myCellID); // CELL-ID
- os.writeInt(myLAC); // LAC
- os.writeInt(0); os.writeInt(0);
- os.writeInt(0); os.writeInt(0);
- os.flush();
- }
Parsed in 0.038 seconds, using GeSHi 1.0.8.4
So, thats it
.
The CellID and LAC - Values provided by the emulator are not valid.
Try the following to see an actual result (type them to the EditTexts):
.- Code: Select all
cellid=20442
lac=6015
The full source:
"/res/layout/main.xml":
Using xml Syntax Highlighting
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <!-- Input-Stuff -->
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="CELL-ID"
- />
- <EditText id="@+id/edit_cellid"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- />
- <!-- The Buttons next to each other -->
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="LAC"
- />
- <EditText id="@+id/edit_lac"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- />
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- >
- <Button id="@+id/cmd_update"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:text="Update"
- />
- <Button id="@+id/cmd_locateme"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:text="Locate Me!">
- <requestFocus/>
- </Button>
- </LinearLayout>
- </LinearLayout>
Parsed in 0.006 seconds, using GeSHi 1.0.8.4
"/src/your_package_structure/CellIDToLatLong.java":
Using java Syntax Highlighting
- package org.anddev.android.cellidtolatlong;
- import java.io.DataInputStream;
- import java.io.InputStream;
- import org.apache.commons.httpclient.HostConfiguration;
- import org.apache.commons.httpclient.HttpConnection;
- import org.apache.commons.httpclient.HttpConnectionManager;
- import org.apache.commons.httpclient.HttpState;
- import org.apache.commons.httpclient.HttpURL;
- import org.apache.commons.httpclient.SimpleHttpConnectionManager;
- import org.apache.commons.httpclient.methods.PostMethod;
- import android.app.Activity;
- import android.app.NotificationManager;
- import android.app.ProgressDialog;
- import android.content.Intent;
- import android.net.ContentURI;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.telephony.PhoneStateIntentReceiver;
- import android.telephony.ServiceState;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.EditText;
- public class CellIDToLatLong extends Activity {
- // ===========================================================
- // Static Final Fields
- // ===========================================================
- protected static final boolean CONTINUOUS_UPDATING = false;
- protected static final int MY_NOTIFICATION_ID = 0x1337;
- protected static final int PROGRESSBAR_ID = 0x1234;
- // ===========================================================
- // Static Fields
- // ===========================================================
- protected static HttpConnectionManager connectionManager = new SimpleHttpConnectionManager();
- // ===========================================================
- // Fields
- // ===========================================================
- protected PhoneStateIntentReceiver myPhoneStateReceiver;
- /* GUI-Stuff */
- protected EditText myEditLac;
- protected EditText myEditCid;
- protected Button myCmdLocateMe;
- protected Button myCmdUpdate;
- protected int myCellID = -1;
- protected int myLAC = -1;
- protected ProgressDialog myProgressDialog;
- // ===========================================================
- // Fields with anonymous SubClasses
- // ===========================================================
- protected OnClickListener cmdLocateListener = new OnClickListener() {
- // @Override
- public void onClick(View arg0) {
- /* Show a progress-bar */
- myProgressDialog = ProgressDialog.show(CellIDToLatLong.this,
- "Please wait...", "Doing Extreme Calculations...", true);
- new Thread() {
- public void run() {
- try {
- /* Pretend this is really complex <img src="http://www.anddev.org/images/smilies/wink.png" alt=";)" title="Wink" /> */
- sleep(5000);
- /* Parse the values from the EditTexts
- * and try to locate ourselves */
- int cellid = Integer.parseInt(myEditCid.getText().toString());
- int lac = Integer.parseInt(myEditLac.getText().toString());
- tryToLoate(cellid, lac);
- } catch (NumberFormatException nfe) {
- // Crap was typed <img src="http://www.anddev.org/images/smilies/wink.png" alt=";)" title="Wink" />
- } catch (Exception e) {
- Log.e("LocateMe", e.toString(), e);
- }
- myProgressDialog.dismiss();
- }
- }.start();
- }
- };
- protected OnClickListener cmdUpdateListener = new OnClickListener() {
- // @Override
- public void onClick(View arg0) {
- updateTextFields();
- }
- };
- /** Notification Handler for changed PhoneState 'events' */
- Handler myServiceStateHandler = new Handler() {
- private boolean firstUpdate = true;
- // @Override
- public void handleMessage(Message msg) {
- /* Recognize our message based on the what-ID*/
- switch (msg.what) {
- case MY_NOTIFICATION_ID:
- ServiceState sState = myPhoneStateReceiver
- .getServiceState();
- /* Save the CellID and the LAC */
- CellIDToLatLong.this.myCellID = sState.getCid();
- CellIDToLatLong.this.myLAC = sState.getLac();
- /* At least one refresh, then continuous refreshes, if set */
- if (firstUpdate || CONTINUOUS_UPDATING == true) {
- firstUpdate = false;
- updateTextFields();
- }
- return;
- }
- }
- };
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- this.setContentView(R.layout.main);
- /* Set up the PhoneStateIntentReceiver
- * and register it to notify us on changes
- * to the Phone's ServiceState. */
- this.myPhoneStateReceiver = new PhoneStateIntentReceiver(this,
- this.myServiceStateHandler);
- this.myPhoneStateReceiver.notifyServiceState(MY_NOTIFICATION_ID);
- this.myPhoneStateReceiver.registerIntent();
- // Save references to the EditTexts
- this.myEditLac = (EditText) findViewById(R.id.edit_lac);
- this.myEditCid = (EditText) findViewById(R.id.edit_cellid);
- // Set the OnClickListeners
- this.myCmdUpdate = (Button) findViewById(R.id.cmd_update);
- this.myCmdUpdate.setOnClickListener(this.cmdUpdateListener);
- this.myCmdLocateMe = (Button) findViewById(R.id.cmd_locateme);
- this.myCmdLocateMe.setOnClickListener(this.cmdLocateListener);
- }
- /**
- * Update the UI from the values stored in the notification handler
- */
- private void updateTextFields() {
- this.myEditCid.setText("" + this.myCellID);
- this.myEditLac.setText("" + this.myLAC);
- }
- public void tryToLoate(int aCellID, int aLAC) throws Exception {
- // Create a connection to some 'hidden' Google-API
- String baseURL = "http://www.google.com/glm/mmap";
- // Setup the connection
- HttpURL httpURL = new HttpURL(baseURL);
- HostConfiguration host = new HostConfiguration();
- host.setHost(httpURL.getHost(), httpURL.getPort());
- HttpConnection connection = connectionManager.getConnection(host);
- try{
- // Open it
- connection.open();
- // Post (send) data to the connection
- PostMethod postMethod = new PostMethod(baseURL);
- /* MyCellIDRequestEntity will provides
- * the request-content to send */
- postMethod.setRequestEntity(new MyCellIDRequestEntity(aCellID, aLAC));
- postMethod.execute(new HttpState(), connection);
- InputStream response = postMethod.getResponseBodyAsStream();
- DataInputStream dis = new DataInputStream(response);
- // Read some prior data
- dis.readShort();
- dis.readByte();
- // Read the error-code
- int errorCode = dis.readInt();
- if (errorCode == 0) {
- double lat = (double) dis.readInt() / 1000000D;
- double lng = (double) dis.readInt() / 1000000D;
- // Read the rest of the data
- dis.readInt();
- dis.readInt();
- dis.readUTF();
- /* Create a geo-ContentURI containing the
- * lat/long-values and start the Map-Application */
- ContentURI geoURI = ContentURI.create("geo:" + lat + "," + lng);
- Intent mapViewIntent = new Intent(
- android.content.Intent.VIEW_ACTION, geoURI);
- startActivity(mapViewIntent);
- } else {
- /* If the return code was not
- * valid or indicated an error,
- * we display a Sorry-Notification */
- NotificationManager nm = (NotificationManager)
- getSystemService(NOTIFICATION_SERVICE);
- nm.notifyWithText(100, "Could not find lat/long information",
- NotificationManager.LENGTH_SHORT, null);
- }
- }finally{
- connection.close();
- }
- }
- }
Parsed in 0.064 seconds, using GeSHi 1.0.8.4
"/src/your_package_structure/MyCellIDRequestEntity.java":
Using java Syntax Highlighting
- package org.anddev.android.cellidtolatlong;
- import java.io.DataOutputStream;
- import java.io.IOException;
- import java.io.OutputStream;
- import org.apache.commons.httpclient.methods.RequestEntity;
- class MyCellIDRequestEntity implements RequestEntity {
- protected int myCellID;
- protected int myLAC;
- public MyCellIDRequestEntity(int aCellID, int aLAC) {
- this.myCellID = aCellID;
- this.myLAC = aLAC;
- }
- public boolean isRepeatable() {
- return true;
- }
- /** Pretend to be a French Sony_Ericsson-K750 that
- * wants to receive its lat/long-values =)
- * The data written is highly proprietary !!! */
- public void writeRequest(OutputStream outputStream) throws IOException {
- DataOutputStream os = new DataOutputStream(outputStream);
- os.writeShort(21);
- os.writeLong(0);
- os.writeUTF("fr");
- os.writeUTF("Sony_Ericsson-K750");
- os.writeUTF("1.3.1");
- os.writeUTF("Web");
- os.writeByte(27);
- os.writeInt(0); os.writeInt(0); os.writeInt(3);
- os.writeUTF("");
- os.writeInt(myCellID); // CELL-ID
- os.writeInt(myLAC); // LAC
- os.writeInt(0); os.writeInt(0);
- os.writeInt(0); os.writeInt(0);
- os.flush();
- }
- public long getContentLength() {
- return -1;
- }
- public String getContentType() {
- return "application/binary";
- }
- }
Parsed in 0.041 seconds, using GeSHi 1.0.8.4
Regards,
plusminus




. i am exited to see your tutorial. I run your application. if i click the 



