Bug 674725 - Part U - Implement WebSMS delivered event for the Android backend. r=cjones
authorMounir Lamouri <mounir.lamouri@gmail.com>
Fri, 13 Jan 2012 14:26:24 +0100
changeset 85931 33d6c9206d5e8aab8c10f08296a35c9ede97bffa
parent 85930 12357c16d0fa4e312826b6ed05aa410c3f266f53
child 85932 d1847657d5d83b3d9ab859a785b5b3ccb192715e
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones
bugs674725
milestone12.0a1
Bug 674725 - Part U - Implement WebSMS delivered event for the Android backend. r=cjones
embedding/android/GeckoApp.java
embedding/android/GeckoAppShell.java
embedding/android/GeckoSmsManager.java
mozglue/android/APKOpen.cpp
widget/android/AndroidBridge.cpp
widget/android/AndroidJNI.cpp
--- a/embedding/android/GeckoApp.java
+++ b/embedding/android/GeckoApp.java
@@ -412,16 +412,17 @@ abstract public class GeckoApp
         IntentFilter batteryFilter = new IntentFilter();
         batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
         mBatteryReceiver = new GeckoBatteryManager();
         registerReceiver(mBatteryReceiver, batteryFilter);
 
         IntentFilter smsFilter = new IntentFilter();
         smsFilter.addAction(GeckoSmsManager.ACTION_SMS_RECEIVED);
         smsFilter.addAction(GeckoSmsManager.ACTION_SMS_SENT);
+        smsFilter.addAction(GeckoSmsManager.ACTION_SMS_DELIVERED);
         mSmsReceiver = new GeckoSmsManager();
         registerReceiver(mSmsReceiver, smsFilter);
 
         if (!checkAndSetLaunchState(LaunchState.PreLaunch,
                                     LaunchState.Launching))
             return;
 
         checkAndLaunchUpdate();
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -117,17 +117,19 @@ public class GeckoAppShell
     public static native void onChangeNetworkLinkStatus(String status);
     public static native void reportJavaCrash(String stack);
 
     public static native void processNextNativeEvent();
 
     public static native void notifyBatteryChange(double aLevel, boolean aCharging, double aRemainingTime);
 
     public static native void notifySmsReceived(String aSender, String aBody, long aTimestamp);
-    public static native void onSmsSent(String aReceiver, String aBody, long aTimestamp);
+    public static native int  saveMessageInSentbox(String aReceiver, String aBody, long aTimestamp);
+    public static native void notifySmsSent(int aId, String aReceiver, String aBody, long aTimestamp);
+    public static native void notifySmsDelivered(int aId, String aReceiver, String aBody, long aTimestamp);
 
     // A looper thread, accessed by GeckoAppShell.getHandler
     private static class LooperThread extends Thread {
         public SynchronousQueue<Handler> mHandlerQueue =
             new SynchronousQueue<Handler>();
         
         public void run() {
             Looper.prepare();
--- a/embedding/android/GeckoSmsManager.java
+++ b/embedding/android/GeckoSmsManager.java
@@ -72,40 +72,87 @@ class PendingIntentUID
 }
 
 /**
  * The envelope class contains all information that are needed to keep track of
  * a sent SMS.
  */
 class Envelope
 {
-  protected int     mId;
-  protected int     mRemainingParts;
-  protected boolean mFailing;
+  enum SubParts {
+    SENT_PART,
+    DELIVERED_PART
+  }
+
+  protected int       mId;
+  protected int       mMessageId;
+  protected long      mMessageTimestamp;
+
+  /**
+   * Number of sent/delivered remaining parts.
+   * @note The array has much slots as SubParts items.
+   */
+  protected int[]     mRemainingParts;
+
+  /**
+   * Whether sending/delivering is currently failing.
+   * @note The array has much slots as SubParts items.
+   */
+  protected boolean[] mFailing;
 
   public Envelope(int aId, int aParts) {
     mId = aId;
-    mRemainingParts = aParts;
-    mFailing = false;
+    mMessageId = -1;
+    mMessageTimestamp = 0;
+
+    int size = Envelope.SubParts.values().length;
+    mRemainingParts = new int[size];
+    mFailing = new boolean[size];
+
+    for (int i=0; i<size; ++i) {
+      mRemainingParts[i] = aParts;
+      mFailing[i] = false;
+    }
   }
 
-  public void decreaseRemainingParts() {
-    --mRemainingParts;
+  public void decreaseRemainingParts(Envelope.SubParts aType) {
+    --mRemainingParts[aType.ordinal()];
+
+    if (mRemainingParts[SubParts.SENT_PART.ordinal()] >
+        mRemainingParts[SubParts.DELIVERED_PART.ordinal()]) {
+      Log.e("GeckoSmsManager", "Delivered more parts than we sent!?");
+    }
   }
 
-  public boolean arePartsRemaining() {
-    return mRemainingParts != 0;
+  public boolean arePartsRemaining(Envelope.SubParts aType) {
+    return mRemainingParts[aType.ordinal()] != 0;
+  }
+
+  public void markAsFailed(Envelope.SubParts aType) {
+    mFailing[aType.ordinal()] = true;
+  }
+
+  public boolean isFailing(Envelope.SubParts aType) {
+    return mFailing[aType.ordinal()];
   }
 
-  public void markAsFailed() {
-    mFailing = true;
+  public int getMessageId() {
+    return mMessageId;
   }
 
-  public boolean isFailing() {
-    return mFailing;
+  public void setMessageId(int aMessageId) {
+    mMessageId = aMessageId;
+  }
+
+  public long getMessageTimestamp() {
+    return mMessageTimestamp;
+  }
+
+  public void setMessageTimestamp(long aMessageTimestamp) {
+    mMessageTimestamp = aMessageTimestamp;
   }
 }
 
 /**
  * Postman class is a singleton that manages Envelope instances.
  */
 class Postman
 {
@@ -163,18 +210,20 @@ class Postman
       Log.e("GeckoSmsManager", "Trying to destroy an empty Envelope!");
     }
   }
 }
 
 public class GeckoSmsManager
   extends BroadcastReceiver
 {
-  public final static String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
-  public final static String ACTION_SMS_SENT     = "org.mozilla.gecko.SMS_SENT";
+  public final static String ACTION_SMS_RECEIVED  = "android.provider.Telephony.SMS_RECEIVED";
+  public final static String ACTION_SMS_SENT      = "org.mozilla.gecko.SMS_SENT";
+  public final static String ACTION_SMS_DELIVERED = "org.mozilla.gecko.SMS_DELIVERED";
+
   private final static int kMaxMessageSize = 160;
 
   @Override
   public void onReceive(Context context, Intent intent) {
     if (intent.getAction().equals(ACTION_SMS_RECEIVED)) {
       // TODO: Try to find the receiver number to be able to populate
       //       SmsMessage.receiver.
       // TODO: Get the id and the date from the stock app saved message.
@@ -195,52 +244,88 @@ public class GeckoSmsManager
         GeckoAppShell.notifySmsReceived(msg.getDisplayOriginatingAddress(),
                                         msg.getDisplayMessageBody(),
                                         System.currentTimeMillis());
       }
 
       return;
     }
 
-    if (intent.getAction().equals(ACTION_SMS_SENT)) {
+    if (intent.getAction().equals(ACTION_SMS_SENT) ||
+        intent.getAction().equals(ACTION_SMS_DELIVERED)) {
       Bundle bundle = intent.getExtras();
 
       if (bundle == null || !bundle.containsKey("envelopeId") ||
           !bundle.containsKey("number") || !bundle.containsKey("message")) {
         Log.e("GeckoSmsManager", "Got an invalid ACTION_SMS_SENT!");
         return;
       }
 
       int envelopeId = bundle.getInt("envelopeId");
       Postman postman = Postman.getInstance();
-      Envelope envelope = postman.getEnvelope(envelopeId);
 
-      envelope.decreaseRemainingParts();
+      Envelope envelope = postman.getEnvelope(envelopeId);
+      if (envelope == null) {
+        Log.e("GeckoSmsManager", "Got an invalid envelope id (or Envelope has been destroyed)!");
+        return;
+      }
+
+      Envelope.SubParts part = intent.getAction().equals(ACTION_SMS_SENT)
+                                 ? Envelope.SubParts.SENT_PART
+                                 : Envelope.SubParts.DELIVERED_PART;
+      envelope.decreaseRemainingParts(part);
+ 
 
       if (getResultCode() != Activity.RESULT_OK) {
         // TODO: manage error types.
         Log.i("GeckoSmsManager", "SMS part sending failed!");
-        envelope.markAsFailed();
+        envelope.markAsFailed(part);
       }
 
-      if (envelope.arePartsRemaining()) {
+      if (envelope.arePartsRemaining(part)) {
         return;
       }
 
-      if (envelope.isFailing()) {
-        // TODO: inform about the send failure.
-        Log.i("GeckoSmsManager", "SMS sending failed!");
+      if (envelope.isFailing(part)) {
+        if (part == Envelope.SubParts.SENT_PART) {
+          // TODO: inform about the send failure.
+          Log.i("GeckoSmsManager", "SMS sending failed!");
+        } else {
+          // It seems unlikely to get a result code for a failure to deliver.
+          // Even if, we don't want to do anything with this.
+          Log.e("GeckoSmsManager", "SMS failed to be delivered... is that even possible?");
+        }
       } else {
-        GeckoAppShell.onSmsSent(bundle.getString("number"),
-                                bundle.getString("message"),
-                                System.currentTimeMillis());
-        Log.i("GeckoSmsManager", "SMS sending was successfull!");
+        if (part == Envelope.SubParts.SENT_PART) {
+          String number = bundle.getString("number");
+          String message = bundle.getString("message");
+          long timestamp = System.currentTimeMillis();
+
+          int id = GeckoAppShell.saveMessageInSentbox(number, message, timestamp);
+
+          GeckoAppShell.notifySmsSent(id, number, message, timestamp);
+
+          envelope.setMessageId(id);
+          envelope.setMessageTimestamp(timestamp);
+
+          Log.i("GeckoSmsManager", "SMS sending was successfull!");
+        } else {
+          GeckoAppShell.notifySmsDelivered(envelope.getMessageId(),
+                                           bundle.getString("number"),
+                                           bundle.getString("message"),
+                                           envelope.getMessageTimestamp());
+          Log.i("GeckoSmsManager", "SMS succesfully delivered!");
+        }
       }
 
-      postman.destroyEnvelope(envelopeId);
+      // Destroy the envelope object only if the SMS has been sent and delivered.
+      if (!envelope.arePartsRemaining(Envelope.SubParts.SENT_PART) &&
+          !envelope.arePartsRemaining(Envelope.SubParts.DELIVERED_PART)) {
+        postman.destroyEnvelope(envelopeId);
+      }
 
       return;
     }
   }
 
   public static int getNumberOfMessagesForText(String aText) {
     return SmsManager.getDefault().divideMessage(aText).size();
   }
@@ -252,25 +337,28 @@ public class GeckoSmsManager
      * delivered messages.
      */
     int envelopeId = Postman.kUnknownEnvelopeId;
 
     try {
       SmsManager sm = SmsManager.getDefault();
 
       Intent sentIntent = new Intent(ACTION_SMS_SENT);
+      Intent deliveredIntent = new Intent(ACTION_SMS_DELIVERED);
+
       Bundle bundle = new Bundle();
       bundle.putString("number", aNumber);
       bundle.putString("message", aMessage);
 
       if (aMessage.length() <= kMaxMessageSize) {
         envelopeId = Postman.getInstance().createEnvelope(1);
+        bundle.putInt("envelopeId", envelopeId);
 
-        bundle.putInt("envelopeId", envelopeId);
         sentIntent.putExtras(bundle);
+        deliveredIntent.putExtras(bundle);
 
         /*
          * There are a few things to know about getBroadcast and pending intents:
          * - the pending intents are in a shared pool maintained by the system;
          * - each pending intent is identified by a token;
          * - when a new pending intent is created, if it has the same token as
          *   another intent in the pool, one of them has to be removed.
          *
@@ -278,36 +366,52 @@ public class GeckoSmsManager
          * unique id to all pending intents we are creating. This unique id is
          * generated by GetPendingIntentUID().
          */
         PendingIntent sentPendingIntent =
           PendingIntent.getBroadcast(GeckoApp.surfaceView.getContext(),
                                      PendingIntentUID.generate(), sentIntent,
                                      PendingIntent.FLAG_CANCEL_CURRENT);
 
-        sm.sendTextMessage(aNumber, "", aMessage, sentPendingIntent, null);
+        PendingIntent deliveredPendingIntent =
+          PendingIntent.getBroadcast(GeckoApp.surfaceView.getContext(),
+                                     PendingIntentUID.generate(), deliveredIntent,
+                                     PendingIntent.FLAG_CANCEL_CURRENT);
+
+        sm.sendTextMessage(aNumber, "", aMessage,
+                           sentPendingIntent, deliveredPendingIntent);
       } else {
         ArrayList<String> parts = sm.divideMessage(aMessage);
         envelopeId = Postman.getInstance().createEnvelope(parts.size());
+        bundle.putInt("envelopeId", envelopeId);
 
-        bundle.putInt("envelopeId", envelopeId);
         sentIntent.putExtras(bundle);
+        deliveredIntent.putExtras(bundle);
 
         ArrayList<PendingIntent> sentPendingIntents =
           new ArrayList<PendingIntent>(parts.size());
+        ArrayList<PendingIntent> deliveredPendingIntents =
+          new ArrayList<PendingIntent>(parts.size());
 
         for (int i=0; i<parts.size(); ++i) {
           sentPendingIntents.add(
             PendingIntent.getBroadcast(GeckoApp.surfaceView.getContext(),
                                        PendingIntentUID.generate(), sentIntent,
                                        PendingIntent.FLAG_CANCEL_CURRENT)
           );
+
+          deliveredPendingIntents.add(
+            PendingIntent.getBroadcast(GeckoApp.surfaceView.getContext(),
+                                       PendingIntentUID.generate(), deliveredIntent,
+                                       PendingIntent.FLAG_CANCEL_CURRENT)
+          );
         }
 
-        sm.sendMultipartTextMessage(aNumber, "", parts, sentPendingIntents, null);
+        sm.sendMultipartTextMessage(aNumber, "", parts, sentPendingIntents,
+                                    deliveredPendingIntents);
       }
     } catch (Exception e) {
       Log.e("GeckoSmsManager", "Failed to send an SMS: ", e);
 
       if (envelopeId != Postman.kUnknownEnvelopeId) {
         Postman.getInstance().destroyEnvelope(envelopeId);
       }
     }
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -175,16 +175,34 @@ Java_org_mozilla_gecko_GeckoAppShell_ ##
 typedef return_type (*name ## _t)(JNIEnv *, jclass, type1 one, type2 two, type3 three); \
 static name ## _t f_ ## name; \
 extern "C" NS_EXPORT return_type JNICALL \
 Java_org_mozilla_gecko_GeckoAppShell_ ## name(JNIEnv *jenv, jclass jc, type1 one, type2 two, type3 three) \
 { \
   return f_ ## name(jenv, jc, one, two, three); \
 }
 
+#define SHELL_WRAPPER4(name,type1,type2,type3,type4) \
+typedef void (*name ## _t)(JNIEnv *, jclass, type1 one, type2 two, type3 three, type4 four); \
+static name ## _t f_ ## name; \
+extern "C" NS_EXPORT void JNICALL \
+Java_org_mozilla_gecko_GeckoAppShell_ ## name(JNIEnv *jenv, jclass jc, type1 one, type2 two, type3 three, type4 four) \
+{ \
+  f_ ## name(jenv, jc, one, two, three, four); \
+}
+
+#define SHELL_WRAPPER4_WITH_RETURN(name, return_type, type1, type2, type3, type4) \
+typedef return_type (*name ## _t)(JNIEnv *, jclass, type1 one, type2 two, type3 three, type4 four); \
+static name ## _t f_ ## name; \
+extern "C" NS_EXPORT return_type JNICALL \
+Java_org_mozilla_gecko_GeckoAppShell_ ## name(JNIEnv *jenv, jclass jc, type1 one, type2 two, type3 three, type4 four) \
+{ \
+  return f_ ## name(jenv, jc, one, two, three, four); \
+}
+
 SHELL_WRAPPER0(nativeInit)
 SHELL_WRAPPER1(nativeRun, jstring)
 SHELL_WRAPPER1(notifyGeckoOfEvent, jobject)
 SHELL_WRAPPER0(processNextNativeEvent)
 SHELL_WRAPPER1(setSurfaceView, jobject)
 SHELL_WRAPPER1(setSoftwareLayerClient, jobject)
 SHELL_WRAPPER0(onResume)
 SHELL_WRAPPER0(onLowMemory)
@@ -193,17 +211,19 @@ SHELL_WRAPPER1(removeObserver, jstring)
 SHELL_WRAPPER1(onChangeNetworkLinkStatus, jstring)
 SHELL_WRAPPER1(reportJavaCrash, jstring)
 SHELL_WRAPPER0(executeNextRunnable)
 SHELL_WRAPPER1(cameraCallbackBridge, jbyteArray)
 SHELL_WRAPPER3(notifyBatteryChange, jdouble, jboolean, jdouble);
 SHELL_WRAPPER3(notifySmsReceived, jstring, jstring, jlong);
 SHELL_WRAPPER0(bindWidgetTexture);
 SHELL_WRAPPER0_WITH_RETURN(testDirectTexture, bool);
-SHELL_WRAPPER3(onSmsSent, jstring, jstring, jlong);
+SHELL_WRAPPER3_WITH_RETURN(saveMessageInSentbox, jint, jstring, jstring, jlong);
+SHELL_WRAPPER4(notifySmsSent, jint, jstring, jstring, jlong);
+SHELL_WRAPPER4(notifySmsDelivered, jint, jstring, jstring, jlong);
 
 static void * xul_handle = NULL;
 static time_t apk_mtime = 0;
 #ifdef DEBUG
 extern "C" int extractLibs = 1;
 #else
 extern "C" int extractLibs = 0;
 #endif
@@ -581,17 +601,19 @@ loadLibs(const char *apkName)
   GETFUNC(onChangeNetworkLinkStatus);
   GETFUNC(reportJavaCrash);
   GETFUNC(executeNextRunnable);
   GETFUNC(cameraCallbackBridge);
   GETFUNC(notifyBatteryChange);
   GETFUNC(notifySmsReceived);
   GETFUNC(bindWidgetTexture);
   GETFUNC(testDirectTexture);
-  GETFUNC(onSmsSent);
+  GETFUNC(saveMessageInSentbox);
+  GETFUNC(notifySmsSent);
+  GETFUNC(notifySmsDelivered);
 #undef GETFUNC
   sStartupTimeline = (uint64_t *)__wrap_dlsym(xul_handle, "_ZN7mozilla15StartupTimeline16sStartupTimelineE");
   gettimeofday(&t1, 0);
   struct rusage usage2;
   getrusage(RUSAGE_THREAD, &usage2);
   __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loaded libs in %dms total, %dms user, %dms system, %d faults",
                       (t1.tv_sec - t0.tv_sec)*1000 + (t1.tv_usec - t0.tv_usec)/1000, 
                       (usage2.ru_utime.tv_sec - usage1.ru_utime.tv_sec)*1000 + (usage2.ru_utime.tv_usec - usage1.ru_utime.tv_usec)/1000,
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1355,17 +1355,17 @@ AndroidBridge::SendMessage(const nsAStri
 }
 
 PRInt32
 AndroidBridge::SaveSentMessage(const nsAString& aRecipient,
                                 const nsAString& aBody, PRUint64 aDate)
 {
     ALOG_BRIDGE("AndroidBridge::SaveSentMessage");
 
-    AutoLocalJNIFrame jniFrame;
+    AutoLocalJNIFrame jniFrame(GetJNIForThread());
     jstring jRecipient = GetJNIForThread()->NewString(PromiseFlatString(aRecipient).get(), aRecipient.Length());
     jstring jBody = GetJNIForThread()->NewString(PromiseFlatString(aBody).get(), aBody.Length());
     return GetJNIForThread()->CallStaticIntMethod(mGeckoAppShellClass, jSaveSentMessage, jRecipient, jBody, aDate);
 }
 
 void *
 AndroidBridge::LockBitmap(jobject bitmap)
 {
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -81,17 +81,19 @@ extern "C" {
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_callObserver(JNIEnv *, jclass, jstring observerKey, jstring topic, jstring data);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_removeObserver(JNIEnv *jenv, jclass, jstring jObserverKey);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onChangeNetworkLinkStatus(JNIEnv *, jclass, jstring status);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_reportJavaCrash(JNIEnv *, jclass, jstring stack);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_executeNextRunnable(JNIEnv *, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyUriVisited(JNIEnv *, jclass, jstring uri);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyBatteryChange(JNIEnv* jenv, jclass, jdouble, jboolean, jdouble);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsReceived(JNIEnv* jenv, jclass, jstring, jstring, jlong);
-    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onSmsSent(JNIEnv* jenv, jclass, jstring, jstring, jlong);
+    NS_EXPORT PRInt32 JNICALL Java_org_mozilla_gecko_GeckoAppShell_saveMessageInSentbox(JNIEnv* jenv, jclass, jstring, jstring, jlong);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsSent(JNIEnv* jenv, jclass, jint, jstring, jstring, jlong);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsDelivered(JNIEnv* jenv, jclass, jint, jstring, jstring, jlong);
 
 #ifdef MOZ_JAVA_COMPOSITOR
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_bindWidgetTexture(JNIEnv* jenv, jclass);
     NS_EXPORT bool JNICALL Java_org_mozilla_gecko_GeckoAppShell_testDirectTexture(JNIEnv* jenv, jclass);
 #endif
 }
 
 
@@ -270,66 +272,108 @@ Java_org_mozilla_gecko_GeckoAppShell_not
 
     SmsMessageData message(0, eDeliveryState_Received, nsJNIString(aSender, jenv), EmptyString(),
                            nsJNIString(aBody, jenv), aTimestamp);
 
     nsCOMPtr<nsIRunnable> runnable = new NotifySmsReceivedRunnable(message);
     NS_DispatchToMainThread(runnable);
 }
 
-NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoAppShell_onSmsSent(JNIEnv* jenv, jclass,
-                                               jstring aReceiver,
-                                               jstring aBody,
-                                               jlong aTimestamp)
+NS_EXPORT PRInt32 JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_saveMessageInSentbox(JNIEnv* jenv, jclass,
+                                                          jstring aReceiver,
+                                                          jstring aBody,
+                                                          jlong aTimestamp)
 {
-    class OnSmsSentRunnable : public nsRunnable {
+    nsCOMPtr<nsISmsDatabaseService> smsDBService =
+      do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
+
+    if (!smsDBService) {
+      NS_ERROR("Sms Database Service not available!");
+      return -1;
+    }
+
+    PRInt32 id;
+    smsDBService->SaveSentMessage(nsJNIString(aReceiver, jenv),
+                                  nsJNIString(aBody, jenv), aTimestamp, &id);
+
+    return id;
+}
+
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_notifySmsSent(JNIEnv* jenv, jclass,
+                                                   jint aId,
+                                                   jstring aReceiver,
+                                                   jstring aBody,
+                                                   jlong aTimestamp)
+{
+    class NotifySmsSentRunnable : public nsRunnable {
     public:
-      OnSmsSentRunnable(const nsAString& aReceiver, const nsAString& aBody, PRUint64 aTimestamp)
-        : mReceiver(aReceiver)
-        , mBody(aBody)
-        , mTimestamp(aTimestamp)
+      NotifySmsSentRunnable(const SmsMessageData& aMessageData)
+        : mMessageData(aMessageData)
       {}
 
       NS_IMETHODIMP Run() {
-        nsCOMPtr<nsISmsDatabaseService> smsDBService =
-          do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
-
-        if (!smsDBService) {
-          NS_ERROR("Sms Database Service not available!");
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        if (!obs) {
           return NS_OK;
         }
 
-        int id;
-        smsDBService->SaveSentMessage(mReceiver, mBody, mTimestamp, &id);
-
-        nsCOMPtr<SmsMessage> message =
-          new SmsMessage(id, eDeliveryState_Sent, EmptyString(),
-                         mReceiver, mBody, mTimestamp);
-
-        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-        if (!obs) {
-            NS_ERROR("Observer Service not available!");
-            return NS_OK;
-        }
-
+        nsCOMPtr<nsIDOMMozSmsMessage> message = new SmsMessage(mMessageData);
         obs->NotifyObservers(message, kSmsSentObserverTopic, nsnull);
 
         return NS_OK;
       }
 
     private:
-      nsString mReceiver;
-      nsString mBody;
-      PRUint64 mTimestamp;
+      SmsMessageData mMessageData;
     };
 
-    nsCOMPtr<nsIRunnable> runnable =
-      new OnSmsSentRunnable(nsJNIString(aReceiver, jenv),
-                            nsJNIString(aBody, jenv), aTimestamp);
+    SmsMessageData message(aId, eDeliveryState_Sent, EmptyString(),
+                           nsJNIString(aReceiver, jenv),
+                           nsJNIString(aBody, jenv), aTimestamp);
+
+    nsCOMPtr<nsIRunnable> runnable = new NotifySmsSentRunnable(message);
+    NS_DispatchToMainThread(runnable);
+}
+
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_notifySmsDelivered(JNIEnv* jenv, jclass,
+                                                        jint aId,
+                                                        jstring aReceiver,
+                                                        jstring aBody,
+                                                        jlong aTimestamp)
+{
+    class NotifySmsDeliveredRunnable : public nsRunnable {
+    public:
+      NotifySmsDeliveredRunnable(const SmsMessageData& aMessageData)
+        : mMessageData(aMessageData)
+      {}
+
+      NS_IMETHODIMP Run() {
+        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+        if (!obs) {
+          return NS_OK;
+        }
+
+        nsCOMPtr<nsIDOMMozSmsMessage> message = new SmsMessage(mMessageData);
+        obs->NotifyObservers(message, kSmsDeliveredObserverTopic, nsnull);
+
+        return NS_OK;
+      }
+
+    private:
+      SmsMessageData mMessageData;
+    };
+
+    SmsMessageData message(aId, eDeliveryState_Sent, EmptyString(),
+                           nsJNIString(aReceiver, jenv),
+                           nsJNIString(aBody, jenv), aTimestamp);
+
+    nsCOMPtr<nsIRunnable> runnable = new NotifySmsDeliveredRunnable(message);
     NS_DispatchToMainThread(runnable);
 }
 
 #ifdef MOZ_JAVA_COMPOSITOR
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_bindWidgetTexture(JNIEnv* jenv, jclass)
 {