Motivations
I have been a happy T-Mobile customer for over 6 years now. Their service can be spotty (partly due to their use of high frequency spectrum, which results in poor building penetration) but they have one killer app:
WiFi calling. Originally this was achieved with UMA, and you had to have a phone with special hardware to take advantage of it. With the rise of Android phones, however, T-Mobile released an app that could be installed on any device that would enable WiFi calling in software. The original WiFi calling app was developed by Kineto and also used UMA. At some point, T-Mobile switched their WiFi calling system over to IMS and began using an app developed by Movial. You can read up on the differences between UMA and IMS
here. The biggest annoyance I have with these software-based approaches is that they no longer achieve seamless handovers between WiFi/cell networks.
In fact,
any handing over from WiFi to cell networks when WiFi calling is enabled (even if it's not in use!) is excruciatingly slow and results in long periods of intermittent connectivity. This is because the WiFi calling app completely disables your cell radio when it is enabled. That might not seem like such a big deal until you're walking through a college campus and your phone is connecting/disconnecting to every single AP you walk past. I wanted to have WiFi calling enabled on my school network, but only in certain buildings (that I spend a lot of time in). Actually, I really only wanted to have WiFi calling enabled at home and at the computer science building.
I consider myself to be an Android novice. Sure, I've developed a few simple Android apps, but they're functionally simple and I'm not sure if I've really
understood everything that I've "written" - there's been a lot of copying/pasting from StackOverflow. I decided that I could use these motivations to learn a little bit more about Android and cure one of my annoyances with my phone in the process. After all, reverse engineering is a great way to learn some of the intimate details of a system.
If you're only here for the end result, you can skip my boring story and check out the links to my code/app in the
conclusion.
My name is Adam and I am a n00b
So where do you start when you're trying to learn more about a mystical feature that T-Mobile has baked in to your ROM? Google it. Unfortunately there isn't really much about the subject since WiFi calling kind of a niche feature. I did find
this one thread on XDA, but I didn't expect it to work, and it didn't. Their approach was to disable WiFi calling using the pm command, which is the Android package manager. Not only did this seem dirty to me, it also requires root. Also, it references a Kineto package, so I knew it was the old version. So... what next?
Logcat. For the uninitiated, logcat is a tool used when developing Android apps that shows all system logs in real-time and lets you filter them down using log levels and query strings. I spent a lot of time sifting through the system logs to see what happened when I enabled/disabled my phone. I kept seeing one line that stuck out to me when I disabled WiFi calling:
I/IPPhoneProxy(1329): receive intent com.movial.ACTION_TERMINATE_STACK
I knew that an intent was a message that could be broadcast in Android and then processed by different apps that are registered to receive that broadcast. I figured that the toggle button in the settings menu must issue this broadcast, which then told the WiFi calling app that it should disable itself. I decided to use ADB (Android Debug Bridge) to poke around in a shell on the device itself and see if I couldn't use am (activity manager) to broadcast this intent and disable WiFi calling via command line. I could see the WiFi calling app reacting to the broadcast and trying to disable itself... but it always immediately re-enabled. I did find one ray of hope; if WiFi calling was disabled, I was able to reliably enable it by starting the service using am startservice. The only issue with this was that the settings menu would get out of sync and still think WiFi calling was disabled.
Assembly...
At this point I was tired of feeling like I was working with my hands tied behind my back, so I decided to try to pull the apps off the device and look at the actual code. Looking through the packages that were installed on the device (pm list packages -f), I found three that matched up with the log messages that I was seeing. I used adb pull to get the apk archives off the phone, and then used baksmali to "disassemble" the app. I found a few programs that claimed to convert smali back to Java, but none of them worked well enough so I ended up just learning to read smali. I've taken a class in assembly, and while not much of it stuck (seriously, who programs in SPARC assembly?) it was enough background for me to pick up what was going on in the smali pseudo-assembly code.
The first thing I tried searching for was the intent that I saw earlier. I found the BroadcastReceiver that handles that intent, calling a function that handles the teardown of WiFi calling. I figured there must be another function for enabling WiFi calling as well, so I spent a lot of time messing around and broadcasting various intents that I found in the code. Unfortunately I did not make much progress with any of this, and I was beginning to think I had hit a dead end.
Sometimes you need to take a step back and underthink things
I realized that I had been going about this the wrong way. There was clearly a simple way of toggling the WiFi calling state since there is a toggle button in the Android settings app. I revisited my trusty logcat dump and saw that the very first event that happened when I toggled WiFi calling was D/IPPhoneProvider(25446): ipphonesettings <- value=1 name=CELL_ONLY
Android settings are typically stored in sqlite3 databases so I searched for databases with that name. Once I found the correct database, I manually changed the entry in the table via the sqlite3 CLI. Nothing happened. I scratched my head for a few minutes before it dawned on me how inefficient it would be if Android was polling every database on the device for changes. Of course! It must only notify the change listeners if the database is modified through the Android API.
SO, I needed an app in order to effectively toggle the WiFi calling state. Which is fine, because I kind of wanted to make a Locale/Tasker plugin for this anyways.
Android Reflection
In an old blog post, I talked a little bit about using reflection to access device manufacturer-specific functions in an API. We want to do something a little different here; we want to be able to access and modify the settings in another app installed on the device developed by someone else. Looking through the smali again, I saw that there was a class that was used to get and set the status of CELL_ONLY
In Android, every application is sandboxed from one another so you have to explicitly request permission to communicate with or modify the settings of another app. Looking through the manifest file of my decompiled app, I saw that it used the permission movial.permission.IPPHONE_SETTINGS
I added this permission to my application's manifest. Now that I had the correct permissions in my app and the name of the method to be called, I was able to use reflection to execute the method and successfully get/set the state of the CELL_ONLY flag. Success! WiFi Calling was now being enabled/disabled properly.
Conclusion
Once this step was complete, the rest of my app simply implemented the Locale plug-in and Widget specifications. I did have to do a little more reverse engineering to get notifications for the state change of CELL_ONLY (to support ACTION_QUERY_CONDITION); I ended up registering a broadcast receiver for an intent that was consistently broadcast after an update to CELL_ONLY, and then used my previous method of querying the state via reflection.
After some beta testing, I found that Samsung devices use Samsung's proprietary IMS system for T-Mobile's WiFi Calling. This required me to go through the reverse engineering process yet again (and purchase a Samsung device...) to ensure my app was compatible with all of T-Mobile's WiFi Calling devices. Unfortunately the method I am currently using to enable/disable WiFi calling state on Samsung devices requires the app to be installed as a system application (which requires root). I still have yet to reverse engineer the Kineto app, which I will hopefully have the time to do in the near future.
I have released my app as
open source on github and it is available to download
on the play store. The app is compatible with any productivity app that makes use of the Locale API (Tasker, Locale, Llama) and also has a widget that can toggle the wifi calling state.