andbook!.pdf - Learning Android Get an anddev.org - Android-Shirt Back to index
anddev.org Header Logo
FAQ Search Top rated articles Browse Feeds anddev.org - Authors Contact Details Register Log in

Poor mans GPS - Cell(Tower)ID / Location Area Code -Lookup

Goto page 1, 2, 3  Next
 
       anddev.org - Android Development Community | Android Tutorials | Index -> Map Applications
Author Message
plusminus
Site Admin


Joined: 14 Nov 2007
Posts: 1880
Location: Germany

PostPosted: Tue Dec 11, 2007 3:46 pm    Post subject: Poor mans GPS - Cell(Tower)ID / Location Area Code -Lookup Reply with quote

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...

Question Problems/Questions: post right below...

Difficulty: 3 of 5 Smile

What it will look like:
Input (loaded automatically) Arrow searching Arrow 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:
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 Smile -->
        </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 Laughing

At first it creates the ProgressDialog, then diggs into a Thread (Remember doing extensive work in the GUI Thread is Evil or Very Mad ) 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 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 Wink
                         } 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.
Idea Note the data returned (also the data we sent) is highly proprietary Exclamation

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 Exclamation
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 Smile.

Idea The CellID and LAC - Values provided by the emulator are not valid.
Arrow 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 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 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();
          }
     }
}


"/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
View user's profile Send private message Send e-mail Visit poster's website
Katharnavas
Senior Developer


Joined: 04 Dec 2007
Posts: 100
Location: India

PostPosted: Wed Dec 12, 2007 5:53 am    Post subject: Reply with quote

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
View user's profile Send private message Yahoo Messenger
venkat
Senior Developer


Joined: 27 Nov 2007
Posts: 152
Location: India

PostPosted: Wed Dec 12, 2007 6:59 am    Post subject: Reply with quote

Hi Plusminus,
once again very useful tutorial from anddev. Very Happy . 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
View user's profile Send private message
gsmith53
Freshman


Joined: 01 Dec 2007
Posts: 9
Location: Dallas, TX

PostPosted: Wed Dec 12, 2007 5:06 pm    Post subject: Why not Location Manager Reply with quote

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
View user's profile Send private message
plusminus
Site Admin


Joined: 14 Nov 2007
Posts: 1880
Location: Germany

PostPosted: Wed Dec 12, 2007 5:23 pm    Post subject: Reply with quote

venkat wrote:
Hi Plusminus,
once again very useful tutorial from anddev. Very Happy . 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 Wink

Idea Btw: The Cel