| Author |
Message |
plusminus Site Admin

Joined: 14 Nov 2007 Posts: 1880 Location: Germany
|
Posted: Tue Dec 11, 2007 3:46 pm Post subject: Poor mans GPS - Cell(Tower)ID / Location Area Code -Lookup |
|
|
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...
Problems/Questions: post right below...
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 from other 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:
| XML: | <?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 -->
</Button>
</LinearLayout>
</LinearLayout> |
1.) Lets have a look at the well commented OnCreate(...)-method.
| Java: | // ...
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);
} |
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).
| Java: | /** 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;
}
}
}; |
3.)Now the both OnClickListeners:
The cmdUpdateListener is pretty easy, as it just updates the Edittexts.
| Java: | protected OnClickListener cmdUpdateListener = new OnClickListener() {
// @Override
public void onClick(View arg0) {
// Update the CellID and LAC EditTexts in the GUI
updateTextFields();
} |
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 ) trys to locate ourself by the CellID & LAC and finally dismisses the ProgressDialog.
| Java: | 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 */
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
} catch (Exception e ) {
Log. e("LocateMe", e. toString(), e );
}
myProgressDialog. dismiss();
}
}. start();
}
}; |
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.
| Java: | 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); |
Now we open that Connection and send some data, using a RequestEntity, to it:
| Java: | 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); |
Having executed the Request, we want to grab what was responded by the Google-Service.
Note the data returned (also the data we sent) is highly proprietary 
Read some bytes until we reach the """errorCode""":
| Java: | 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 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:
| Java: | 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); |
If the """errorCode""" was not Zero (0), we display a "Sorry"-Notification:
| Java: | } 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);
} |
And finally ensure, that the connection gets closed
| Java: | }finally{
connection.close();
}
} |
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:
| Java: | /** 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();
} |
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: | cellid=20442
lac=6015 |
The full source:
"/res/layout/main.xml":
| XML: | <?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> |
"/src/your_package_structure/CellIDToLatLong.java":
| Java: | 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 */
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
} 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();
}
}
} |
"/src/your_package_structure/MyCellIDRequestEntity.java":
| Java: | 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";
}
} |
Regards,
plusminus _________________
| Android Development Community / Tutorials
Last edited by plusminus on Tue Dec 18, 2007 9:07 pm; edited 2 times in total |
|
| Back to top |
|
 |
Katharnavas Senior Developer
Joined: 04 Dec 2007 Posts: 100 Location: India
|
Posted: Wed Dec 12, 2007 5:53 am Post subject: |
|
|
Hi,
Its really a very nice application and really worth waiting for it. Thanks for the nice work .. Can u tell me how to get the LAC and CellId for any location. (ex Im from india how to get the LAC and cellid for a location in India).. |
|
| Back to top |
|
 |
venkat Senior Developer
Joined: 27 Nov 2007 Posts: 152 Location: India
|
Posted: Wed Dec 12, 2007 6:59 am Post subject: |
|
|
Hi Plusminus,
once again very useful tutorial from anddev. . i am exited to see your tutorial. I run your application. if i click the Locate Me button, it is shows progress ProgressDialog box. after that, it's shows same screen only. i am from India and my port number is 3128. i have read below article in google group and they are saying google map will not display other than 1080(Port number). is it true??
| Quote: | Hey Adrian thats a bad news ....
but i guess its not all that bad ....there is one last option i can
think of but it'll be bit slow ...
but u'll have internet access on ur emulator...but don't try maps on
its coz it wud give u speeds of around 10kBps
get HTTPTunnel ( http://www.http-tunnel.com/html/ ) ....
install it and set it to use your proxy server
it's establish a tunnel over http(something that'll never be blocked)
it'll run a socks proxy on port 1080 by default ...
so make proxifier use this proxy (localhost:1080)
and make sure HTTPTunnel is excluded from proxifier...or else it'll
get stuck in a loop
best option proxify only ur emulator
this should probably help...
in case it doesn't IM me i'm online till 2 in the night Indian
Standard Time |
regards.
venkat |
|
| Back to top |
|
 |
gsmith53 Freshman
Joined: 01 Dec 2007 Posts: 9 Location: Dallas, TX
|
Posted: Wed Dec 12, 2007 5:06 pm Post subject: Why not Location Manager |
|
|
Ok, this is great, but why wouldn't you create a LocationManager that uses the CellID functionality. Then you program your application to the existing LocationManager interface. That would make your application able to use the real GPS capabilities, if it has them, or the CellID capabilities if it doesn't have the GPS.
It would seem to me that this is where Google is going. It's GoogleMaps for mobile devices seems to already do this.
Don't get me wrong. It's a nice tutorial. |
|
| Back to top |
|
 |
plusminus Site Admin

Joined: 14 Nov 2007 Posts: 1880 Location: Germany
|
Posted: Wed Dec 12, 2007 5:23 pm Post subject: |
|
|
| venkat wrote: | Hi Plusminus,
once again very useful tutorial from anddev. . i am exited to see your tutorial. I run your application. if i click the Locate Me button, it is shows progress ProgressDialog box. after that, it's shows same screen only. i am from India and my port number is 3128. i have read below article in google group and they are saying google map will not display other than 1080(Port number). is it true??
regards.
venkat |
Hello venkat,
unfortunately I don't haven't been digging on that issue, but if they say that and no one complains about it it is probably true
Btw: The Cel | | |