SMS Content Providers and Delivery Reports

Hi there,

I am back again, after a busy development period that didn’t leave enough time to write a post. But still it helped me that I had taken notes of what I would like to post for!

As part of an Android project that I have been working on, was to implement an activity for sending quick SMS replies, with full support of delivery reports. The documentation was poor and the stackoverflow.com discussions can become confusing, when most of the people don’t have an official reference to base their answers on.
The fact that even Google developers and bloggers discourage the usage of Content Provider that are not documented, and may change between Android version or distributions without prior notice, made me mad (http://android-developers.blogspot.com/2010/05/be-careful-with-content-providers.html). There are so many applications on the market, trying to give a better experience with messaging on the Android Platform, whose stock SMS application looks very poor in functionality and easy of use. So, a question came to my mind: “How can a mobile platform owner decide to have a undocumented part of the Platform, when it comes to a so integral aspect of a mobile platform, the SMS?”. And what the platform owner decides to say is that this part of the platform is free to change, which would of course result in breaking all the applications that almost of the Android device user use in order to fill the gap of the poor stock SMS application.

I spent quit some time to implement the delivery reports reception and sms DB updates required for integrating well with the native or third-party SMS applications, where the SMS status that my application will be setting the SMS to will be one that will correctly describe the SMS status on the other SMS applications. So, I give you some of the code (of course its not all mine, I had to copy parts from online resources to end up with this code!) for senging SMS and handling the delivery reports.

public void sendMessage(String dest, String message) {

		SmsManager smsManager = SmsManager.getDefault();
		PendingIntent deliveryIntent = null, sentIntent = null;

		Uri smsUri = null;
		try {
			smsUri = addMessage(mContext.getContentResolver(), dest, mMessageText, null, mTimestamp, requestDeliveryReport, mThreadId);
		} catch (SQLiteException e) {
			// SqliteWrapper.checkSQLiteException(mContext, e);
		}

		if (requestDeliveryReport) {
			Intent delIntent = new Intent(ServiceSms.ACTION_MESSAGE_DELIVERY_STATUS);
			delIntent.putExtra(ServiceSms.EXTRA_SMS_URI, smsUri);
			deliveryIntent = PendingIntent.getBroadcast(mContext, 0, delIntent, 0);
		}
		Intent sIntent = new Intent(ServiceSms.ACTION_MESSAGE_SENT);
		sIntent.putExtra(ServiceSms.EXTRA_SMS_URI, smsUri);
		sentIntent = PendingIntent.getBroadcast(mContext, 0, sIntent, 0);

		smsManager.sendTextMessage(dest, mServiceCenter, message, sentIntent, deliveryIntent);

	}

public static Uri addMessage(ContentResolver resolver, String address, String body, Long date, long threadId) {
		
		final Uri CONTENT_URI = Uri.parse("content://sms/sent");
		ContentValues values = new ContentValues();

		values.put(ADDRESS, address);
		if (date != null) {
			values.put(DATE, date);
		}
		values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
		values.put(BODY, body);
		if (threadId != -1L) {
			values.put(THREAD_ID, threadId);
		}

		return resolver.insert(uri, values);
	}

The above was just for sending an SMS and specifying how to be notified about initial status of the SMS (sentIntents) or the final status of the SMS (deliveryIntents). But to successfully receive these callback, you will need to register a BroadcastReceiver for the ACTION_MESSAGE_SENT action and the ACTION_MESSAGE_DELIVERY_STATUS action (these are some final strings with the namespace of my app, that I have defined). Because the reception of these events might need to be handled with queries to the SMS DB, you ll need to pass the intent to a Service to actually handle the events:

public class ServiceSms extends Service {

private final class ServiceHandler extends Handler {
		public ServiceHandler(Looper looper) {
			super(looper);
		}

		@Override
		public void handleMessage(Message msg) {
			int serviceId = msg.arg1;
			Intent intent = (Intent) msg.obj;
			int resultCode = msg.arg2;
			String action = intent.getAction();
			String dataType = intent.getType();

			if (ACTION_MESSAGE_SENT.equals(action)) {
				handleSmsSent(intent, resultCode);
			} else if (ACTION_MESSAGE_DELIVERY_STATUS.equals(action)) {
				handleSmsDelivered(intent, resultCode);
			}
		}
}

private void handleSmsSent(Intent intent, int resultCode) {
		ContentValues values = null;
		Uri smsUri = null;
		if (intent != null && intent.getExtras() != null && intent.getExtras().getParcelable(EXTRA_SMS_URI) != null) {
			smsUri = (Uri) intent.getExtras().getParcelable(EXTRA_SMS_URI);
		}
		switch (resultCode) {
		case Activity.RESULT_OK:
			if (smsUri != null) {
				values = new ContentValues();

				SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
				boolean requestDeliveryReport = mPrefs.getBoolean(mContext.getString(R.string.pref_delivery_report_key), Boolean.valueOf(mContext.getString(R.string.pref_delivery_report_default)));
				if (requestDeliveryReport) {
					values.put(SmsMessageSender.STATUS, SmsMessageSender.STATUS_PENDING);
				} else {
					values.put(SmsMessageSender.STATUS, SmsMessageSender.STATUS_NONE);
				}

				getContentResolver().update(smsUri, values, null, null);
				mToastHandler.sendEmptyMessage(TOAST_MESSAGE_SENT);
			}
			break;
		case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
			if (smsUri != null) {
				values = new ContentValues();
				values.put(SmsMessageSender.STATUS, SmsMessageSender.STATUS_FAILED);
				getContentResolver().update(smsUri, values, null, null);
				mToastHandler.sendEmptyMessage(TOAST_MESSAGE_SENDING_FAILURE);
			}
			break;
		case SmsManager.RESULT_ERROR_NO_SERVICE:
			if (smsUri != null) {
				values = new ContentValues();
				values.put(SmsMessageSender.STATUS, SmsMessageSender.STATUS_FAILED);
				getContentResolver().update(smsUri, values, null, null);
				mToastHandler.sendEmptyMessage(TOAST_MESSAGE_SENDING_FAILURE);
			}
			break;
		case SmsManager.RESULT_ERROR_NULL_PDU:
			if (smsUri != null) {
				values = new ContentValues();
				values.put(SmsMessageSender.STATUS, SmsMessageSender.STATUS_FAILED);
				getContentResolver().update(smsUri, values, null, null);
				mToastHandler.sendEmptyMessage(TOAST_MESSAGE_SENDING_FAILURE);
			}
			break;
		case SmsManager.RESULT_ERROR_RADIO_OFF:
			if (smsUri != null) {
				values = new ContentValues();
				values.put(SmsMessageSender.STATUS, SmsMessageSender.STATUS_FAILED);
				getContentResolver().update(smsUri, values, null, null);
				mToastHandler.sendEmptyMessage(TOAST_MESSAGE_SENDING_FAILURE);
			}
			break;
		}
	}

	private void handleSmsDelivered(Intent intent, int resultCode) {
		ContentValues values = null;
		Uri smsUri = null;
		if (intent != null && intent.getExtras() != null && intent.getExtras().getParcelable(EXTRA_SMS_URI) != null) {
			smsUri = (Uri) intent.getExtras().getParcelable(EXTRA_SMS_URI);
		}

		switch (resultCode) {
		case Activity.RESULT_OK:
			if (smsUri != null) {
				values = new ContentValues();
				values.put(SmsMessageSender.STATUS, SmsMessageSender.STATUS_COMPLETE);
				getContentResolver().update(smsUri, values, null, null);
				mToastHandler.sendEmptyMessage(TOAST_MESSAGE_DELIVERED);
			}
			break;
		case Activity.RESULT_CANCELED:
			if (smsUri != null) {
				values = new ContentValues();
				values.put(SmsMessageSender.STATUS, SmsMessageSender.STATUS_FAILED);
				getContentResolver().update(smsUri, values, null, null);
				mToastHandler.sendEmptyMessage(TOAST_MESSAGE_DELIVERY_FAILURE);
			}
			break;
		}
	}

}

Hope these code samples will help you implement our own SMS sending functions with delivery reports support. But always remember that the API might change, resulting in your code being broken, so be ready for fast updates!

Good luck!

Advertisements

About sermojohn

Professional Software Engineer
This entry was posted in Android and tagged , , , , , . Bookmark the permalink.

2 Responses to SMS Content Providers and Delivery Reports

  1. chanusukarno says:

    Nice information, even i started implementing a custom messaging app. Can you please check my thread on SO(http://stackoverflow.com/questions/14545661/android-get-sms-from-inbox-optimized-way-to-read-all-messages-and-group-them), am not getting much assistance there. Hope you could help me. Thanks!

  2. Thank you so much, this help me alot.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s