Detect Multiple SMS sent due to length of content

358 views Asked by At

I have setup a background service to registerContentObserver to get notified whenever an SMS is sent. Upon receiving this event, I would increment a variable to know the count of messages sent. This is working as expected.

When someone sends SMS with more than 140 characters, the mobile carrier would treat this as multiple SMS, but it seems that I get only 1 callback for the sent message. This is causing my app to miss counting some messages.

Is there any proper way to know how many messages were actually sent?

1

There are 1 answers

8
Mike M. On BEST ANSWER

When an app is responsible for writing its own messages to the Provider, it's most likely going to write the whole message in one go, regardless of whether the message must be sent as multipart. This would be why your Observer is often firing only once for each complete message, no matter how big.

Since KitKat, the system will automatically save the outgoing messages for any non-default apps, and for multipart messages, each part will be saved individually, firing your Observer each time. Of course, this doesn't help for anything prior to KitKat, or if a default app saves its own messages on later versions.

One possibility is to fetch the message body in your ContentObserver, and determine how many message parts it would've been split into. The SmsMessage.calculateLength() method can do this for us. It returns an int array, the first element of which will have the message count for the given text.

For example, using the old onChange(boolean) method, to support API < 16:

private class SmsObserver extends ContentObserver {
    private static final Uri SMS_SENT_URI = Uri.parse("content://sms/sent");
    private static final String COLUMN_ID = "_id";
    private static final String COLUMN_BODY = "body";
    private static final String[] PROJECTION = {COLUMN_ID, COLUMN_BODY};

    // You might want to persist this value to storage, rather than
    // keeping a field, in case the Observer is killed and recreated.
    private int lastId;

    public SmsObserver(Handler handler) {
        super(handler);
    }

    @Override
    public void onChange(boolean selfChange) {
        Cursor c = null;
        try {
            // Get the most recent sent message.
            c = getContentResolver().query(SMS_SENT_URI, PROJECTION, null,
                                           null, "date DESC LIMIT 1");
            if (c != null && c.moveToFirst()) {

                // Check that we've not already counted this one.
                final int id = c.getInt(c.getColumnIndex(COLUMN_ID));
                if (id == lastId) {
                    return;
                }
                lastId = id;

                // Get the message body, and have the SmsMessage
                // class calculate how many parts it would need.
                final String body = c.getString(c.getColumnIndex(COLUMN_BODY));
                final int numParts = SmsMessage.calculateLength(body, false)[0];

                // Add the number of parts to the count,
                // however you might be doing that.
                addToCount(numParts);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (c != null) {
                c.close();
            }
        }
    }
}

Should you be supporting API 16 and above, we can use the onChange(boolean, Uri) overload, and things get a little simpler, since we don't necessarily need to keep track of the last message ID.

@Override
public void onChange(boolean selfChange, Uri uri) {
    Cursor c = null;
    try {
        // type=2 restricts the query to the sent box, so this just
        // won't return any records if the Uri isn't for a sent message.
        c = getContentResolver().query(uri, PROJECTION, "type=2", null, null);
        if (c != null && c.moveToFirst()) {
            final String body = c.getString(c.getColumnIndex(COLUMN_BODY));
            final int numParts = SmsMessage.calculateLength(body, false)[0];

            addToCount(numParts);
        }
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    finally {
        if (c != null) {
            c.close();
        }
    }
}