One of the earliest attractions of mobile programming has been location based applications. Your mobile knows where you are, and you can use that information in your application in any way you want. In this article we would be creating an application that responds to an SMS query with your location.
The requirement
We will develop an application that runs on your mobile. When someone sends you an SMS with the text #WhereAreYou#, the application intercepts the message and replies the sender with your location. The application has a service running that listens for the SMS. I have kept my explanations brief, with the assumption either you already have some knowledge of Android programming or you have been following my previous PCQuest articles on Android programming. Some amount of Internet search always helps, and I have not provided elaborate explanations of APIs for which you can refer to the documentation.
The solution
Create a new Eclipse project ‘WhereAreYou’. There is a TextView that gets created by default. Assign this Hello World TextView the ID txtHello. Grant android.permission.ACCESS_FINE_LOCATION permission to your application. We will build the application in steps, so that you can confirm some concept works for you before you try other concepts. You would find it easier to debug this way if something does not work. First we will build an application that waits till you get the address, and sends it to a pre-specified number. I have pasted below the code for the activity class.
public class MainActivity extends Activity {
private static final String SMS_TO = "09999999999";
private String strAddress;
private final LocationListener lolSMS = new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
@Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
@Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
@Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
refreshUI(location);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LocationManager lmgSMS = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
Location locCurrent = lmgSMS.getLastKnownLocation(LocationManager.GPS_PROVIDER);
refreshUI(locCurrent);
lmgSMS.requestLocationUpdates(LocationManager. NETWORK_PROVIDER, 1000, 1, lolSMS);
}
private void parseLocation(Location locCurrent) {
if (locCurrent != null) {
double dblLatitude = locCurrent.getLatitude();
double dblLongitude = locCurrent.getLongitude();
Geocoder gocSMS = new Geocoder(this, Locale.getDefault());
try {
List lstAddresses = gocSMS.getFromLocation(dblLatitude, dblLongitude, 1);
if (lstAddresses.size() > 0) {
strAddress = "";
Address adrCurrent = lstAddresses.get(0);
for (int intCounter = 0; intCounter < adrCurrent.getMaxAddressLineIndex(); intCounter++) {
strAddress += adrCurrent.getAddressLine(intCounter) + "\n";
}
strAddress += "Locality: " + adrCurrent.getLocality() + "\n";
strAddress += "Country: " + adrCurrent.getCountryName();
SmsManager smsElevator = SmsManager.getDefault();
smsElevator.sendTextMessage(SMS_TO, null, strAddress, null, null);
finish();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void refreshUI(Location locCurrent) {
TextView txtHello = (TextView)findViewById(R.id.txtHello);
parseLocation(locCurrent);
if (locCurrent != null) {
txtHello.setText(strAddress);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
I have put an arbitrary number for the SMS_TO constant. In your code, provide a phone number here who will receive the SMS in this first iteration of the application. I am using a global variable to store the address that we will find out in the rest of the code. We are defining a location listener that will listen for change in location and then call back. In the onCreate() method, we are getting a reference to the location manager and then extracting the last known location. The refreshUI() method updates the user interface with the latest location. We are also registering the location listener in onCreate(). The parseLocation() method takes a Location as its argument and converts the location to a string with a readable address. It also sends the SMS after parsing.
Run the application and check if you are getting the desired results. We will next create the receiver that will watch for an SMS . If the desired SMS is received, the receiver will start a service.
Grant the android.permission.RECEIVE_SMS permission to your application. We will next create the service that will be started when you receive the SMS.
The receiver class will send the phone number to the service it starts as an extra.
public class SMSReceiver extends BroadcastReceiver {
public final String WHEREAREYOU = "#WhereAreYou#";
public static final String PHONENUMBER = "PhoneNumber";
@Override
public void onReceive(Context context, Intent intent) {
Bundle bunSMS = intent.getExtras();
if (bunSMS != null) {
Object<> pdus = (Object<>)bunSMS.get("pdus");
SmsMessage<> msgLocations = new SmsMessage
for (int intCounter = 0; intCounter < pdus.length; intCounter++) {
msgLocations
}
for (SmsMessage msgLocation : msgLocations) {
String strMessage = msgLocation.getMessageBody();
String strFrom = msgLocation.getOriginatingAddress();
if (strMessage.equals(WHEREAREYOU)) {
Intent itnStart = new Intent(context, LocationService.class);
itnStart.putExtra(PHONENUMBER, strFrom);
context.startService(itnStart);
}
}
}
}
The service will start only if the SMS received matches #WhereAreYou#, else the SMS will be ignored. Just like the Twitter hashtags, choosing such a string means there is less possibility of the application responding to casual conversations with your friends with an automated reply. I have defined the string as a constant, and feel free to choose a different string to suit your taste.
Here is the service class.
public class LocationService extends Service {
private String strAddress = "";
private String strTo;
private LocationManager lmgSMS;
private final LocationListener lolSMS = new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
@Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
@Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
@Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
if (location != null) {
sendLocation(location);
}
}
};
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Bundle bunSMS = intent.getExtras();
strTo = bunSMS.getString(SMSReceiver.PHONENUMBER);
lmgSMS = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
lmgSMS.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1, lolSMS);
return START_STICKY;
}
public void onCreate() {
super.onCreate();
}
private void parseLocation(Location locCurrent) {
if (locCurrent != null) {
double dblLatitude = locCurrent.getLatitude();
double dblLongitude = locCurrent.getLongitude();
Geocoder gocSMS = new Geocoder(this, Locale.getDefault());
if (Geocoder.isPresent()) {
List lstAddresses;
try {
lstAddresses = gocSMS.getFromLocation(dblLatitude, dblLongitude, 1);
if (lstAddresses.size() > 0) {
strAddress = "Address:";
Address adrCurrent = lstAddresses.get(0);
for (int intCounter = 0; intCounter < adrCurrent.getMaxAddressLineIndex(); intCounter++) {
strAddress += adrCurrent.getAddressLine(intCounter) + "\n";
}
strAddress += "Locality: " + adrCurrent.getLocality() + "\n";
strAddress += "Country: " + adrCurrent.getCountryName();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
private void sendLocation(Location locCurrent) {
if (locCurrent != null) {
parseLocation(locCurrent);
if (strAddress.length() > 0) {
SmsManager smsLocation = SmsManager.getDefault();
smsLocation.sendTextMessage(strTo, null, strAddress, null, null);
lmgSMS.removeUpdates(lolSMS);
stopSelf();
}
}
}
In the location listener, we are checking that the new location is not null, before proceeding with processing the location. In the onStartCommand(), we are extracting the phone number that was sent as an extra. The sendLocation() method sends the location as an SMS. After sending the SMS, we are unregistering the location listener, as there is a chance the sticky service may send multiple SMSes. We are subsequently stopping the service.
There is no important task left for the activity now, so let us cut it short as follows:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
finish();
}
}
We are done with the application now. The benefit of the application is from the point of view of safety. You might have a concern about privacy, and I am treating the hash tag as kind of a password. That may not be enough, and you may want to rewrite the service or the receiver so that the application would respond to SMS originating only from your approved list of numbers. Please note a limitation that because the application relies on the Geocoder, you are required to have a working Internet connection. I have also used the network provider instead of the GPS provider. While that compromises on accuracy of the location, the service responds faster.