How to get sms sent confirmation for each contact/person in android?

This is a very simple example to demonstrate the use of the send and delivery PendingIntents available for all of the SmsManager#send*() methods, and attaching data to those to easily differentiate the results in the Receiver.

Attaching that data is as simple as putting extras on the Intents backing the PendingIntents we pass to the send*() methods. The catch is that PendingIntents might not behave as one expects. To conserve resources, the system will only create new ones when it must. The get*() methods will only return a distinct PendingIntent if the Intent is different per the Intent#filterEquals() method, the request code is not currently in use for an equal Intent, or an appropriate flag is passed.

Different extras on an otherwise-same Intent with the same request code will not cause a new PendingIntent to be created. Depending on the flag passed in that case, those extras might be ignored, or overwrite those in a currently active PendingIntent, which can lead to incorrect results.

In our example, we’re basically using the same Intent for each send, so we’ll ensure a distinct PendingIntent for each by passing unique request codes. This simple example uses the size of a shrinking list for those codes, which will be unique in the context of a single run. The request code can ultimately be any arbitrary int, as long as you know it’s unused at the time of request.

The system will want to cache these PendingIntents, should we need them again in the near future, so we’ll also pass FLAG_ONE_SHOT to “clear them out” after use, and make sure we get the correct, current extras in subsequent runs.

public class SmsActivity extends Activity implements View.OnClickListener {
    private static final String SMS_SENT_ACTION = "com.mycompany.myapp.SMS_SENT";
    private static final String SMS_DELIVERED_ACTION = "com.mycompany.myapp.SMS_DELIVERED";
    private static final String EXTRA_NUMBER = "number";
    private static final String EXTRA_MESSAGE = "message";

    // Initialize our sample numbers list.
    private final List<String> numberList = new ArrayList<String>() {{{
                add("111-111-1111");
                add("222-222-2222");
                add("333-333-3333");
    }}};

    // Initialize our sample message list.
    private final List<String> messageList = new ArrayList<String>() {{{
                add("Hello.");
                add("Howdy.");
                add("Hi.");
    }}};

    private SmsManager smsManager;
    private IntentFilter intentFilter;
    private BroadcastReceiver resultsReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sms);

        findViewById(R.id.button_send).setOnClickListener(this);

        smsManager = SmsManager.getDefault();
        resultsReceiver = new SmsResultReceiver();

        intentFilter = new IntentFilter(SMS_SENT_ACTION);
        intentFilter.addAction(SMS_DELIVERED_ACTION);
    }

    @Override
    protected void onResume() {
        super.onResume();
        registerReceiver(resultsReceiver, intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(resultsReceiver);
    }

    public void onClick(View v) {
        v.setEnabled(false);
        sendNextMessage();
    }

    private void sendNextMessage() {
        // We're going to remove numbers and messages from
        // the lists as we send, so if the lists are empty, we're done.
        if (numberList.size() == 0) {
            return;
        }

        // The list size is a sufficiently unique request code,
        // for the PendingIntent since it decrements for each send.
        int requestCode = numberList.size();

        String number = numberList.get(0);
        String message = messageList.get(0);

        // The Intents must be implicit for this example,
        // as we're registering our Receiver dynamically.
        Intent sentIntent = new Intent(SMS_SENT_ACTION);
        Intent deliveredIntent = new Intent(SMS_DELIVERED_ACTION);

        // We attach the recipient's number and message to
        // the Intents for easy retrieval in the Receiver.
        sentIntent.putExtra(EXTRA_NUMBER, number);
        sentIntent.putExtra(EXTRA_MESSAGE, message);
        deliveredIntent.putExtra(EXTRA_NUMBER, number);
        deliveredIntent.putExtra(EXTRA_MESSAGE, message);

        // Construct the PendingIntents for the results.
        // FLAG_ONE_SHOT cancels the PendingIntent after use so we
        // can safely reuse the request codes in subsequent runs.
        PendingIntent sentPI = PendingIntent.getBroadcast(this,
                                                          requestCode,
                                                          sentIntent,
                                                          PendingIntent.FLAG_ONE_SHOT);

        PendingIntent deliveredPI = PendingIntent.getBroadcast(this,
                                                               requestCode,
                                                               deliveredIntent,
                                                               PendingIntent.FLAG_ONE_SHOT);

        // Send our message.
        smsManager.sendTextMessage(number, null, message, sentPI, deliveredPI);

        // Remove the number and message we just sent to from the lists.
        numberList.remove(0);
        messageList.remove(0);
    }

    private class SmsResultReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            // A simple result Toast text.
            String result = null;

            // Get the result action.
            String action = intent.getAction();

            // Retrieve the recipient's number and message.
            String number = intent.getStringExtra(EXTRA_NUMBER);
            String message = intent.getStringExtra(EXTRA_MESSAGE);

            // This is the result for a send.
            if (SMS_SENT_ACTION.equals(action)) {
                int resultCode = getResultCode();
                result = "Send result : " + translateSentResult(resultCode);

                // The current send is complete. Send the next one.
                sendNextMessage();
            }
            // This is the result for a delivery.
            else if (SMS_DELIVERED_ACTION.equals(action)) {
                SmsMessage sms = null;

                // A delivery result comes from the service
                // center as a simple SMS in a single PDU.
                byte[] pdu = intent.getByteArrayExtra("pdu");
                String format = intent.getStringExtra("format");

                // Construct the SmsMessage from the PDU.
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && format != null) {
                    sms = SmsMessage.createFromPdu(pdu, format);
                }
                else {
                    sms = SmsMessage.createFromPdu(pdu);
                }

                // getResultCode() is not reliable for delivery results.
                // We need to get the status from the SmsMessage.
                result = "Delivery result : " + translateDeliveryStatus(sms.getStatus());
            }

            result = number + ", " + message + "\n" + result;
            Toast.makeText(context, result, Toast.LENGTH_SHORT).show();
        }

        String translateSentResult(int resultCode) {
            switch (resultCode) {
                case Activity.RESULT_OK:
                    return "Activity.RESULT_OK";
                case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                    return "SmsManager.RESULT_ERROR_GENERIC_FAILURE";
                case SmsManager.RESULT_ERROR_RADIO_OFF:
                    return "SmsManager.RESULT_ERROR_RADIO_OFF";
                case SmsManager.RESULT_ERROR_NULL_PDU:
                    return "SmsManager.RESULT_ERROR_NULL_PDU";
                case SmsManager.RESULT_ERROR_NO_SERVICE:
                    return "SmsManager.RESULT_ERROR_NO_SERVICE";
                default:
                    return "Unknown error code";
            }
        }

        String translateDeliveryStatus(int status) {
            switch (status) {
                case Telephony.Sms.STATUS_COMPLETE:
                    return "Sms.STATUS_COMPLETE";
                case Telephony.Sms.STATUS_FAILED:
                    return "Sms.STATUS_FAILED";
                case Telephony.Sms.STATUS_PENDING:
                    return "Sms.STATUS_PENDING";
                case Telephony.Sms.STATUS_NONE:
                    return "Sms.STATUS_NONE";
                default:
                    return "Unknown status code";
            }
        }
    }
}

Notes:

  • Do make note of the method we’re using to get the delivery status. The result code in the Receiver is not a reliable indicator. We must check the getStatus() return of the SmsMessage obtained from the PDU extra on the Intent to get the actual result.

  • Also be aware that not all carriers provide delivery results, in which case the delivery PendingIntents will never fire. Do not rely on a delivery result.

  • This example uses a “correct”, albeit simple, method to sequentially send multiple messages, in that it waits until the current send is compete before proceeding to the next. For short lists, you might be able to get away with a loop firing all of the sends as quickly as it executes, but this can result in a generic failure if the system can’t keep up.

  • As noted, this is a very simple example. It is not really suitable for production, as the dynamically registered Receiver is tied to the Activity‘s lifecycle. Ideally, you’d want to implement a static Receiver class, registered in the manifest, and use explicit Intents to target it. Using a Service to process the results is also recommended, and those results could be delivered to the UI through any number of mechanisms; e.g., LocalBroadcastManager, another event bus implementation, Intents, Notifications, etc.

Leave a Comment