Thursday, March 28, 2013

Reading LTE signal strength (RSSI) in older versions of the Android SDK. Also, Java reflection.

I do a lot of work with an Android app that collects data about the connectivity of the phone. One of the main pieces of information that we need to get from the phone is the RSSI of the current wireless connection.  Unfortunately, obtaining a reading of this information is not as straightforward as you think it might be.

The way I am gathering cell signal info is as follows: I use TelephonyManager and a PhoneStateListener (with an overridden onSignalStrengthsChanged function) to listen for updates in the cell signal RSSI. The onSignalStrengthsChanged function gives you a SignalStrength object. 

Here is where it gets messy. The SignalStrength object contains many functions to find properties of the signal specific to a particular cell technology (CDMA/EDVO/GSM). You have to use TelephonyManager.getNetworkType to figure out which of these functions to use. getNetworkType returns a code corresponding to one of the enumerated network types in TelephonyManager. My app uses API level 9 since we want it to be compatible with a wide range of phones. Unfortunately, this API version came out before LTE was used in Android phones, so they were not included in the network type enumerations. In fact, LTE was not added as a network type until API level 11 (Android 3.0). In API 11, LTE is defined as 13 and eHRPD (a technology used for handoffs from EVDO to LTE) is defined as 14.

My first instinct was that we needed to have two versions of the app; one for phones with LTE and one for phones without LTE. However, when I did some more looking at the Android API, I found that even though the LTE network type was defined in API 11, there were still no signal strength methods. Nothing even reminescent of an LTE signal strength method was added until API 17, which is currently the latest API. This was obviously unacceptable, as very few phones currently use this version of Android. There had to be another way.

More snooping around on the internet told me that phone manufacturers actually added their own methods to the SignalStrength object in order to retrieve LTE RSSI data. This left me with the question of how to call a method that's not in the official Android API. The answer to this is the programming technique of reflection. Java allows us the ability to examine a class dynamically at run time. We can ask if a certain method exists, and if it does we can call it.

The problem of knowing which methods to call is a little harder. Tom at sevenplusandroid.org wrote a blog on this very topic that I found incredibly useful. He wrote an app that examined the methods available on the SignalStrength object using reflection. Then, his app was run on many devices. Sorting through his results, I noted that the three main LTE signal strength methods were called getLteRssi, getLteSignalStrength, and getLteRsrp. RSRP is different than RSSI, and you should keep that in mind when working with the data returned from that function.

Here is a snippet of how I used reflection to call these methods (you should probably handle those exceptions..).


//13 = NETWORK_TYPE_LTE
case 13:
 //the android API doesn't support LTE signal strength until 4.2... so we must use reflection to find these methods
 try {
  Method[] methods = android.telephony.SignalStrength.class.getMethods();
  for(Method mthd:methods){
   if(mthd.getName().equals("getLteRssi") || mthd.getName().equals("getLteSignalStrength") || mthd.getName().equals("getLteRsrp")){
    signalStrength = (Integer) mthd.invoke(signal, new Object[]{});
    break;
   }
  }
 } catch (SecurityException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 } catch (IllegalArgumentException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 } catch (IllegalAccessException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 } catch (InvocationTargetException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 }
 break;

Hope this helps someone! Reflection was completely new to me, so it wasn't an intuitive solution to this problem. I have used this to successfully get LTE RSSI readings using an app compiled with Android API 9 (2.3).