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

16 comments:

  1. I have samsung galaxy s3 I9300 phone, OS version 4.1.2. Using your source codes I tried to use reflection to get the method getLteRsrp() or getLteRsrq() from SignalStrength class. However when I get all the methods like this: Method[] m = android.telephony.SignalStrength.class.getMethods();
    in the list of methods I don’t see getLteRsrp() or getLteRsrq(). Does this mean that the SDK of my android phone doesn't have those functions and I have not way to use them?

    ReplyDelete
    Replies
    1. Hi lu,

      I got the names of my functions from the results posted here:
      http://sevenplusandroid.org/blog/2011/12/signal-strength-detector-the-results/

      This blog was posted before the S3 was released so I'm not sure which one should be used by it. What methods do you see in the getMethods()? You can also use the APK posted in that blog to find out which methods you have.

      You do not need Rsrp or Rsrq. Actually, these aren't the same as RSSI. Do you have getLteRssi or getLteSignalStrength?

      Adam

      Delete
  2. Just wanted to say thanks, I had been trying to figure out why I couldn't just use these functions from the get-go and didn't take the time to see how SevenPlusAndroid had done it.

    Keep it up!

    ReplyDelete
  3. Hi, I copy the result of your method an get me this:
    public int android.telephony.SignalStrength.describeContents(),
    public boolean android.telephony.SignalStrength.equals(java.lang.Object),
    public void android.telephony.SignalStrength.fillInNotifierBundle(android.os.Bundle),
    public int android.telephony.SignalStrength.getCdmaDbm(),
    public int android.telephony.SignalStrength.getCdmaEcio(),
    public int android.telephony.SignalStrength.getEvdoDbm(),
    public int android.telephony.SignalStrength.getEvdoEcio(),
    public int android.telephony.SignalStrength.getEvdoSnr(),
    public int android.telephony.SignalStrength.getGsmBitErrorRate(),
    public int android.telephony.SignalStrength.getGsmSignalStrength(),
    public int android.telephony.SignalStrength.hashCode(),
    public boolean android.telephony.SignalStrength.isGsm(),
    public java.lang.String android.telephony.SignalStrength.toString(),
    public void android.telephony.SignalStrength.writeToParcel(android.os.Parcel,int),
    public static android.telephony.SignalStrength android.telephony.SignalStrength.newFromBundle(android.os.Bundle),
    public final native java.lang.Class java.lang.Object.getClass(),
    public final native void java.lang.Object.notify(),
    public final native void java.lang.Object.notifyAll(),
    public final void java.lang.Object.wait() throws java.lang.InterruptedException,
    public final void java.lang.Object.wait(long) throws java.lang.InterruptedException,
    public final native void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

    how is the LTE Signal?

    ReplyDelete
    Replies
    1. I can't tell much from that exception. What device are you running this on?

      Delete
    2. I put a new question Adam.
      Thankz!

      Delete
  4. I run this on the emulator, it doesn't throws any exception, only get the result of the method you post, because don't insert in this instruction:
    if(mthd.getName().equals("getLteRssi") || mthd.getName().equals("getLteSignalStrength") || mthd.getName().equals("getLteRsrp")){
    signalStrength = (Integer) mthd.invoke(signal, new Object[]{});
    break;
    }

    Thankz again and sorry...for my bad inglish!

    ReplyDelete
    Replies
    1. I don't understand the problem you are facing. If you are using the emulator, then you will not see any of these proprietary extensions to the Android API. These are only present in some devices because the phone manufacturers need them and have created them. The emulator will use vanilla Android, so it won't have any LTE functions.

      Sorry if I do not understand your situation!

      Delete
  5. new one...and last...

    this is rigth: mthd.invoke(signal, new Object[]{});

    because throws me varius exceptions, never resolve for good.

    I try pass signal like SignalStrength, int and string.

    Thanks again master!

    ReplyDelete
  6. Hi Adam,
    I used an alternative method. I use the SignalStrength.toString() method. Then I split it.The last value are for example for the LTE.
    My question would be about the value getLteRssi or getLteSignalStrength you (we) are looking for. When it's not 99 we get something between 0 and (looks like) 31. It appears to be an ASU value like in GSM or CDMA. But how can we convert it into a dbm Rssi value ?

    ReplyDelete
    Replies
    1. I am pretty sure that there is no good answer for that. This depends on the device/radio itself. It is even called "Arbitrary Strength Unit"...

      But Wikipedia says that you should be able to subtract 140 from that number to get the dBm, but I would take that with a grain of salt.

      http://en.wikipedia.org/wiki/Mobile_phone_signal#ASU

      Delete
    2. Hi Adam,
      I'm still on it. The ASU from the getLteSignalStrength or getLteRssi is in a 0.31 -99 range. it doesnt work with the asu-140. I made a lot of tests but I still not find anything that work.

      For information, the signal bars on LTE use the RSCP (getLteRsrp) and should be the best value.

      Thanks for your reply.

      Delete
  7. where can we get the LTE programming SDK?

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. what we pass in place of "signal" .............. please suggest...

    ReplyDelete
    Replies
    1. Sorry I did not make that clear. You need an instance of this object: http://developer.android.com/reference/android/telephony/SignalStrength.html

      Delete