Using SMS
The Android Framework provides full access to SMS functionality using the SmsManager class. Early versions of Android placed SmsManager in the android.telephony.gsm package. Since Android 1.5, where SmsManager supports both GSM and CDMA mobile telephony standards, the SmsManager class is now placed in the android.telephony package.
Sending an SMS through the SmsManager class is fairly straightforward:
- Set the permission in the AndroidManifest.xml file to send SMS:
<uses-permission android:name="android.permission.SEND_SMS" />
- Use the SmsManager.getDefault() static method to get an SMS manager instance:
SmsManager mySMS = SmsManager.getDefault();
- Define the destination phone number and the message that is to be sent. Use the sendTextMesssage() method to send the SMS to another device:
String destination = "16501234567"; String msg = "Sending my first message"; mySMS.sendTextMessage(destination, null, msg, null, null);
This is sufficient to send an SMS message. However, the three additional parameters in the previous call set to null can be used as follows:
- The second parameter is the specific SMS service center to use. Set this to null to use the default service center from the carrier.
- The fourth parameter is a PendingIntent to track if the SMS message was sent.
- The fifth parameter is a PendingIntent to track if the SMS message was received.
To use the fourth and fifth parameters, a sent message and a delivered message intent need to be declared:
String SENT_SMS_FLAG = "SENT_SMS"; String DELIVER_SMS_FLAG = "DELIVER_SMS"; Intent sentIn = new Intent(SENT_SMS_FLAG); PendingIntent sentPIn = PendingIntent.getBroadcast(this,0,sentIn,0); Intent deliverIn = new Intent(SENT_SMS_FLAG); PendingIntent deliverPIn = PendingIntent.getBroadcast(this,0,deliverIn,0);
Then, a BroadcastReceiver class needs to be registered for each PendingIntent to receive the result:
BroadcastReceiver sentReceiver = new BroadcastReceiver(){ @Override public void onReceive(Context c, Intent in) { switch(getResultCode()){ case Activity.RESULT_OK: //sent SMS message successfully; break; default: //sent SMS message failed break; } } }; BroadcastReceiver deliverReceiver = new BroadcastReceiver(){ @Override public void onReceive(Context c, Intent in) { //SMS delivered actions } }; registerReceiver(sentReceiver, new IntentFilter(SENT_SMS_FLAG)); registerReceiver(deliverReceiver, new IntentFilter(DELIVER_SMS_FLAG));
Most SMSs are restricted to 140 characters per text message. To make sure the message is within this limitation, use the divideMessage() method that divides the text into fragments in the maximum SMS message size. Then, the method sendMultipartTextMessage() should be used instead of the sendTextMessage() method. The only difference is the use of an ArrayList of messages and pending intents:
ArrayList<String> multiSMS = mySMS.divideMessage(msg); ArrayList<PendingIntent> sentIns = new ArrayList<PendingIntent>(); ArrayList<PendingIntent> deliverIns = new ArrayList<PendingIntent>(); for(int i=0; i< multiSMS.size(); i++){ sentIns.add(sentIn); deliverIns.add(deliverIn); } mySMS.sendMultipartTextMessage(destination, null, multiSMS, sentIns, deliverIns);
Recipe: Autosending an SMS Based on a Received SMS
Because most SMS messages are not read by the recipient until hours later, this recipe sends an autoresponse SMS when an SMS is received. This is done by creating an Android service in the background that can receive incoming SMSs. An alternative method is to register a broadcast receiver in the AndroidManifest.xml file.
The application must declare permission to send and receive SMSs in the AndroidManifest.xml file, as shown in Listing 10.3. It also declares a main activity SMSResponder that creates the autoresponse and a service ResponderService to send the response when an SMS is received.
Listing 10.3. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.cookbook.SMSResponder" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".SMSResponder" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:enabled="true" android:name=".ResponderService"> </service> </application> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.SEND_SMS"/> </manifest>
The main layout file shown in Listing 10.4 contains a LinearLayout with three views: a TextView to display the message used for the autoresponse, Button used to commit changes on the reply message inside the application, and EditText where the user can enter a reply message.
Listing 10.4. res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/display" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello" android:textSize="18dp" /> <Button android:id="@+id/submit" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Change my response" /> <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
The main activity is shown in Listing 10.5. It starts the service that listens and autoresponds to SMS messages. It also allows the user to change the reply message and save it in SharedPreferences for future use.
Listing 10.5. src/com/cookbook/SMSresponder/SMSResponder.java
package com.cookbook.SMSresponder; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class SMSResponder extends Activity { TextView tv1; EditText ed1; Button bt1; SharedPreferences myprefs; Editor updater; String reply=null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); myprefs = PreferenceManager.getDefaultSharedPreferences(this); tv1 = (TextView) this.findViewById(R.id.display); ed1 = (EditText) this.findViewById(R.id.editText); bt1 = (Button) this.findViewById(R.id.submit); reply = myprefs.getString("reply", "Thank you for your message. I am busy now." + "I will call you later"); tv1.setText(reply); updater = myprefs.edit(); ed1.setHint(reply); bt1.setOnClickListener(new OnClickListener() { public void onClick(View view) { updater.putString("reply", ed1.getText().toString()); updater.commit(); SMSResponder.this.finish(); } }); try { // Start service Intent svc = new Intent(this, ResponderService.class); startService(svc); } catch (Exception e) { Log.e("onCreate", "service creation problem", e); } } }
The majority of code is contained in the service, as shown in Listing 10.6. It retrieves SharedPreferences for this application first. Then, it registers a broadcast receiver for listening to incoming and outgoing SMS messages. The broadcast receiver for outgoing SMS messages is not used here but is shown for completeness.
The incoming SMS broadcast receiver uses a bundle to retrieve the protocol description unit (PDU), which contains the SMS text and any additional SMS metadata, and parses it into an Object array. The method createFromPdu() converts the Object array into an SmsMessage. Then the method getOriginatingAddress() can be used to get the sender’s phone number, and getMessageBody() can be used to get the text message.
In this recipe, after the sender address is retrieved, the respond() method is called. This method tries to get the data stored inside SharedPreferences for the autorespond message. If no data is stored, it uses a default value. Then, it creates two PendingIntents for sent status and delivered status. The method divideMessage() is used to make sure the message is not oversized. After all the data is managed, it is sent using sendMultiTextMessage().
Listing 10.6. src/com/cookbook/SMSresponder/ResponderService.java
package com.cookbook.SMSresponder; import java.util.ArrayList; import android.app.Activity; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.util.Log; import android.widget.Toast; public class ResponderService extends Service { //the action fired by the Android system when an SMS was received private static final String RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED"; private static final String SENT_ACTION="SENT_SMS"; private static final String DELIVERED_ACTION="DELIVERED_SMS"; String requester; String reply=""; SharedPreferences myprefs; @Override public void onCreate() { super.onCreate(); myprefs = PreferenceManager.getDefaultSharedPreferences(this); registerReceiver(sentReceiver, new IntentFilter(SENT_ACTION)); registerReceiver(deliverReceiver, new IntentFilter(DELIVERED_ACTION)); IntentFilter filter = new IntentFilter(RECEIVED_ACTION); registerReceiver(receiver, filter); IntentFilter attemptedfilter = new IntentFilter(SENT_ACTION); registerReceiver(sender,attemptedfilter); } private BroadcastReceiver sender = new BroadcastReceiver(){ @Override public void onReceive(Context c, Intent i) { if(i.getAction().equals(SENT_ACTION)) { if(getResultCode() != Activity.RESULT_OK) { String recipient = i.getStringExtra("recipient"); requestReceived(recipient); } } } }; BroadcastReceiver sentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context c, Intent in) { switch(getResultCode()) { case Activity.RESULT_OK: //sent SMS message successfully; smsSent(); break; default: //sent SMS message failed smsFailed(); break; } } }; public void smsSent() { Toast.makeText(this, "SMS sent", Toast.LENGTH_SHORT); } public void smsFailed() { Toast.makeText(this, "SMS sent failed", Toast.LENGTH_SHORT); } public void smsDelivered() { Toast.makeText(this, "SMS delivered", Toast.LENGTH_SHORT); } BroadcastReceiver deliverReceiver = new BroadcastReceiver() { @Override public void onReceive(Context c, Intent in) { //SMS delivered actions smsDelivered(); } }; public void requestReceived(String f) { Log.v("ResponderService","In requestReceived"); requester=f; } BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context c, Intent in) { Log.v("ResponderService","On Receive"); reply=""; if(in.getAction().equals(RECEIVED_ACTION)) { Log.v("ResponderService","On SMS RECEIVE"); Bundle bundle = in.getExtras(); if(bundle!=null) { Object[] pdus = (Object[])bundle.get("pdus"); SmsMessage[] messages = new SmsMessage[pdus.length]; for(int i = 0; i<pdus.length; i++) { Log.v("ResponderService","FOUND MESSAGE"); messages[i] = SmsMessage.createFromPdu((byte[])pdus[i]); } for(SmsMessage message: messages) { requestReceived(message.getOriginatingAddress()); } respond(); } } } }; @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } public void respond() { Log.v("ResponderService","Responding to " + requester); reply = myprefs.getString("reply", "Thank you for your message. I am busy now." + "I will call you later."); SmsManager sms = SmsManager.getDefault(); Intent sentIn = new Intent(SENT_ACTION); PendingIntent sentPIn = PendingIntent.getBroadcast(this, 0,sentIn,0); Intent deliverIn = new Intent(DELIVERED_ACTION); PendingIntent deliverPIn = PendingIntent.getBroadcast(this, 0,deliverIn,0); ArrayList<String> Msgs = sms.divideMessage(reply); ArrayList<PendingIntent> sentIns = new ArrayList<PendingIntent>(); ArrayList<PendingIntent> deliverIns = new ArrayList<PendingIntent>(); for(int i=0; i< Msgs.size(); i++) { sentIns.add(sentPIn); deliverIns.add(deliverPIn); } sms.sendMultipartTextMessage(requester, null, Msgs, sentIns, deliverIns); } @Override public void onDestroy() { super.onDestroy(); unregisterReceiver(receiver); unregisterReceiver(sender); } @Override public IBinder onBind(Intent arg0) { return null; } }