The problem
Obviously you want your QA guys to test the real app as much as possible without having to mock up anything. Sometimes this is not possible and you need to dummy up some data or mock something to imitate the real world environment. For my specific use case I had an app that had to verify the phone number of the device. To do this we had to send a verification code via sms to the device and I have a custom SMS BroadcastReceiver that would intercept it and send the message back to the server to verify the device. Our QA device doesn't have a sim card so the device used for testing couldn't receive incoming texts. There was also one other use case that I'll mention later.After searching the internet long and hard I couldn't find anywhere that showed how to send a fake sms to a real device. There are several options for sending to an emulator but not for a real device. There are several apps that do fake SMS messaging but they somehow bypass the normal SMS functioning. I tried several but my SMS handling code never got invoked. So I had to figure out another way to get the device registered so that QA could test the app.
As a side note, our app did not allow the user to input the code. The app intercepted the SMS message and did it for them. If the app allowed the user to input the code, this would not have been an issue. Even so, this is still a useful method so don't get caught up in my use case details.
What is a BroadcastReceiver?
A BroadcastReceiver is a component that allows you to receive system events and events you want your app to fire. There's a good blog post about that here if you want to read more.
- Parse out the registration code from the SmsMessage
- Send the code to our server for verification
- Save a boolean in user preferences indicating verification was successful
- Broadcast to the RegistrationActivity that registration was successful so the screen would refresh and allow the user to have access to the app
The solution
So I thought I could tackle this problem another way. I would create a "back door". I know, this sounds horrible from a security perspective but for this case it seemed pretty harmless and the user of the back door would have to have the verification code and a lot of knowledge about our app to use it. Also you can protect it with a hard coded username/password that will get obfuscated with progaurd if you wish.The Android Debug Bridge(adb) tool allows you to broadcast intents and send key-value pairs along with it. So all you have to do is register a QABroadcastReceiver to listen for custom intents. This BroadcastReceiver can pick out any data that you send to it via adb. Bam! We have a solution. I created one that allowed special commands to be sent that it would recognize. So if I broadcast a com.mycompany.android.SMS_CODE intent, and put a code=123 string in the intent, then the BroadcastReceiver would do everything the regular SMSBroadcastReceiver did without having to send the SMS.
Let's take a look at some code so you can see this in action!
Create the QABroadcastReceiver class
This class is simply going to show a Toast message that we send to it to prove that our method works.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.BroadcastReceiver; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.os.Bundle; | |
import android.widget.Toast; | |
/** | |
* Allows us to use adb to send messages to our app. For example, for a device connected | |
* to a computer with adb installed you could run the following: | |
* | |
* adb shell am broadcast -a com.mycompany.android.intent.QA_MESSAGE --es message Hello | |
*/ | |
public class QABroadcastReceiver extends BroadcastReceiver | |
{ | |
@Override | |
public void onReceive(Context context, Intent intent) | |
{ | |
Bundle bundle = intent.getExtras(); | |
if(bundle != null) | |
{ | |
String message = bundle.getString("message"); | |
Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); | |
} | |
} | |
} |
Registering in the AndroidManifest.xml file
You can name the intent filter whatever you would like. In fact you can create multiple intent filters and use them like command names in your BroadcastReceiver. Or you can just use one intent filter and embed the commands as key-value pairs. It's really up to you.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<receiver android:name=".QABroadcastReceiver"> | |
<intent-filter> | |
<action android:name="com.mycompany.android.intent.QA_MESSAGE" /> | |
</intent-filter> | |
</receiver> |
That's all of the android code that we'll have to write. The rest is getting adb to broadcast a message that our BroadcastReceiver will...you guessed it, RECEIVE!
Setting up adb to broadcast messages to your app
If you have the Android sdk installed then the adb.exe is located in the sdk/platform-tools folder. You can download adb by itself but I haven't tested that download so try at your own risk. On windows I would recommend creating an environment variable for %ANDROID_HOME% pointing to your Android sdk location and editing the path variable to include %ANDROID_HOME%/platform-tools. That way you should be able to pull up a command line and type "adb" without the quotes and see that it starts the adb command line tool.The adb command to invoke our BroadcastReceiver and log the message "Hello" is the following:
adb shell am broadcast -a com.mycompany.android.intent.QA_MESSAGE --es message Hello
Now that you have hooked in to your app on a real device you can pretty much invoke whatever you would like. You just have to create the custom functionality in your QABroadcastReceiver.
Other use cases
You can do lots with this. Another use case was that we had an auto-refresh on one screen, where if the last refresh time was more than 1 hour ago, we would auto refresh the data. For QA this is a pain! So I added functionality to set this to x seconds so that QA could set it to whatever they liked and not have to wait a whole hour to test.A word of warning
Don't go too far with this though. You don't want your app that QA uses to be very different in functionality from production. Use this as a last resort and evaluate the risk of missing a potential bug by using this. Also depending on the nature of your app this may be too risky from a security standpoint so just use common sense.
Summary
So in this post we created a custom BroadcastReceiver to do our bidding on a real device! Once you have this in place you can can build custom functions to override pretty much anything in your app. I hope this helps someone in the future. Please leave comments and let me know what you think.Thanks for reading!