Sending SMS using the Android SDK is simple enough, right? Get a hold of an SmsManager instance and call sendTextMessage. That does indeed send a message but:

  • only works when the phone has reception
  • is not multipart-aware (for longer messages)
  • does not add the message to content://sms/inbox or content://sms/sent
  • gives no UI feedback whatsoever
  • includes no retry or recover scenarios

It is obviously possible to implement all of the above using the standard Android SDK but that requires a truckload of cumbersome PendingIntents, BroadcastReceivers and getResultCode()s that make it unpleasant and error prone.

This might shock you but the standard Android Messaging application happens to implement all of it and more! When you look through the source code and find things like a TransactionService or a RetryScheduler among others, you realize that sending SMS might be a little tricker to get right afterall.

public class Sender {
  
  private Context context;
  private Method getOrCreateThreadIdMethod;
  private Method sendMessageMethod;
  private Constructor<?> smsMessageSenderConstructor;
  
  public Sender(Context ctx) throws Exception {
    this.context = ctx;
    getOrCreateThreadIdMethod = 
      Class.forName("android.provider.Telephony$Threads")
      .getMethod("getOrCreateThreadId", Context.class, String.class);
    ClassLoader classLoader = new PathClassLoader(
      "/system/app/Mms.apk", ctx.getClassLoader());
    Class<?> smsMessageSenderClass = classLoader
      .loadClass("com.android.mms.transaction.SmsMessageSender");
    smsMessageSenderConstructor = smsMessageSenderClass
      .getConstructor(Context.class, String[].class, String.class, long.class);
    sendMessageMethod = smsMessageSenderClass.getMethod("sendMessage", long.class);
  }
  
  public void send(String dest, String msgText) throws Exception {
    long threadId = (Long) getOrCreateThreadIdMethod.invoke(null, context, dest);
    Object smsMessageSender = smsMessageSenderConstructor.newInstance(
      context, new String[] { dest }, msgText, threadId);
    sendMessageMethod.invoke(smsMessageSender, threadId);
  }
}