Sunday, December 7, 2008

Accessing hidden System Service APIs in Android

Android's SDK allows developers to do a lot with the platform, but there are some interesting capabilities of the system that aren't accessible through the public API. It's still possible to access these capabilities with a little bit of work and the Android source code, available from http://source.android.com. The source code I wrote is here.

The capability I wanted to expose is collecting GPS satellite information - data on the satellites (SVs - space vehicles) themselves. There are hidden methods in the LocationManager service API to set up callbacks to collect this data, but they aren't accessible through the public API. I considered the various ways I could get this data and the easiest one I came up with is to send messages directly to the LocationManager service itself using the IPC interface.

LocationManager and LocationManagerService

The LocationManager API in the android.location namespace is the public interface to the LocationManagerService, in com.android.server. The service runs in a separate process, and the API communicates with it using Android's IPC mechanism (Binder). When an Android application wants to communicate with the LocationManagerService, it uses this API call to get a reference to the API object:

LocationManager mLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
Normally, an application will then use the LocationManager API to register for location events (via requestLocationUpdates()) and then use the data to do really fun and interesting things, like this.
If you dig into the LocationManager source code, you'll see that there's a method called registerGpsStatusListener(). This sets up a listener that's able to get updates on GPS satellite information – PRNs, elevations, azimuths, etc, of each of the satellites in view of the GPS. This is what I want. Unfortunately, this method, as well as the argument type (GpsStatusListenerTransport), aren't visible in the android.jar that comes with the SDK. It's still possible to get at this information by communicating directly with the LocationManagerService itself.

The APIs available in the Android SDK communicate with system services using IPC. Specifically, they use interfaces defined with AIDL to communicate with the service. The AIDL specifications for the service IPC API's isn't available in the SDK, but you can get them from the Android source code. The AIDL is used to generate a client stub that Java classes can use to send messages to and receive messages from the service.

Accessing LocationManagerService directly

The class in LocationManager that communicates with the LocationManagerService is GpsStatusListenerTransport. This is an extension of the IGpsStatusListener.Stub class, which was generated from an AIDL specification – IGpsStatusListener.aidl. It's possible to copy IGpsStatusListener.aidl from the Android source code and add to your project to generate the IGpsListenerStatus.Stub class. This project can then use this to communicate directly with the service that implements that interface.
IGpsStatusListener isn't the only AIDL interface we'll need – ILocationManager.aidl, ILocationListener.aidl, and Address.aidl are also needed to do what we want. Once these classes are available in our project (I put them in the android.location namespace to avoid changing the code, but it doesn't really matter which namespace they belong to). Once these interfaces are available to our project, it's really easy to get what we want.

While it is possible to write a totally new client for the LocationManagerService using the AIDL interfaces, it's actually easier for us to re-use an existing client. Setting up a new connection involves a bunch of lines of code and my carpal tunnels hate when I write too much code. The easy way is to use the Java reflection API to get the handle to the system service:
Class c = Class.forName(mLocationManager.getClass().getName());
Field f = c.getDeclaredField("mService");
f.setAccessible(true);
ILocationManager mILM = (ILocationManager)f.get(mLocationManager);
Accessing private fields like this in Java is generally frowned upon in production code, but we're hackers and we want the data and we can do whatever we want to get it and the establishment can't stop us.

So now that we have access to the ILocationManager API, we can see that there is an addGpsStatusListener() method we can call to add a listener that retrieves the GPS satellite status updates. This takes an IGpsStatusListener object, which we can create by instantiating a class that extends IGpsStatusListener.stub. Passing that object to addGpsStatusListener() will result in us getting onSvStatusChanged() callbacks with the satellite status. Nice.

Before we get any of this spiffy satellite info, the GPS must be enabled. In my sample app, I used the regular LocationManager API to do this by adding a LocationListener. The result of all of my efforts is this useless and boring application that shows some information about some satellites:


Oops...

If you start digging into the service code, you might notice that no permissions are required to add a listener for GPS status updates. This is a minor and low-risk information leak vulnerability. Applications shouldn't have access to GPS location data unless they have the correct permission. If an application creates a service that listens for GPS status updates (but not GPS location updates, which require the ACCESS_FINE_LOCATION permission), it will get these updates when any other application turns on the GPS. It's possible, with some fancy math, to determine the location of the phone with this data. You'll need to know the exact time, the relative locations of the GPS satellites to the user, and the absolute locations of the GPS satellites in space at that time. The time and relative locations can come from the phone, and the precise locations of the satellites can be found at http://www.navcen.uscg.gov/GPS/almanacs.htm. I'll leave doing the fancy math as an exercise to the reader.
Update: Proposed patch to add permission check, http://review.source.android.com/5124.

11 comments:

Yev said...

Nice code!

Which SDK are you using? The problem is that the onFirstFix(int), onGpsStarted(), etc are in IGpsStatusListener.Stub.Proxy not in IGpsStatusListener.Stub

and the Proxy class is private so I can't override these methods.

Any ideas?

Thx!

Jon said...

I was using the public SDK, 1.0r2. I'll upload the full source for the eclipse project later today.

I was able to override those methods by extending IGPSStatusListener.stub:

private class GpsListener extends IGpsStatusListener.Stub {
...
}

I didn't really dig too much into the internals of the .Stub and .Stub.Proxy classes, but what I have seems to work.

Yev said...

Thanks for the prompt reply. I can't wait to get this working.

The problem is:

private class GpsListener extends IGpsStatusListener.Stub implements ILocationListener
{

@Override
public void onFirstFix(int ttff) throws RemoteException {

if I remove "@Override" then there're no errors, but then it's not overriding and is the wrong approach right?

Otherwise it just won't work, maybe the stubs are getting corrected incorrectly from aidl's for me...

Would love to see your Eclipse project to import, thanks!

Jon said...

Source is up, see http://code.google.com/p/codetastrophe/source/browse/#svn/trunk/projects/satinfo

There are instructions somewhere on there for pulling that down with svn. If you run into any issues shoot me an email, jondroid at nologs dot com.

Ludwig said...

I put together a little apk that visualizes the GPS status info (signal strength, constellation of satellites). Find an image and links to source and apk at
http://androidgps.blogspot.com/2009/02/visualizing-gps-satellite-status.html.
I have only had the chance to test this on an Openmoko Freerunner running Android, so I would love to see if this runs the same with a G1.

Anonymous said...

I could add android.location and put aidl files there. it generated .java files too. however when I try to import those files, it shows error. also I get an error saying
"unable to open file for read" for all the AIDL files. how do I fix this problem?

alexdonnini said...

Hello,

I read your post and code with pleasure and great interest because I have been trying to do some of the same things.

Specifically, I have been trying to write an application which, among other functions produces a map of cell towers available for connection at any point in time.

In order to do this, I need to be able to access functions such as getNeighboringCellInfo() method in TelephonyManager (and other hidden ones) not accessible via the SDK.

To make a long sotry short, for testing purposes (not to be included in a production release) I added to my application package all the necessary classes, including TelephonyManager, PhoneStateListener, and NeighboringCellInfo, and all the required interfaces including ITelephony.

So far so good. getNeighboringCellInfo() is accessible. However, the application crashes with the following message:

04-24 17:06:08.039: WARN/Parcel(95): **** enforceInterface() expected 'com.android.internal.telephony.ITelephony' but read 'com.android.locationtest.ITelephony'
.
.
.
04-24 17:06:08.229: ERROR/AndroidRuntime(4529): Caused by: java.lang.SecurityException: Binder invocation to an incorrect interface

I suspect/hope that I should be able to resolve the problem fairly easily but I can't quite see how.

Given your own experience in accessing "hidden" resources in Android, I would appreciate your thoughts and suggestions.

Thanks.

Alex Donnini

Sorin said...

Hello Alex,
Have you managed to get acces to the com.android.internal.telephony methods?
I have a project to do witch needs toggle the phone on/off, supply PIN etc.
Can you post your eclpise project?

Regards,
Sorin

draffodx said...

Hi Jon,

Where does the mService property come from?

Field f = c.getDeclaredField("mService");

I am trying to access the iTelephony.aidl but I get a No Such Field Exception

Sumit said...

Hi,
Is there any way to block the calls. One way as you suggested is writing the aidl. But with that for a fraction of second call screen used to come and than the call ends. I don't want even that to happen. Please suggest some way to achieve this. There used to be an API called setRadio but it has been deleted I guess. I am using android 2.1.

Android app developer said...

I absolutely accept you will do abundant more good in the approaching I acknowledge aggregate you accept added to my ability base.Admiring the time and accomplishment you put into your blog and abundant advice you offer!