How to check for successful Multi-part sms send

user2400538 picture user2400538 · May 20, 2013 · Viewed 7.8k times · Source

I have read lots of posts on sending SMS and multipart SMS messages such as:

Sending SMS in Android, Sending and Receiving SMS and MMS in Android (pre Kit Kat Android 4.4), Android PendingIntent extras, not received by BroadcastReceiver

... and others, but they don't seem to go into checking that all parts of the message were sent successfully.

From what I understand, with a multi part message, you add a PendingIntent for each message part, but the examples I have seen don't seem to check that all parts are sent successfully... if one is (I guess the first one) they seem to assume it was successful...

So I thought I would send a multipart message and keep track of the parts. I would not say the message is successfully sent until all parts are successfully sent.

I have attempted to do this in the following code... SmsMessageInfo is just a simple class containing the phone number, a message and a list of booleans for parts processed and a list of parts successfully sent, it also has a unique message Id.

I have tried the following:

private void sendLongSmsMessage(Context context, final SmsMessageInfo messageInfo) {

    // Receive when each part of the SMS has been sent (or does it????)
    context.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String messageIdAsString = intent.getExtras().getString(INTENT_EXTRA_MESSAGE_ID_KEY);

            Log.i("SMSMessageSender", "Broadcast Intent Recieved, IntentMessageID: " + messageIdAsString + "  messageInfoId: " + messageInfo.messageId);

            if (messageIdAsString != null) {
                if (Long.parseLong(messageIdAsString) == messageInfo.messageId) {
                    String messagePartNrAsString = (String) intent.getExtras().get(INTENT_EXTRA_PART_NR_KEY);
                    int messagePartNr = Integer.parseInt(messagePartNrAsString);

                    Log.i("SMSMessageSender", "Broadcast Intent Recieved Multi Part Message Part No: " + messagePartNrAsString);

                    // We need to make all the parts succeed before we say we have succeeded.
                    switch (getResultCode()) {
                    case Activity.RESULT_OK:
                        messageInfo.partsSent.add(messagePartNr, true);
                        break;
                    case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                        messageInfo.failMessage = "Error - Generic failure";
                        break;
                    case SmsManager.RESULT_ERROR_NO_SERVICE:
                        messageInfo.failMessage = "Error - No Service";
                        break;
                    case SmsManager.RESULT_ERROR_NULL_PDU:
                        messageInfo.failMessage = "Error - Null PDU";
                        break;
                    case SmsManager.RESULT_ERROR_RADIO_OFF:
                        messageInfo.failMessage = "Error - Radio off";
                        break;
                    }

                    messageInfo.partsProcessed.add(messagePartNr, true);

                    boolean allSent = true;
                    for (Boolean partSent : messageInfo.partsSent) {
                        allSent = allSent && partSent;
                    }
                    messageInfo.sent = allSent;

                    boolean allProcessed = true;
                    for (Boolean partProcessed : messageInfo.partsProcessed) {
                        allProcessed = allProcessed && partProcessed;
                    }

                    if (allProcessed) {
                        // We have our response for all of our message parts, so we can unregister our receiver.
                        Log.i("SMSMessageSender", "All message part resoponses received, unregistering message Id: " + messageIdAsString);
                        context.unregisterReceiver(this);
                    } 
                } else {
                    Log.w("SMSMessageSender", "Received a broadcast message with Id for a different message");
                }
            } else {
                Log.w("SMSMessageSender", "Received a broadcast message but not for message Id");
            }
        }
    }, new IntentFilter(SENT));

    ArrayList<String> messageList = SmsManager.getDefault().divideMessage(messageInfo.message);
    ArrayList<PendingIntent> pendingIntents = new ArrayList<PendingIntent>(messageList.size());

    messageInfo.partsSent.clear();

    for (int i = 0; i < messageList.size(); i++) {
        messageInfo.partsSent.add(i, false);
        messageInfo.partsProcessed.add(i, false);

        Intent sentIntent = new Intent(SENT);
        sentIntent.putExtra(INTENT_EXTRA_MESSAGE_ID_KEY, Long.toString(messageInfo.messageId));
        sentIntent.putExtra(INTENT_EXTRA_PART_NR_KEY, Integer.toString(i));

        Log.i("SMSMessageSender", "Adding part " + i + " tp multi-part message Id: " + messageInfo.messageId);
        pendingIntents.add(PendingIntent.getBroadcast(context, 0, sentIntent,  PendingIntent.FLAG_ONE_SHOT));
    }

    Log.i("SMSMessageSender", "About to send multi-part message Id: " + messageInfo.messageId);
    SmsManager.getDefault().sendMultipartTextMessage(messageInfo.phoneNumber, null, messageList, pendingIntents, null);
}

The problem with this is that the second message part never gets received.

It seems strange to have to go into all the hassle of creating multiple PendingIntents only to not go and check that they all worked.

In the log message where I show the Message Part No, it is always 0, I never get the second part, therefore this code never thinks it gets completed.

Am I just making it too complicated, should I just take any old PendingIntent that comes back and assume the same applies to the rest (in which case why did Google make you supply a list of them in the first place).

I apologise for the long question, but didn't really know how to ask more clearly in a shorter fashion :-)

Regards Colin

Answer

user2400538 picture user2400538 · May 21, 2013

Here is the code I have ended up using, it is a mix of my original and that of Bolton (thanks Bolton :-)).

The new code may detect a failure of a part better, a bit hard to test, the code above sets anyError to false for each message part, so if part 1 failed and part 2 succeeded it might think that the whole thing succeeded... the code below calls (my) messageInfo.fail which won't get reset if a subsequent message part succeeds... I imagine this is all pretty unneeded as you would think that if one part works, the rest will too... anyhow, below is the code I ultimately used.

Edit > Updated the code to remove the Extras in the intent as under heavy load, multiple intents (I think) got merged into one (using different PendingIntent flags did not help). In the end, I used a different intent action for each message (e.g. new Intent(SENT + messageInfo.getMessageId())), this way the receiver definitely only gets broadcasts for its own message. Seems to work better under heavy load.

Thanks.

private void sendLongSmsMessage4(Context context, final SmsMessageInfo messageInfo) {

    // Receive when each part of the SMS has been sent
    BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // We need to make all the parts succeed before we say we have succeeded.
            switch (getResultCode()) {
            case Activity.RESULT_OK:
                break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                messageInfo.fail("Error - Generic failure");
                break;
            case SmsManager.RESULT_ERROR_NO_SERVICE:
                messageInfo.fail("Error - No Service");
                break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
                messageInfo.fail("Error - Null PDU");
                break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
                messageInfo.fail("Error - Radio off");
                break;
            }

            nMsgParts--;
            if (nMsgParts <= 0) {
                // Stop us from getting any other broadcasts (may be for other messages)
                Log.i(LOG_TAG, "All message part resoponses received, unregistering message Id: " + messageInfo.getMessageId());
                context.unregisterReceiver(this);

                if (messageInfo.isFailed()) {
                    Log.d(LOG_TAG, "SMS Failure for message id: " + messageInfo.getMessageId());
                } else {
                    Log.d(LOG_TAG, "SMS Success for message id: " + messageInfo.getMessageId());
                    messageInfo.setSent(true);
                }
            }
        }
    };

    context.registerReceiver(broadcastReceiver, new IntentFilter(SENT + messageInfo.getMessageId()));

    SmsManager smsManager = SmsManager.getDefault();

    ArrayList<String> messageParts = smsManager.divideMessage(messageInfo.getMessage());
    ArrayList<PendingIntent> pendingIntents = new ArrayList<PendingIntent>(messageParts.size());
    nMsgParts = messageParts.size();

    for (int i = 0; i < messageParts.size(); i++) {
        Intent sentIntent = new Intent(SENT + messageInfo.getMessageId());
        pendingIntents.add(PendingIntent.getBroadcast(context, 0, sentIntent, 0));
    }

    Log.i(LOG_TAG, "About to send multi-part message Id: " + messageInfo.getMessageId());
    smsManager.sendMultipartTextMessage(messageInfo.getPhoneNumber(), null, messageParts, pendingIntents, null);
}