PhoneFinder - SMS Phone Locator


SDK Version: 
M5

Introduction

In this tutorial we will create an application called PhoneFinder. This application will illustrate how to deal with sending and receiving SMS messages. The idea of the application is that when your phone is lost or stolen you will be able to use someone else's phone to retrieve the GPS coordinates at your phone's location to help you find it.

This application needs an Activity that will allow the user to enter in the password and an IntentReceiver that will be kicked off on incoming SMS messages.

Click here to download the complete source.

Password Entry

We will use the simple dialog shown below for password entry. Once the password is correctly entered we will save a MD5 sum of the password into the SharedPreferences for the package. The preferences is an easy way to save small amounts of persistent data. It is also only accessible by classes in your package. We will take the extra precaution of saving an MD5 of the password, this way if the data was read somehow it would not reveal the plain text password unless the password is very weak (aka in the dictionary).

The layout for this dialog, main.xml, is shown below:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:orientation="vertical"
  4.     android:layout_width="fill_parent"
  5.     android:layout_height="fill_parent"
  6.     >
  7.  
  8.         <TextView
  9.             android:layout_width="fill_parent"
  10.             android:layout_height="wrap_content"
  11.             android:text="@string/password_label&quot;
  12.             />
  13.         <EditText android:id="@+id/password"
  14.                 android:maxLines="1"
  15.                 android:layout_marginTop="2dip"
  16.                 android:layout_width="wrap_content"
  17.                 android:ems="25"
  18.                 android:layout_height="wrap_content"
  19.                 android:autoText="true"
  20.                 android:scrollHorizontally="true"
  21.                 android:password="true" />
  22.         <TextView
  23.             android:layout_width="fill_parent"
  24.             android:layout_height="wrap_content"
  25.             android:text="@string/password_confirm_label"
  26.             />
  27.         <EditText android:id="@+id/password_confirm&quot;
  28.                 android:maxLines="1"
  29.                 android:layout_marginTop="2dip"
  30.                 android:layout_width="wrap_content"
  31.                 android:ems="25"
  32.                 android:layout_height="wrap_content"
  33.                 android:autoText="true"
  34.                 android:scrollHorizontally="true"
  35.                 android:password="true" />
  36.  
  37.     <Button android:id="@+id/ok"
  38.         android:layout_width="wrap_content"
  39.         android:layout_height="wrap_content"
  40.         android:layout_gravity="right"
  41.         android:text="@string/button_ok" />
  42.        
  43.         <TextView  android:id="@+id/text1"
  44.             android:layout_width="fill_parent"
  45.             android:layout_height="wrap_content"
  46.             />
  47. </LinearLayout>

As you can see it is a very simple layout, 2 text fields, 2 input fields, a button and another text field at the end to display messages to the user. The strings are defined in the strings.xml for better multilingual support.

The code for this activity is very simple. It's job is to make sure that the password is at least 6 characters, and that the 2 password fields match. Once that is confirmed then all we need to do is save the MD5 sum of the password the user entered into the SharedPreferences.

Here is the PhoneFinder Activity:

  1. public class PhoneFinder extends Activity {
  2.  
  3.         public static final String PASSWORD_PREF_KEY = "passwd";
  4.  
  5.         private TextView messages;
  6.         private EditText pass1;
  7.         private EditText pass2;
  8.  
  9.         @Override
  10.         public void onCreate(Bundle icicle) {
  11.                 super.onCreate(icicle);
  12.                 setContentView(R.layout.main);
  13.  
  14.                 messages = (TextView) findViewById(R.id.text1);
  15.                 pass1 = (EditText) findViewById(R.id.password);
  16.                 pass2 = (EditText) findViewById(R.id.password_confirm);
  17.  
  18.                 Button button = (Button) findViewById(R.id.ok);
  19.                 button.setOnClickListener(clickListener);
  20.         }
  21.  
  22.         private OnClickListener clickListener = new OnClickListener() {
  23.  
  24.                 public void onClick(View v) {
  25.                         String p1 = pass1.getText().toString();
  26.                         String p2 = pass2.getText().toString();
  27.  
  28.                         if (p1.equals(p2)) {
  29.  
  30.                                 if (p1.length() >= 6 || p2.length() >= 6) {
  31.  
  32.                                         Editor passwdfile = getSharedPreferences(PhoneFinder.PASSWORD_PREF_KEY, 0).edit();
  33.                                         String md5hash = getMd5Hash(p1);
  34.                                         passwdfile.putString(PhoneFinder.PASSWORD_PREF_KEY,
  35.                                                         md5hash);
  36.                                         passwdfile.commit();
  37.                                         messages.setText("Password updated!");
  38.  
  39.                                 } else
  40.                                         messages.setText("Passwords must be at least 6 characters");
  41.  
  42.                         } else {
  43.                                 pass1.setText("");
  44.                                 pass2.setText("");
  45.                                 messages.setText("Passwords do not match");
  46.                         }
  47.  
  48.                 }
  49.  
  50.         };
  51. }

In onCreate() we initialize the various Views that we are using in the layout and then we setup the OnClickListener object for the "ok" button. When the "ok" button is pressed we are taken down into the onClick() function that starts on line 40.

In the onClick() function we confirm that the requirements for password length is met, and we make sure that both of the text boxes match. If that all happens then we get to line 48 where we setup the SharedPreferences.Editor class. This class allows us to edit the shared preferences for this application. It is called "shared" because it is application wide preferences, there are also Activty level preferences available via Activity.getPreferences(int).

Writing to the preferences is easy once you have the Editor object. You just use one of the putX() functions to add key/value pairs and then call the commit() function to save the results.

Then on line 48 you'll see that we call the member function getMd5Hash(String) which returns the MD5 sum as a string, and we store that in the preferences. Here is the getMd5Hash(String) function:

  1. public static String getMd5Hash(String input) {
  2.         try     {
  3.                 MessageDigest md = MessageDigest.getInstance("MD5");
  4.                 byte[] messageDigest = md.digest(input.getBytes());
  5.                 BigInteger number = new BigInteger(1,messageDigest);
  6.                 String md5 = number.toString(16);
  7.            
  8.                 while (md5.length() < 32)
  9.                         md5 = "0" + md5;
  10.            
  11.                 return md5;
  12.         } catch(NoSuchAlgorithmException e) {
  13.                 Log.e("MD5", e.getMessage());
  14.                 return null;
  15.         }
  16. }

We use a android.security.MessageDigest object passing in "MD5" as the algorithm we want to use. The digest() function is called passing in a Byte array from the String passed in, and it returns a byte array. This byte array can then be saved as a BigInteger and then converted to a hex string using toString(16). When we convert this byte array to a BigInteger leading zeros will be trimmed, so we add leading zeros with the while loop until the MD5 sum reaches 32 characters.

On the next page we will create the IntentReciever that will listen for SMS messages and respond to a relavent SMS message...

Handling the SMS Message

Now that the user can save the password to the preferences we will need to check all incoming SMS messages and respond to any relavent ones. We are looking for a message in the format:
SMSLOCATE:<password>

So if a SMS messages starts with "SMSLOCATE:" and the MD5 sum of the password after the ":" matches that of the one saved earlier then we will send a text message with everything we know about the phones current location. To do this we need to setup an IntentReceiver that will respond to the "android.provider.Telephony.SMS_RECEIVED" action, this will take some additional lines in the AndroidManifest.xml file. Here is the AndroidManifest.xml file:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.     package="com.helloandroid.android.phonefinder">
  4.     <uses-permission android:name="android.permission.RECEIVE_SMS" />
  5.     <uses-permission android:name="android.permission.ACCESS_GPS" />
  6.     <uses-permission android:name="android.permission.ACCESS_LOCATION" />    
  7.     <application android:icon="@drawable/icon">
  8.         <activity android:name=".PhoneFinder" android:label="@string/app_name">
  9.             <intent-filter>
  10.                 <action android:name="android.intent.action.MAIN" />
  11.                 <category android:name="android.intent.category.LAUNCHER" />
  12.             </intent-filter>
  13.         </activity>
  14.        
  15.         <receiver android:name=".FinderReceiver">
  16.             <intent-filter>
  17.                 <action android:name="android.provider.Telephony.SMS_RECEIVED" />
  18.             </intent-filter>
  19.         </receiver>    
  20.        
  21.     </application>
  22. </manifest>

You'll see starting on line 4 that our application must request permission to receive SMS messages, Access the GPS device, and Access the phone's location. This is requested with the <uses-permission> tag. Then down on line 15 we must specify our receiver as "FinderReceiver" and also the intent-filter that will be checked against all intents that are broadcasted. You can see here we are only concerned with IntentBroadcasts with the action "android.provider.Telephony.SMS_RECEIVED".

Now the OS will know what receiver to call for that action, so lets create the IntentReceiver called FinderReceiver:

  1. public class FinderReceiver extends IntentReceiver {
  2.  
  3.         @Override
  4.         public void onReceiveIntent(Context context, Intent intent) {
  5.  
  6.                 SharedPreferences passwdfile = context.getSharedPreferences(
  7.                                 PhoneFinder.PASSWORD_PREF_KEY, 0);
  8.                
  9.                 String correctMd5 = passwdfile.getString(PhoneFinder.PASSWORD_PREF_KEY,
  10.                                 null);
  11.                
  12.                 if (correctMd5 != null) {
  13.  
  14.                         SmsMessage[] messages = Telephony.Sms.Intents
  15.                                         .getMessagesFromIntent(intent);
  16.  
  17.                         for (SmsMessage msg : messages) {
  18.                                 if (msg.getMessageBody().contains("SMSLOCATE:")) {
  19.                                         String[] tokens = msg.getMessageBody().split(":");
  20.                                         if (tokens.length >= 2) {
  21.                                                 String md5hash = PhoneFinder.getMd5Hash(tokens[1]);
  22.  
  23.                                                 if (md5hash.equals(correctMd5)) {
  24.                                                         String to = msg.getOriginatingAddress();
  25.                                                         LocationManager lm =
  26.                                                                 (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
  27.                                                        
  28.                                                         SmsManager sm = SmsManager.getDefault();
  29.  
  30.                                                         sm.sendTextMessage(to, null, lm.getCurrentLocation("gps").toString(),
  31.                                                                         null, null, null);
  32.                                                        
  33.                                                         Toast.makeText(context, context.getResources().getString(R.string.notify_text) + to,
  34.                                                                         Toast.LENGTH_SHORT).show();
  35.                                                 }
  36.                                         }
  37.                                 }
  38.                         }
  39.                 }
  40.         }
  41. }

We start by getting the correct MD5 sum for the saved password from the SharedPreferences, we do this on lines 18-21. If there is actually a password in there then we now want to loop through all of the SMS messages that were received.

We use Telphony.Sms.Intents.getMessageFromInent(intent) to get an array of SmsMessages. We will now loop through this array and see if the body of the message has "SMSLOCATE:" in it. If a message does, we need to get the password after the ":", take it's MD5 sum and compare it to the one we are looking for.

If the passwords match, then we get into the block starting on line 36. Now all we need is a String with the address to send it to, and a String with the location information. To get the location information we create a new LocationManager and simply use getCurrentLocation("gps").toString(). This will print out everything known about the location using gps as the location provider. We then send the text message using an SmsManager object. We also show a notification (Toast) saying that the message was sent.

Note: It might be a good idea to have an option in the password entry dialog to either show or don't show the notification. If the phone was stolen it would be better to hide the notification or the theif may realize that he's being tracked and will turn off the phone, etc.

Testing this operation

Now, everything is setup. With the new version of the SDK it is very easy to send in phone calls or text messages to the emulator. It is all done using the "Emulator Control" view in Eclipse. You can add this view by going to "Window -> Show View -> Other" and then selecting the "Emulator Control" in the Android section.

So to test first you need to launch the main activity and setup a password. For this example I've entered "123456" for the password. Now you can send a text message with "SMSLOCATE:123456" as the body of the text as shown below:

And then after you send it you should see a notification like this:

There you have it! I think this is a great example of how easy it is to develop useful applications for Android. Two basic objects to handle a very useful task.