Android Send and Receive SMS (Text and Data/Binary) Messages with SmsManager and Intents

There’ll be times when you’ll want to allow the users to send SMS messages directly from your app to other numbers (destination). The Android SDK does support to capability of sending SMS/MMS messages in two ways (from your app):

  • Invoke an SMS client app via Implicit Intents.
  • Send the SMS directly yourself by using the SmsManager telephony class API.

We’ll delve into both the approaches.

Using Implicit Intents

Using Android implicit intents we can display a list of SMS client apps that the user already has on his phone that he can use. Obviously if they have only one app (which is generally the default Messaging app unless other apps registered themselves for these intents using intent filter) then that’ll open up directly without presenting the user with a dialog filled with a list of choices that can be made. We can either use the ACTION_SENDTO action or ACTION_VIEW.

Let’s see an example where ACTION_VIEW is used:

String phoneNumber = "9999999999";
String smsBody = "This is an SMS!";

Intent smsIntent = new Intent(Intent.ACTION_VIEW);
// Invokes only SMS/MMS clients
smsIntent.setType("vnd.android-dir/mms-sms");
// Specify the Phone Number
smsIntent.putExtra("address", phoneNumber);
// Specify the Message
smsIntent.putExtra("sms_body", smsBody);

// Shoot!
startActivity(smsIntent);

With that piece of code, if you now test your app on your device, your Messaging app should be invoked with the number (or name if the number exists in your contacts) and message pre-populated. Amazing, isn’t it! Now we’ll see an example where ACTION_SENDTO is used:

String phoneNumber = "9999999999";
String smsBody = "This is an SMS!";

// Add the phone number in the data
Uri uri = Uri.parse("smsto:" + phoneNumber);
// Create intent with the action and data
Intent smsIntent = new Intent(Intent.ACTION_SENDTO, uri);
// smsIntent.setData(uri); // We just set the data in the constructor above
// Set the message
smsIntent.putExtra("sms_body", smsBody);

startActivity(smsIntent);

Using the SmsManager API

With the android.telephone.SmsManager class, we can send an SMS automatically in two lines of API code.

String phoneNumber = "9999999999";
String smsBody = "Message from the API";

// Get the default instance of SmsManager
SmsManager smsManager = SmsManager.getDefault();
// Send a text based SMS
smsManager.sendTextMessage(phoneNumber, null, smsBody, null, null);

This method requires the SEND_SMS permission which you should request in the manifest file.

<uses-permission android:name="android.permission.SEND_SMS" />

The first argument passed to sendTextMessage() is the destination address to which the message has to be sent and the second argument is the SMSC address (it’s also like a phone number generally) to which if you pass null, the default service center of the device’s carrier will be used. Third argument is the text message to be sent in the SMS. The fourth and fifth arguments if not null must be pending intents performing broadcasts when the message is successfully sent (or failed) and delivered to the recipient.

Let’s see an example where we set broadcast receivers for when the message is sent successfully and is also delivered (or not).

String phoneNumber = "9999999999";
String smsBody = "This is an SMS!";

String SMS_SENT = "SMS_SENT";
String SMS_DELIVERED = "SMS_DELIVERED";

PendingIntent sentPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
PendingIntent deliveredPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);

// For when the SMS has been sent
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        switch (getResultCode()) {
            case Activity.RESULT_OK:
                Toast.makeText(context, "SMS sent successfully", Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                Toast.makeText(context, "Generic failure cause", Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_NO_SERVICE:
                Toast.makeText(context, "Service is currently unavailable", Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
                Toast.makeText(context, "No pdu provided", Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
                Toast.makeText(context, "Radio was explicitly turned off", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}, new IntentFilter(SMS_SENT));

// For when the SMS has been delivered
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        switch (getResultCode()) {
            case Activity.RESULT_OK:
                Toast.makeText(getBaseContext(), "SMS delivered", Toast.LENGTH_SHORT).show();
                break;
            case Activity.RESULT_CANCELED:
                Toast.makeText(getBaseContext(), "SMS not delivered", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}, new IntentFilter(SMS_DELIVERED));

// Get the default instance of SmsManager
SmsManager smsManager = SmsManager.getDefault();
// Send a text based SMS
smsManager.sendTextMessage(phoneNumber, null, smsBody, sentPendingIntent, deliveredPendingIntent);

The code is pretty simple actually if you know the concept of pending intents and broadcast receivers.

Divide and Send Multipart Text Messages

Generally an SMS is restricted to 160 (7 bit) characters or 140 (8 bit) characters. So if your message is longer than that, then you’ll have to divide it into multiple parts using divideMessage() and then send with the sendMultipartTextMessage() method. It’s fairly simple, let’s see an example.

// Get the default instance of SmsManager
SmsManager smsManager = SmsManager.getDefault();

String phoneNumber = "9999999999";
String smsBody = "Some piece of really long text, longer than 140*n characters!";

String SMS_SENT = "SMS_SENT";
String SMS_DELIVERED = "SMS_DELIVERED";

PendingIntent sentPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
PendingIntent deliveredPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);

ArrayList<String> smsBodyParts = smsManager.divideMessage(smsBody);
ArrayList<PendingIntent> sentPendingIntents = new ArrayList<PendingIntent>();
ArrayList<PendingIntent> deliveredPendingIntents = new ArrayList<PendingIntent>();

for (int i = 0; i < smsBodyParts.size(); i++) {
    sentPendingIntents.add(sentPendingIntent);
    deliveredPendingIntents.add(deliveredPendingIntent);
}

// For when the SMS has been sent
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        switch (getResultCode()) {
            case Activity.RESULT_OK:
                Toast.makeText(context, "SMS sent successfully", Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                Toast.makeText(context, "Generic failure cause", Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_NO_SERVICE:
                Toast.makeText(context, "Service is currently unavailable", Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
                Toast.makeText(context, "No pdu provided", Toast.LENGTH_SHORT).show();
                break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
                Toast.makeText(context, "Radio was explicitly turned off", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}, new IntentFilter(SMS_SENT));

// For when the SMS has been delivered
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        switch (getResultCode()) {
            case Activity.RESULT_OK:
                Toast.makeText(getBaseContext(), "SMS delivered", Toast.LENGTH_SHORT).show();
                break;
            case Activity.RESULT_CANCELED:
                Toast.makeText(getBaseContext(), "SMS not delivered", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}, new IntentFilter(SMS_DELIVERED));

// Send a text based SMS
smsManager.sendMultipartTextMessage(phoneNumber, null, smsBodyParts, sentPendingIntents, deliveredPendingIntents);

Send Binary (Data) SMS

We can send binary messages (as opposed to text based messages that we covered earlier) to specific application ports using sendDataMessage(). According to this Stack Exchange thread data sms is one which is sent over 2G/3G as well as GSM. I’ve tested it with mobile data turned off and it works fine charging me the same amount, so not very sure on whether it uses 2G/3G or not, but generally the term data is used in telephony when it’s related to network (tcp/ip). Anyway, SMS’s are generally sent to a specific port on the device (which is probably port 0 [zero]). But using sendDataMessage() we can send SMS’s to some other random port on which our app can listen for incoming SMSs and do something with that. In this case the default messaging app will not store the SMSs in their inbox for both the sender as well as the receiver.

For binary messages, Java uses UTF-8 encoding hence the length is 140 characters out of which the User Data Header (UDH) data size is 7 bytes, hence 133 characters can be sent. More details on SMPP, PDUs and UDH here.

We’ll see how to send data messages now.

// Get the default instance of SmsManager
SmsManager smsManager = SmsManager.getDefault();

String phoneNumber = "9999999999";
byte[] smsBody = "Let me know if you get this SMS".getBytes();
short port = 6734;

// Send a text based SMS
smsManager.sendDataMessage(phoneNumber, null, port, smsBody, null, null);

This piece of code will send a data message to the port 2345 on the recipient’s device.

Receive SMS Text Messages

Now that we know how to send SMS messages, it’s time to see how to receive them using broadcast receivers as soon as the recipient device receives it.

Firstly, you’ll need the RECEIVE_SMS permission, so put this in your manifest:

<uses-permission android:name="android.permission.RECEIVE_SMS" />

Next we’ll create a broadcast receiver class called SmsManager.java to listen for any incoming SMS. So create the file and put this code into it:

public class SmsReceiver extends BroadcastReceiver {
    private String TAG = SmsReceiver.class.getSimpleName();

    public SmsReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // Get the data (SMS data) bound to intent
        Bundle bundle = intent.getExtras();

        SmsMessage[] msgs = null;

        String str = "";

        if (bundle != null) {
            // Retrieve the SMS Messages received
            Object[] pdus = (Object[]) bundle.get("pdus");
            msgs = new SmsMessage[pdus.length];

            // For every SMS message received
            for (int i=0; i < msgs.length; i++) {
                // Convert Object array
                msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
                // Sender's phone number
                str += "SMS from " + msgs[i].getOriginatingAddress() + " : ";
                // Fetch the text message
                str += msgs[i].getMessageBody().toString();
                // Newline 🙂
                str += "\n";
            }

            // Display the entire SMS Message
            Log.d(TAG, str);
        }
    }
}

It’ll loop through all the PDUs (one for each message in a multipart text message) and form a big string with the sender phone number and the messages. Finally it logs the entire string formed. This reception technique will work for both single and multipart messages. You’ll also need create an entry for the receiver in the manifest file with an SMS_RECEIVED_ACTION intent filter.

<receiver
    android:name=".SmsReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="999">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

You can learn more about intent filter android:priority here.

The code will dump something like this:

D/SmsReceiver(21819): SMS from +919999999999 : This is an SMS!

Binary SMS Receiver

When a binary SMS is received, the code has to be a little different as the data received is not in plain text but binary. Here’s the code handling the reception:

public class SmsReceiver extends BroadcastReceiver {
    private String TAG = SmsReceiver.class.getSimpleName();

    public SmsReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // Get the data (SMS data) bound to intent
        Bundle bundle = intent.getExtras();

        SmsMessage[] msgs = null;

        String str = "";

        if (bundle != null){
            // Retrieve the Binary SMS data
            Object[] pdus = (Object[]) bundle.get("pdus");
            msgs = new SmsMessage[pdus.length];

            // For every SMS message received (although multipart is not supported with binary)
            for (int i=0; i<msgs.length; i++) {
                byte[] data = null;

                msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);

                str += "Binary SMS from " + msgs[i].getOriginatingAddress() + " :";

                str += "\nBINARY MESSAGE: ";

                // Return the User Data section minus the
                // User Data Header (UDH) (if there is any UDH at all)
                data = msgs[i].getUserData();

                // Generally you can do away with this for loop
                // You'll just need the next for loop
                for (int index=0; index < data.length; index++) {
                    str += Byte.toString(data[index]);
                }

                str += "\nTEXT MESSAGE (FROM BINARY): ";

                for (int index=0; index < data.length; index++) {
                    str += Character.toString((char) data[index]);
                }

                str += "\n";
            }

            // Dump the entire message
            // Toast.makeText(context, str, Toast.LENGTH_LONG).show();
            Log.d(TAG, str);
        }
    }
}

It’ll dump something like this:

D/SmsReceiver(25507): Binary SMS from +919999999999 :
D/SmsReceiver(25507): BINARY MESSAGE: 7610111632109101321071101111193210510232121111117321031011163211610410511532837783
D/SmsReceiver(25507): TEXT MESSAGE (FROM BINARY): Let me know if you get this SMS

The entry in the manifest file will also be a little different with the most important portion beind, specifying the port and a different action (from the previous one).

<receiver
    android:name=".SmsReceiver"
    android:enabled="true"
    android:exported="true"
    >
    <intent-filter android:priority="10" >
        <action android:name="android.intent.action.DATA_SMS_RECEIVED" />
        <data
            android:scheme="sms"
            android:host="*"
            android:port="6734" />
    </intent-filter>
</receiver>

You should read this SO thread that has some useful information regarding the manifest entry.

Dynamic BroadcastReceiver Registration

We registered our broadcast receiver in the manifest file (statically). Alternatively, we can also dynamically register it in the code itself. You should completely understand the difference between statically and dynamically registered broadcast receivers. I’ve explained everything in my article. Let’s see how to register the previous binary sms receiver example dynamically in code:

// Register a broadcast receiver
IntentFilter intentFilter = new IntentFilter("android.intent.action.DATA_SMS_RECEIVED");
intentFilter.setPriority(10);
intentFilter.addDataScheme("sms");
intentFilter.addDataAuthority("*", "6734");
registerReceiver(smsReceiver, intentFilter);

The object smsReceiver is that of the SmsReceiver broadcast receiver implementation (class). Instead of having a separate class you can also have smsReceiver as an instance member variable to which you assign an anonymous class implementation of BroadcastReceiver.

BroadcastReceiver smsReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // code to read the incoming SMS
    }
}

Changes in SMS Reception Since Android 4.4 KitKat (API Level 19)

A lot has changed in Android 4.4 (API level 19). Firstly, with just SMS_RECEIVED_ACTION intent filter, apps can only “observe” (or “monitor”) incoming messages, i.e., read them for purposes like phone verification. But this will not give you write access to the SMS provider (content provider) defined by the android.provider.Telephony class and subclasses. This also means now you cannot delete messages.

To get write access to the provider, you’ll need your app to be selected as the default messaging app by the user. Then incoming SMS’s will be delivered to your app with the SMS_DELIVER_ACTION intent and also you’ll need to request the BROADCAST_SMS permission.

You can find more information on the process to build a full blown messaging app that works on Kitkat and above here.

Pre-Android 4.4, the SMS_RECEIVED_ACTION intents were ordered broadcasts that could be aborted by one of the receivers. This would cause a lot of issues in some cases where a third party app with highest priority would abort the broadcast preventing it from getting received by another third party app’s receivers. Since Kitkat, this is not possible anymore (good news!), i.e., any attempt to abort the broadcast will be ignored and all the apps who had registered to receive incoming messages will get a chance.

Wrapping Up

So we’ve learnt all the different ways in which one can send and receive messages from an Android application programatically. Although receiving is not something that would work consistently across all heavily used versions (as of now), because of the conceptual (and implementation) changes since Android 4.4. If you’re building an application that will require write access to the SMS Content Provider as it might let the user perform all sorts of tasks related to messaging, then you should definitely read this article to make your SMS app Kitkat (and above) ready. Do note that the SmsManager API can also be used to send MMS (Multimedia Messagin Service) messages.

Efficient Android Threading - I highly recommend this book if you want to learn how various components of the Android framework works. You'll learn a ton about Android internals and components like AsyncTask, Services, Broadcast Receivers, Executor Framework, Inter-Process Communication, etc. It also covers various OS internal topics like threads, processes, kernel and how all those components work to execute an Android app. Must read!

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

18 thoughts on “Android Send and Receive SMS (Text and Data/Binary) Messages with SmsManager and Intents”

  1. Hi rishabh ,

    I am developing an application in which i am able to fetch the contacts list from android phone in the autocomplete textview and then on save button that 5 different phone numbers are saved in the database…now what i am doing is using sqlite database and able to fetch the phone numbers from it. but now i want when anyone click on send sms button the message should be sent to these 5 phone numbers at once using smsmanager class. However i am not able to do it.Can you help me on it..Thanks.

  2. Hi, thanks for the useful guide! Do you know of any way to easily send “multipart” data messages (that exceed the 133 byte limit) rather than writing custom code to put multiple messages together at the receiver? Cheers

      1. not really. that section is about text messages, not data messages. There is no explicit method for sending multipart data messages. You are probably expected to add some sort of splitting protocol on top of the messaging system.

  3. Hi, Rishab
    Iam developing an android app where i should send sms like ( REG mobile no 1111) to a particular number for registration . and i should ge ta reply from that number that registration is successfull. so please help me in developing this logic.
    please send me the code that how i could send an sms through this format.
    Thanking you

  4. Dear rishabh i have a datawind kitkat 4.2.2 7 inch dual sim 2g tablet wich not supports goofle play service…plz tell how to root it

  5. my question is, in android emulator send sms message within for loop works fine, but in real phone it is not, the trick was to wait for the broadcast receiver then continue with the for loop, any idea on how to implement the idea?

  6. Object[] pdus = (Object[]) bundle.get(“pdus”);
    returns as String thus giving me ClassCastException on a Samsung S2 Plus running 4.1.2

    any thoughts?

  7. Hi Rishab.
    I am develop an app. i.e Expanses manager…
    tools use android studio….
    i want to update,insert and delete a record. i want to send the updated message to the group friends and add that data to friend application on the SQlite database automatically.But app ma be the same both the sender and receiver how could i do it….

  8. Hi Rishabh, I am trying to develop a simple sms sending demo app to only send the sms so i know there is one permission code and two lines of code to send the sms but its not working in android studio. I need help on this. Thanks!

  9. Thanx for nice tutorial
    i implemented this code but i always get the same response “service is unavailable”
    can you help me to understand why i getting this..?

  10. Rishab, Thanks for delightful code explanation.

    I understand the with PI we can get SMS delivery notification. But, Is there anyway we can fetch details of delivery notification? My app sends multiple SMS together, Can we get any details like which SMS delivered and which aren’t.

    Any input would be appreciated. Thanks.

  11. The code you have provided is only for case of single sim card. if it is dual sim then the android will send the sms from 1st sim or default sim . if i want to send sms from 2nd sim card then what will i do ?

Leave a Reply

Your email address will not be published. Required fields are marked *


*