Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make sure SMS is sent in Android

Tags:

android

In my application there will be a notification sms send upon an action. Now how do I make sure that the SMS will be sent?

For example, if there is no network or sim card removeed, how do I ensure that the sms will be sent once there is a network available at a later time? Is it possible to add the sms to the queue?

Is this possible?

Thanks In Advance, Perumal

like image 915
perumal316 Avatar asked Mar 14 '12 04:03

perumal316


People also ask

How do I know if SMS is delivered Android?

Now when you send a text message you can tap and hold the message and select “View message details“. On some models, it may be under “View report“. The statuses will show “Received“, “Delivered“, or may simply show the time of delivery. If the message was not delivered, it may show as “Pending” or “None” status.

How do you know if SMS text was delivered?

To enable delivery report on Android go to Settings > Text Message (SMS) Settings and turn on the Delivery Reports option. From that point on, your Android device will start receiving delivery reports for SMS messages, notifying you of the current state status of the text message.

Why are my SMS messages not sending?

If your Android won't send text messages, the first thing you should do is make sure you have a decent signal — without cell or Wi-Fi connectivity, those texts are going nowhere. A soft reset of an Android can usually fix an issue with outgoing texts, or you can also force a power cycle reset.

How do I enable SMS on Android?

In some versions of Android, this permission is turned on by default. In other versions, this permission is turned off by default. To set the app's permission on a device or emulator instance, choose Settings > Apps > SMS Messaging > Permissions, and turn on the SMS permission for the app.


1 Answers

You can capture the SMS sent/deliver status with a broadcast receiver. The BroadcastReceiver must be registered before you send the SMS. I use this implementation, along with a parent class, to get the result of the operation up to the SENT or DELIVERED status (must chose one of the two)

package com.mycie.myapp;

import java.util.ArrayList;
import java.util.logging.Level;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.SystemClock;
import android.telephony.SmsManager;
import android.widget.Toast;

extern class Sequencer;
extern class Test;
extern class Status;

/**
 * Sends multipart SMS to multiple destinations and verifies the send and
 * deliver status of each part sent away. The operation is a success if all the
 * codes of the SMS_SENT of all the messages sent are equal to
 * {@link Activity#RESULT_OK}.
 */
public class SendSms extends ModemDependentAtom {

    private static final String LOG_TAG = "SendSms";

    // set this flag to true to print many traces to follow the machine state.
    // NB: DEBUG level traces are always printed for the SEND and DELIVER status
    // of the part, so you don't need to activate this flag to get them.
    private static final boolean DEBUG = false;

    public static final String SMS_DELIVERY = "com.mycie.myapp.SMS_DELIVERY";

    public static final String SMS_SENT = "com.mycie.myapp.SMS_SENT";

    // infinite time-out
    public static final int TIMEOUT_INFINITE = -1;

    // timeout per part.
    private static final int DEFAULT_TIMEOUT = 10000;

    private String mSmsNumber;

    private String mSmsBody;

    private int mTimeout;

    private long mOrgRealtime;

    private SendSmsReceiver mSendReceiver;

    private SendSmsReceiver mDeliverReceiver;

    private int mNbParts;

    private int mDestIndex;

    private int mState;

    private String[] mNumbers;

    private ArrayList<String> mParts;

    private Status mStatus;

    public enum SmsSendNotification {
        SEND,
        DELIVER
    }

    private final SmsSendNotification mSignificantNotification;

    private ArrayList<PendingIntent> mSentIntents;

    private ArrayList<PendingIntent> mDeliveryIntents;

    public SendSms(SmsSendNotification significant) {
        mSignificantNotification = significant;

        mSmsNumber = "";
        mSmsBody = "";
        mTimeout = DEFAULT_TIMEOUT;
        mOrgRealtime = 0;
        mStatus = Status.NotDone;
        mState = 0;

        mSentIntents = new ArrayList<PendingIntent>();
        mDeliveryIntents = new ArrayList<PendingIntent>();
    }

    private final class SendCallBack implements SendSmsReceiver.Callback {

        SendCallBack(Sequencer sequencer, Test test) {
            mSequencer = sequencer;
            mTest = test;
        }

        private Sequencer mSequencer;
        private Test mTest;

        @Override
        public void onPartResult(int part, int code) {

            Log.d(LOG_TAG, String.format("SEND part %d: %s", part,
                    SendSmsReceiver.getCodeString(code)));

            Context context = Controller.controller.getApplication().getApplicationContext();

            if (mSendReceiver.findCode(SendSmsReceiver.RESULT_UNDEFINED) == -1) {
                // no more sent notification to receive.
                int globalCode = mSendReceiver.findNotCode(Activity.RESULT_OK);

                if (globalCode == -1) {
                    // all part were sent successfully.
                    globalCode = Activity.RESULT_OK;
                } else {
                    // at least one part failed to be sent.
                    mSequencer.onTestEvent(mTest, Level.SEVERE, Sequencer.TEST_EXCEPTION,
                            "one or more parts failed to be sent"); //$NON-NLS-1$

                    mTest.setStatus(Status.Error);
                    mStatus = Status.Executed;
                }

                Log.d(LOG_TAG, "SMS send result: " + SendSmsReceiver.getCodeString(globalCode));

                Toast.makeText(context,
                        "SEND: " + SendSmsReceiver.getCodeString(globalCode),
                        Toast.LENGTH_LONG).show();
            }
        }
    };

    private final class DeliverCallBack implements SendSmsReceiver.Callback {

        DeliverCallBack(Sequencer sequencer, Test test) {
            mSequencer = sequencer;
            mTest = test;
        }

        private Sequencer mSequencer;
        private Test mTest;

        @Override
        public void onPartResult(int part, int code) {

            Log.d(LOG_TAG, String.format("DELIVER part %d: %s", part,
                    SendSmsReceiver.getCodeString(code)));

            if (mDeliverReceiver.findCode(SendSmsReceiver.RESULT_UNDEFINED) == -1) {
                // no more delivery notification to receive.
                int globalCode = mDeliverReceiver.findNotCode(Activity.RESULT_OK);

                if (globalCode == -1) {
                    // all part were sent successfully.
                    globalCode = Activity.RESULT_OK;
                } else {
                    // at least one part failed to be sent.
                    mSequencer.onTestEvent(mTest, Level.SEVERE, Sequencer.TEST_EXCEPTION,
                            "one or more parts failed to be delivered"); //$NON-NLS-1$

                    mTest.setStatus(Status.Error);
                    mStatus = Status.Executed;
                }

                Context context = Controller.controller.getApplication().getApplicationContext();

                Log.d(LOG_TAG, "SMS deliver result: " + SendSmsReceiver.getCodeString(globalCode));

                Toast.makeText(context,
                        "DELIVER: " + SendSmsReceiver.getCodeString(globalCode),
                        Toast.LENGTH_LONG).show();

                // we can early examine the send results so do it.
                examine(mSequencer, mTest);
            }
        }
    };

    @Override
    public Status process(Sequencer sequencer, Test test) {

        if (mState == 0) {

            mNumbers = PhoneUtils.splitNumbers(mSmsNumber);

            PhoneUtils.parsePhoneNumbers(mNumbers, new PhoneUtils.PrivilegiateSms());

            AppData data = Controller.controller.getData();
            SmsManager smsManager = data.services.getSmsManager();
            mParts = smsManager.divideMessage(mSmsBody);
            mNbParts = mParts.size();

            Context context = Controller.controller.getApplication().getApplicationContext();
            mSendReceiver = SendSmsReceiver.getInstance(context,
                    sequencer.getPosition(), SMS_SENT, new SendCallBack(sequencer, test));
            mDeliverReceiver = SendSmsReceiver.getInstance(context,
                    sequencer.getPosition(), SMS_DELIVERY, new DeliverCallBack(sequencer, test));

            if (DEBUG) {
                Log.d(LOG_TAG, "broadcast receivers created.");
            }

            mDestIndex = 0;
            if (DEBUG) {
                Log.d(LOG_TAG, "state=1");
            }
            mState = 1;

            process(sequencer, test);

            mStatus = Status.Active;

        } else if (mState == 1) {

            if (mDestIndex < mNumbers.length) {
                Log.i(LOG_TAG, "Sending SMS to " + mNumbers[mDestIndex] + "...");
                Status result = sendMultipartText(sequencer, test, mNumbers[mDestIndex]);
                if (result == Status.Error) {
                    // error: fail to send to a receiver.
                    test.setStatus(Status.Error);

                    mStatus = Status.Executed;
                } else {
                    if (DEBUG) {
                        Log.d(LOG_TAG, "state=2");
                    }
                    mState = 2;
                }
            } else {
                // no more: completed.
                if (DEBUG) {
                    Log.d(LOG_TAG, "No more recipient. Test OK.");
                }

                mStatus = Status.Executed;
            }

        } else if (mState == 2) {
            if (mModemReset) {

                sequencer.onTestEvent(test, Level.SEVERE, Sequencer.EXTERNAL_ERROR,
                        "the test probably caused a modem reset"); //$NON-NLS-1$

                test.setStatus(Status.Error);
                mStatus = Status.Error;

                mModemReset = false;
            }
            // wait for status to change. Force examination if timeout expires.
            else if (isTimerExpired()) {
                if (DEBUG) {
                    Log.d(LOG_TAG, "timeout");
                }
                examine(sequencer, test);
            }
        } else {
            // what?
        }

        if (mStatus != Status.Active) {
            if (DEBUG) {
                Log.d(LOG_TAG, "status = " + mStatus);
            }
        } else {
            sequencer.wakeAfter(0);
        }

        return mStatus;
    }

    @Override
    public void onFinalRelease(Status unused) {
        super.onFinalRelease(unused);

        if (DEBUG) {
            Log.d(LOG_TAG, "broadcast receivers released.");
        }

        cancelPendingIntents();

        if (mSendReceiver != null) {
            mSendReceiver.discard();
            mSendReceiver = null;
        }

        if (mDeliverReceiver != null) {
            mDeliverReceiver.discard();
            mDeliverReceiver = null;
        }
    }

    @Override
    public void reset() {
        super.reset();

        mState = 0;
        if (DEBUG) {
            Log.d(LOG_TAG, "state=0");
        }

        mStatus = Status.NotDone;
    }

    public void examine(Sequencer sequencer, Test test) {

        // If a SENT result is missing then the test failed due to timeout. The
        // method should have been called by the timeout handler, if not this is
        // a programming error.
        if (mSendReceiver.findCode(SendSmsReceiver.RESULT_UNDEFINED) != -1) {

            sequencer.onTestEvent(test, Level.SEVERE, Sequencer.TEST_EXCEPTION,
                    "not all send results received before timeout"); //$NON-NLS-1$

            test.setStatus(Status.Error);
            mStatus = Status.Executed;
        }

        // If one or more SENT failed then the test failed.
        else if (mSendReceiver.findNotCode(Activity.RESULT_OK) != -1) {

            sequencer.onTestEvent(test, Level.SEVERE, Sequencer.TEST_EXCEPTION,
                    "failed to send at least one SMS part."); //$NON-NLS-1$

            test.setStatus(Status.Error);
            mStatus = Status.Executed;
        }

        // Otherwise, if DELIVER is the significant notification then the
        // test fails if there is one or more delivery failed.
        else if (mSignificantNotification.equals(SmsSendNotification.DELIVER)) {
            if (mDeliverReceiver.findCode(SendSmsReceiver.RESULT_UNDEFINED) != -1) {

                // failed due to timeout on DELIVERY
                sequencer.onTestEvent(test, Level.SEVERE, Sequencer.TEST_EXCEPTION,
                        "not all delivery results received before timeout"); //$NON-NLS-1$

                test.setStatus(Status.Error);
                mStatus = Status.Executed;

            } else if (mDeliverReceiver.findNotCode(Activity.RESULT_OK) != -1) {

                // at least on part delivery failed.
                sequencer.onTestEvent(test, Level.SEVERE, Sequencer.TEST_EXCEPTION,
                        "failed to deliver at least one SMS part."); //$NON-NLS-1$

                test.setStatus(Status.Error);
                mStatus = Status.Executed;

            } else {
                // OK, proceed with next number.
                if (DEBUG) {
                    Log.d(LOG_TAG, "send to next recipient, if any.");
                }
                mDestIndex ++;
                mState = 1;
            }
        } else {
            // SEND is significant and all SENT were OK: proceed with next number.
            if (DEBUG) {
                Log.d(LOG_TAG, "send to next receiver, if any.");
            }
            mDestIndex ++;
            mState = 1;
        }
    }

    private Status sendMultipartText(Sequencer sequencer, Test test,
            String number) {

        Intent sent;
        Intent delivery;
        PendingIntent sentIntent;
        PendingIntent deliveryIntent;

        AppData data = Controller.controller.getData();
        SmsManager smsManager = data.services.getSmsManager();
        Context context = Controller.controller.getApplication().getApplicationContext();

        if (mNbParts > 1) {
            Log.i(LOG_TAG, String.format("SMS is devided in %d parts.", mNbParts));
        }

        cancelPendingIntents();

        String serial = getNewSerial(number);

        int iPart;
        for (iPart = 0; iPart < mNbParts; iPart++) {
            Uri uri = new Uri.Builder()
                .scheme(String.format("pos%d", sequencer.getPosition()))
                .authority(serial)
                .appendQueryParameter("part", Integer.toString(iPart))
                .build();
            sent = new Intent(SMS_SENT, uri);

            sentIntent = PendingIntent.getBroadcast(context, 0, sent, 0);
            if (sentIntent == null) {
                sequencer.onTestEvent(test, Level.SEVERE, Sequencer.PROGRAM_ERROR, 
                        "null " + SMS_SENT + " intent"); //$NON-NLS-1$
                return Status.Error;
            }

            mSentIntents.add(sentIntent);

            delivery = new Intent(SMS_DELIVERY, uri);

            deliveryIntent = PendingIntent.getBroadcast(context, 0, delivery, 0);
            if (deliveryIntent == null) {
                sequencer.onTestEvent(test, Level.SEVERE, Sequencer.PROGRAM_ERROR, 
                        "null " + SMS_DELIVERY + " intent");
                return Status.Error;
            }

            mDeliveryIntents.add(deliveryIntent);
        }

        this.mOrgRealtime = SystemClock.elapsedRealtime();
        if (DEBUG) {
            Log.d(LOG_TAG, "timeout: " + getRealTimeout());
        }

        mSendReceiver.restart(serial, mNbParts);
        mDeliverReceiver.restart(serial, mNbParts);

        smsManager.sendMultipartTextMessage(number, null,
                mParts, mSentIntents, mDeliveryIntents);

        return Status.Executed;
    }

    private static String getNewSerial(String number) {
        String serial = number + "." + SystemClock.uptimeMillis();
        return serial;
    }

    private void cancelPendingIntents() {
        // cancel precedent pending intents.
        for (PendingIntent pi : mSentIntents) {
            pi.cancel();
        }
        mSentIntents.clear();

        for (PendingIntent pi : mDeliveryIntents) {
            pi.cancel();
        }
        mDeliveryIntents.clear();
    }

    private boolean isTimerExpired() {
        boolean expired = false;

        long timeout = getRealTimeout();
        if (timeout > 0) {
            if (mOrgRealtime <= 0) {
                mOrgRealtime = SystemClock.elapsedRealtime();
            } else {
                long elapsed = SystemClock.elapsedRealtime() - mOrgRealtime;
                if (elapsed > timeout) {
                    expired = true;
                }
            }
        }

        return expired;
    }

    private long getRealTimeout() {
        long timeout;

        if (mTimeout > 0) {
            if (mSignificantNotification.equals(SmsSendNotification.SEND)) {
                timeout = mTimeout * mNbParts;
            } else {
                // double the times if DELIVER is the significant status.
                timeout = 2 * mTimeout * mNbParts;
            }
        } else {
            timeout = TIMEOUT_INFINITE;
        }

        return timeout;
    }

    public void setTimeout(int timeout) {
        mTimeout = timeout;
    }

    public void setNumber(final String number) {
        mSmsNumber = PhoneUtils.normalizeList(number);
    }

    public String getNumber() {
        return mSmsNumber;
    }

    public void setBody(final String body) {
        mSmsBody = body;
    }

    public String getBody() {
        return mSmsBody;
    }

    /**
     * Send a text message to one or many receivers and forget about the
     * notifications.
     * @param number receiver(s) number(s), separated by a comma if more than one.
     * @param text to send
     */
    public static void sendMessage(String number, String text) {
        AppData data = Controller.controller.getData();
        SmsManager smsManager = data.services.getSmsManager();

        ArrayList<String> parts = smsManager.divideMessage(text);
        Log.d(LOG_TAG, String.format("sending SMS composed of %d parts.", parts.size())); //$NON-NLS-1$

        String[] array = PhoneUtils.splitNumbers(number);
        for (String n : array) {
            smsManager.sendMultipartTextMessage(n, null, parts, null, null);
        }
    }

}
like image 160
slash33 Avatar answered Oct 12 '22 10:10

slash33