9

I tried to send an sms via an Intent with this code:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("smsto:" + phoneNumber));
intent.putExtra("address", phoneNumber);
intent.putExtra("sms_body", messageBody);
intent.putExtra("exit_on_sent", true);
startActivityForResult(intent, CODE);

Then, I want to know if the SMS has been sent or not and I use this code:

public void onActivityResult(int requestCode, int resultCode, Intent intent) {

    switch (requestCode) {

        case CODE:
        if (resultCode == Activity.RESULT_OK)
        {
            //Then do...
        }
        elseif(resultCode == Activity.RESULT_CANCELED)
        {
            // Do...
        }
        break;    
    }
}

The thing is the result is always 0 (Activity.RESULT_CANCELED), even when the SMS has been sent. How can I know if the SMS has been sent or not ? I want to use the SMS default app of the phone, not create an interface that sends SMS.

jbiral
  • 1,431
  • 11
  • 16
  • Yeah actually you need a receiver to monitor the status of the message! – Pavlos Sep 28 '14 at 23:09
  • http://mobiforge.com/design-development/sms-messaging-android – Pavlos Sep 28 '14 at 23:09
  • Using a BroadcastReceiver as shown in the link @Pavlos provided will only work if your app is sending the SMS itself. It will not work if you use an Intent to start another app to send the message. You can accomplish what you want to do using a `ContentObserver`, however. – Mike M. Sep 29 '14 at 00:20
  • Υeah that could work too! Didnt know what the OP exactly wants – Pavlos Sep 29 '14 at 01:04
  • Thank you for your responses. I want to uses an Intent to start the default SMS app to send the message, so the ContentObserver may be the solution. Thanks again ! – jbiral Sep 29 '14 at 01:55

1 Answers1

11

In the following example, we use a ContentObserver to monitor updates to the SMS Provider. This Observer is created and started before the SMS Intent is fired, and checks the Provider changes against the destination address. The Activity that creates the Observer must implement the SmsSendObserver.SmsSendListener interface to receive the callback.

The Observer's constructor includes a timeout parameter (in milliseconds) to allow the Observer to be properly unregistered if the message is not sent after a reasonable amount of time. This can be set to NO_TIMEOUT if desired. However, the class, as written, is meant for "one shot" use, and it will unregister itself and nullify members upon callback. The stop() method can be used to clean up if no callback occurs. In either case, the instance is no longer usable, and any reference to it should be set to null.

Example Activity:

public class MainActivity extends Activity
    implements SmsSendObserver.SmsSendListener {
    ...

    private void sendMessage(String phoneNumber, String messageBody) {
        // This example has a timeout set to 15 seconds
        new SmsSendObserver(this, phoneNumber, 15000).start();

        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("smsto:" + phoneNumber));
        intent.putExtra("address", phoneNumber);
        intent.putExtra("sms_body", messageBody);
        intent.putExtra("exit_on_sent", true);
        startActivity(intent);
    }

    public void onSmsSendEvent(boolean sent) {
        Toast.makeText(this, sent ? "Message was sent" : "Timed out",
                       Toast.LENGTH_SHORT).show();
    }
}

The SmsSendObserver class:

public class SmsSendObserver extends ContentObserver {
    public static final int NO_TIMEOUT = -1;

    private static final Handler handler = new Handler();
    private static final Uri uri = Uri.parse("content://sms/"); 

    private static final String COLUMN_ADDRESS = "address";
    private static final String COLUMN_TYPE = "type";
    private static final String[] PROJECTION = { COLUMN_ADDRESS, COLUMN_TYPE };
    private static final int MESSAGE_TYPE_SENT = 2;

    private Context context = null;
    private ContentResolver resolver = null;

    private String phoneNumber = null;
    private long timeout = NO_TIMEOUT;
    private boolean wasSent = false;
    private boolean timedOut = false;

    public SmsSendObserver(Context context, String phoneNumber, long timeout) {
        super(handler);

        if (context instanceof SmsSendListener) {       
            this.context = context;
            this.resolver = context.getContentResolver();
            this.phoneNumber = phoneNumber;
            this.timeout = timeout;
        }
        else {
            throw new IllegalArgumentException(
                "Context must implement SmsSendListener interface");
        }
    }

    private Runnable runOut = new Runnable() {
        @Override
        public void run() {
            if (!wasSent) {
                timedOut = true;
                callBack();
            }
        }
    };

    public void start() {
        if (resolver != null) {
            resolver.registerContentObserver(uri, true, this);

            if (timeout > NO_TIMEOUT) {
                handler.postDelayed(runOut, timeout);
            }
        }
        else {
            throw new IllegalStateException(
                "Current SmsSendObserver instance is invalid");
        }
    }

    public void stop() {
        if (resolver != null) {
            resolver.unregisterContentObserver(this);

            resolver = null;
            context = null;
        }
    }

    private void callBack() {
        ((SmsSendListener) context).onSmsSendEvent(wasSent);
        stop();
    }

    @Override
    public void onChange(boolean selfChange) {
        if (wasSent || timedOut)
            return;

        Cursor cursor = null;

        try {
            cursor = resolver.query(uri, PROJECTION, null, null, null);

            if (cursor != null && cursor.moveToFirst()) {
                final String address =
                    cursor.getString(cursor.getColumnIndex(COLUMN_ADDRESS));
                final int type =
                    cursor.getInt(cursor.getColumnIndex(COLUMN_TYPE));

                if (PhoneNumberUtils.compare(address, phoneNumber) &&
                        type == MESSAGE_TYPE_SENT) {

                    wasSent = true;
                    callBack();
                }
            }
        }
        finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    public interface SmsSendListener {
        // Passes true if the message was sent
        // Passes false if timed out
        public void onSmsSendEvent(boolean sent);
    }
}
Mike M.
  • 38,532
  • 8
  • 99
  • 95
  • i am not able to call it in fragment can you help me ? – MilapTank Jul 18 '17 at 07:22
  • @Milap I don't really recommend using this exact solution. I've been meaning to update this for a while, but I just keep forgetting. The `SmsSendObserver` class is still perfectly usable as is, but it's quite possible that the initiating `Activity` instance - even the one hosting your `Fragment` - will be killed before the callback fires. I would suggest using a `Service` - possibly a foreground `Service` - to initiate the Observer and receive the callback, and get the result to the `Activity` in some other fashion; e.g., an event bus, saving/retrieving the result via persistent storage, etc. – Mike M. Aug 05 '17 at 08:16
  • 2
    I think this code is not usable anymore usable regarding the new SMS policy changes. https://android-developers.googleblog.com/2019/01/reminder-smscall-log-policy-changes.html – Sinan Dizdarević Feb 12 '19 at 10:54
  • 1
    This code is still perfectly usable, provided you have the required permission. If you distribute your app on the Play Store, and are unable to get an exemption from Google for the `READ_SMS` permission, then no solution is going to fully work for you; not just this one. Google's policies, however, are a completely separate issue, and were not meant to be addressed or considered in this answer (which is more than 4 years old, btw). – Mike M. Feb 20 '19 at 19:45
  • I said you can't use this code regarding new SMS policy. If you want to publish your app on Play Store you will be rejected. If core of your project is an default sms application you will be able to use this permission and some exceptions. But if you want to develop default sms application you will not open screen of the another sms app. In this moment to send message from your app you can open default sms application and send params. I didn't find solution to send mesaage using default app and to get response without using READ_SMS permission. – Sinan Dizdarević Mar 07 '19 at 19:18