Bug 674725 - Part R - Save sent messages in the Android database. r=cjones
authorMounir Lamouri <mounir.lamouri@gmail.com>
Fri, 13 Jan 2012 14:25:47 +0100
changeset 84703 f3e61fd4a07e31575808104e44811d24fded9f75
parent 84702 03c51f35c291c8bfa66f8d9fe65a2418d26cb4e6
child 84704 a4caf71debe5128a9f489177ae6ae63951a686f3
push id21873
push usermlamouri@mozilla.com
push dateWed, 18 Jan 2012 10:29:07 +0000
treeherdermozilla-central@7538f4d4697c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones
bugs674725
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 674725 - Part R - Save sent messages in the Android database. r=cjones
dom/sms/interfaces/nsISmsDatabaseService.idl
dom/sms/src/android/SmsDatabaseService.cpp
dom/sms/src/fallback/SmsDatabaseService.cpp
dom/sms/src/ipc/PSms.ipdl
dom/sms/src/ipc/SmsIPCService.cpp
dom/sms/src/ipc/SmsParent.cpp
dom/sms/src/ipc/SmsParent.h
embedding/android/AndroidManifest.xml.in
embedding/android/GeckoAppShell.java
embedding/android/GeckoSmsManager.java
mozglue/android/APKOpen.cpp
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJNI.cpp
--- a/dom/sms/interfaces/nsISmsDatabaseService.idl
+++ b/dom/sms/interfaces/nsISmsDatabaseService.idl
@@ -33,19 +33,19 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 %{C++
 #define SMS_DATABASE_SERVICE_CID \
-{ 0xb5bd6b55, 0x2e56, 0x4f1d,    \
- { 0xa4, 0x38, 0x1d, 0x10, 0xd6, 0xf3, 0x4d, 0xe5 } }
+{ 0x1cb004a0, 0xe003, 0x48b9,    \
+{ 0x9f, 0xd6, 0xe9, 0x1f, 0xbb, 0xb1, 0x97, 0x89 } }
 #define SMS_DATABASE_SERVICE_CONTRACTID "@mozilla.org/sms/smsdatabaseservice;1"
 %}
 
-interface nsIDOMMozSmsMessage;
-
-[scriptable, function, uuid(ff6128fd-8caf-4b2b-898e-fb4051996767)]
+[scriptable, function, uuid(ab23f736-f545-4fcd-a298-3d6e2b380042)]
 interface nsISmsDatabaseService : nsISupports
 {
+  // Takes some information required to save the message and returns its id.
+  long saveSentMessage(in DOMString aReceiver, in DOMString aBody, in unsigned long long aDate);
 };
--- a/dom/sms/src/android/SmsDatabaseService.cpp
+++ b/dom/sms/src/android/SmsDatabaseService.cpp
@@ -39,11 +39,26 @@
 #include "AndroidBridge.h"
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
 NS_IMPL_ISUPPORTS1(SmsDatabaseService, nsISmsDatabaseService)
 
+NS_IMETHODIMP
+SmsDatabaseService::SaveSentMessage(const nsAString& aReceiver,
+                                    const nsAString& aBody,
+                                    PRUint64 aDate, PRInt32* aId)
+{
+  *aId = -1;
+
+  if (!AndroidBridge::Bridge()) {
+    return NS_OK;
+  }
+
+  *aId = AndroidBridge::Bridge()->SaveSentMessage(aReceiver, aBody, aDate);
+  return NS_OK;
+}
+
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
--- a/dom/sms/src/fallback/SmsDatabaseService.cpp
+++ b/dom/sms/src/fallback/SmsDatabaseService.cpp
@@ -38,11 +38,21 @@
 #include "SmsDatabaseService.h"
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
 NS_IMPL_ISUPPORTS1(SmsDatabaseService, nsISmsDatabaseService)
 
+NS_IMETHODIMP
+SmsDatabaseService::SaveSentMessage(const nsAString& aReceiver,
+                                    const nsAString& aBody,
+                                    PRUint64 aDate, PRInt32* aId)
+{
+  *aId = -1;
+  NS_ERROR("We should not be here!");
+  return NS_OK;
+}
+
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
--- a/dom/sms/src/ipc/PSms.ipdl
+++ b/dom/sms/src/ipc/PSms.ipdl
@@ -65,14 +65,17 @@ parent:
     sync HasSupport()
         returns (bool aHasSupport);
 
     sync GetNumberOfMessagesForText(nsString aText)
         returns (PRUint16 aNumber);
 
     SendMessage(nsString aNumber, nsString aMessage);
 
+    sync SaveSentMessage(nsString aReceiver, nsString aBody, PRUint64 aDate)
+        returns (PRInt32 aId);
+
     __delete__();
 };
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
--- a/dom/sms/src/ipc/SmsIPCService.cpp
+++ b/dom/sms/src/ipc/SmsIPCService.cpp
@@ -55,16 +55,19 @@ SmsIPCService::GetSmsChild()
 {
   if (!sSmsChild) {
     sSmsChild = ContentChild::GetSingleton()->SendPSmsConstructor();
   }
 
   return sSmsChild;
 }
 
+/*
+ * Implementation of nsISmsService.
+ */
 NS_IMETHODIMP
 SmsIPCService::HasSupport(bool* aHasSupport)
 {
   GetSmsChild()->SendHasSupport(aHasSupport);
 
   return NS_OK;
 }
 
@@ -93,11 +96,25 @@ SmsIPCService::CreateSmsMessage(PRInt32 
                                 const jsval& aTimestamp,
                                 JSContext* aCx,
                                 nsIDOMMozSmsMessage** aMessage)
 {
   return SmsMessage::Create(
     aId, aDelivery, aSender, aReceiver, aBody, aTimestamp, aCx, aMessage);
 }
 
+/*
+ * Implementation of nsISmsDatabaseService.
+ */
+NS_IMETHODIMP
+SmsIPCService::SaveSentMessage(const nsAString& aReceiver,
+                               const nsAString& aBody,
+                               PRUint64 aDate, PRInt32* aId)
+{
+  GetSmsChild()->SendSaveSentMessage(nsString(aReceiver), nsString(aBody),
+                                     aDate, aId);
+
+  return NS_OK;
+}
+
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
--- a/dom/sms/src/ipc/SmsParent.cpp
+++ b/dom/sms/src/ipc/SmsParent.cpp
@@ -38,16 +38,17 @@
 #include "SmsParent.h"
 #include "nsISmsService.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "Constants.h"
 #include "nsIDOMSmsMessage.h"
 #include "mozilla/unused.h"
 #include "SmsMessage.h"
+#include "nsISmsDatabaseService.h"
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
 NS_IMPL_ISUPPORTS1(SmsParent, nsIObserver)
 
 SmsParent::SmsParent()
@@ -118,11 +119,26 @@ SmsParent::RecvSendMessage(const nsStrin
 {
   nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(smsService, true);
 
   smsService->Send(aNumber, aMessage);
   return true;
 }
 
+bool
+SmsParent::RecvSaveSentMessage(const nsString& aRecipient,
+                               const nsString& aBody,
+                               const PRUint64& aDate, PRInt32* aId)
+{
+  *aId = -1;
+
+  nsCOMPtr<nsISmsDatabaseService> smsDBService =
+    do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(smsDBService, true);
+
+  smsDBService->SaveSentMessage(aRecipient, aBody, aDate, aId);
+  return true;
+}
+
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
--- a/dom/sms/src/ipc/SmsParent.h
+++ b/dom/sms/src/ipc/SmsParent.h
@@ -52,16 +52,17 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   SmsParent();
 
   NS_OVERRIDE virtual bool RecvHasSupport(bool* aHasSupport);
   NS_OVERRIDE virtual bool RecvGetNumberOfMessagesForText(const nsString& aText, PRUint16* aResult);
   NS_OVERRIDE virtual bool RecvSendMessage(const nsString& aNumber, const nsString& aMessage);
+  NS_OVERRIDE virtual bool RecvSaveSentMessage(const nsString& aRecipient, const nsString& aBody, const PRUint64& aDate, PRInt32* aId);
 
 protected:
   virtual void ActorDestroy(ActorDestroyReason why);
 };
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
--- a/embedding/android/AndroidManifest.xml.in
+++ b/embedding/android/AndroidManifest.xml.in
@@ -19,16 +19,18 @@
     <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
 
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.VIBRATE"/>
 
     <!-- WebSMS -->
     <uses-permission android:name="android.permission.SEND_SMS"/>
     <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
 
     <uses-feature android:name="android.hardware.location" android:required="false"/>
     <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen"/>
 
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-feature android:name="android.hardware.camera" android:required="false"/>
     <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -117,16 +117,17 @@ 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);
 
     // 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();
@@ -1691,16 +1692,20 @@ public class GeckoAppShell
     public static int getNumberOfMessagesForText(String aText) {
         return GeckoSmsManager.getNumberOfMessagesForText(aText);
     }
 
     public static void sendMessage(String aNumber, String aMessage) {
         GeckoSmsManager.send(aNumber, aMessage);
     }
 
+    public static int saveSentMessage(String aRecipient, String aBody, long aDate) {
+        return GeckoSmsManager.saveSentMessage(aRecipient, aBody, aDate);
+    }
+
     public static boolean isTablet() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
             Configuration config = GeckoApp.mAppContext.getResources().getConfiguration();
             // xlarge is defined by android as screens larger than 960dp x 720dp
             // and should include most devices ~7in and up.
             // http://developer.android.com/guide/practices/screens_support.html
             if ((config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE) {
                 return true;
--- a/embedding/android/GeckoSmsManager.java
+++ b/embedding/android/GeckoSmsManager.java
@@ -43,16 +43,21 @@ import java.util.Iterator;
 import android.util.Log;
 
 import android.app.PendingIntent;
 import android.app.Activity;
 
 import android.content.Intent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.ContentUris;
+
+import android.net.Uri;
 
 import android.os.Bundle;
 
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 
 /**
  * This class is returning unique ids for PendingIntent requestCode attribute.
@@ -219,17 +224,19 @@ public class GeckoSmsManager
       if (envelope.arePartsRemaining()) {
         return;
       }
 
       if (envelope.isFailing()) {
         // TODO: inform about the send failure.
         Log.i("GeckoSmsManager", "SMS sending failed!");
       } else {
-        // TODO: inform about the send success.
+        GeckoAppShell.onSmsSent(bundle.getString("number"),
+                                bundle.getString("message"),
+                                System.currentTimeMillis());
         Log.i("GeckoSmsManager", "SMS sending was successfull!");
       }
 
       postman.destroyEnvelope(envelopeId);
 
       return;
     }
   }
@@ -300,9 +307,39 @@ public class GeckoSmsManager
     } catch (Exception e) {
       Log.e("GeckoSmsManager", "Failed to send an SMS: ", e);
 
       if (envelopeId != Postman.kUnknownEnvelopeId) {
         Postman.getInstance().destroyEnvelope(envelopeId);
       }
     }
   }
+
+  public static int saveSentMessage(String aRecipient, String aBody, long aDate) {
+    class IdTooHighException extends Exception { }
+
+    try {
+      ContentValues values = new ContentValues();
+      values.put("address", aRecipient);
+      values.put("body", aBody);
+      values.put("date", aDate);
+
+      ContentResolver cr = GeckoApp.surfaceView.getContext().getContentResolver();
+      Uri uri = cr.insert(Uri.parse("content://sms/sent"), values);
+
+      long id = ContentUris.parseId(uri);
+
+      // The DOM API takes a 32bits unsigned int for the id. It's unlikely that
+      // we happen to need more than that but it doesn't cost to check.
+      if (id > Integer.MAX_VALUE) {
+        throw new IdTooHighException();
+      }
+
+      return (int)id;
+    } catch (IdTooHighException e) {
+      Log.e("GeckoSmsManager", "The id we received is higher than the higher allowed value.");
+      return -1;
+    } catch (Exception e) {
+      Log.e("GeckoSmsManager", "Something went wrong when trying to write a sent message: " + e);
+      return -1;
+    }
+  }
 }
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -193,16 +193,17 @@ 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);
 
 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
@@ -580,16 +581,17 @@ loadLibs(const char *apkName)
   GETFUNC(onChangeNetworkLinkStatus);
   GETFUNC(reportJavaCrash);
   GETFUNC(executeNextRunnable);
   GETFUNC(cameraCallbackBridge);
   GETFUNC(notifyBatteryChange);
   GETFUNC(notifySmsReceived);
   GETFUNC(bindWidgetTexture);
   GETFUNC(testDirectTexture);
+  GETFUNC(onSmsSent);
 #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
@@ -163,16 +163,17 @@ AndroidBridge::Init(JNIEnv *jEnv,
 
     jGetAccessibilityEnabled = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getAccessibilityEnabled", "()Z");
     jHandleGeckoMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "handleGeckoMessage", "(Ljava/lang/String;)Ljava/lang/String;");
     jCheckUriVisited = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "checkUriVisited", "(Ljava/lang/String;)V");
     jMarkUriVisited = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "markUriVisited", "(Ljava/lang/String;)V");
 
     jNumberOfMessages = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getNumberOfMessagesForText", "(Ljava/lang/String;)I");
     jSendMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "sendMessage", "(Ljava/lang/String;Ljava/lang/String;)V");
+    jSaveSentMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "saveSentMessage", "(Ljava/lang/String;Ljava/lang/String;J)I");
 
     jEGLContextClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGLContext"));
     jEGL10Class = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGL10"));
     jEGLSurfaceImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLSurfaceImpl"));
     jEGLContextImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLContextImpl"));
     jEGLConfigImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLConfigImpl"));
     jEGLDisplayImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLDisplayImpl"));
 
@@ -1348,16 +1349,28 @@ AndroidBridge::SendMessage(const nsAStri
 
     AutoLocalJNIFrame jniFrame;
     jstring jNumber = GetJNIForThread()->NewString(PromiseFlatString(aNumber).get(), aNumber.Length());
     jstring jMessage = GetJNIForThread()->NewString(PromiseFlatString(aMessage).get(), aMessage.Length());
 
     GetJNIForThread()->CallStaticVoidMethod(mGeckoAppShellClass, jSendMessage, jNumber, jMessage);
 }
 
+PRInt32
+AndroidBridge::SaveSentMessage(const nsAString& aRecipient,
+                                const nsAString& aBody, PRUint64 aDate)
+{
+    ALOG_BRIDGE("AndroidBridge::SaveSentMessage");
+
+    AutoLocalJNIFrame jniFrame;
+    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)
 {
     int err;
     void *buf;
 
     if ((err = AndroidBitmap_lockPixels(JNI(), bitmap, &buf)) != 0) {
         ALOG_BRIDGE("AndroidBitmap_lockPixels failed! (error %d)", err);
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -55,16 +55,17 @@
 
 #include "nsIAndroidBridge.h"
 
 // Some debug #defines
 // #define DEBUG_ANDROID_EVENTS
 // #define DEBUG_ANDROID_WIDGET
 
 class nsWindow;
+class nsIDOMMozSmsMessage;
 
 namespace mozilla {
 
 namespace hal {
 class BatteryInformation;
 } // namespace hal
 
 // The order and number of the members in this structure must correspond
@@ -330,16 +331,17 @@ public:
     void CloseCamera();
 
     void EnableBatteryNotifications();
     void DisableBatteryNotifications();
     void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo);
 
     PRUint16 GetNumberOfMessagesForText(const nsAString& aText);
     void SendMessage(const nsAString& aNumber, const nsAString& aText);
+    PRInt32 SaveSentMessage(const nsAString& aRecipient, const nsAString& aBody, PRUint64 aDate);
 
     bool IsTablet();
 
 protected:
     static AndroidBridge *sBridge;
 
     // the global JavaVM
     JavaVM *mJavaVM;
@@ -420,16 +422,17 @@ protected:
     jmethodID jGetAccessibilityEnabled;
     jmethodID jHandleGeckoMessage;
     jmethodID jCheckUriVisited;
     jmethodID jMarkUriVisited;
     jmethodID jEmitGeckoAccessibilityEvent;
 
     jmethodID jNumberOfMessages;
     jmethodID jSendMessage;
+    jmethodID jSaveSentMessage;
 
     // stuff we need for CallEglCreateWindowSurface
     jclass jEGLSurfaceImplClass;
     jclass jEGLContextImplClass;
     jclass jEGLConfigImplClass;
     jclass jEGLDisplayImplClass;
     jclass jEGLContextClass;
     jclass jEGL10Class;
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -58,16 +58,17 @@
 #include "nsICrashReporter.h"
 #include "nsExceptionHandler.h"
 #endif
 
 #include "mozilla/dom/sms/SmsMessage.h"
 #include "mozilla/dom/sms/Constants.h"
 #include "mozilla/dom/sms/Types.h"
 #include "mozilla/dom/sms/PSms.h"
+#include "nsISmsDatabaseService.h"
 
 using namespace mozilla;
 using namespace mozilla::dom::sms;
 
 /* Forward declare all the JNI methods as extern "C" */
 
 extern "C" {
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *, jclass);
@@ -80,16 +81,17 @@ 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);
 
 #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
 }
 
 
@@ -268,16 +270,60 @@ 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)
+{
+    class OnSmsSentRunnable : public nsRunnable {
+    public:
+      OnSmsSentRunnable(const nsAString& aReceiver, const nsAString& aBody, PRUint64 aTimestamp)
+        : mReceiver(aReceiver)
+        , mBody(aBody)
+        , mTimestamp(aTimestamp)
+      {}
+
+      NS_IMETHODIMP Run() {
+        nsCOMPtr<nsISmsDatabaseService> smsDBService =
+          do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
+
+        if (!smsDBService) {
+          NS_ERROR("Sms Database Service not available!");
+          return NS_OK;
+        }
+
+        int id;
+        smsDBService->SaveSentMessage(mReceiver, mBody, mTimestamp, &id);
+
+        // TODO: use the ID to build a SmsMessage object and notify about the
+        // sent message.
+
+        return NS_OK;
+      }
+
+    private:
+      nsString mReceiver;
+      nsString mBody;
+      PRUint64 mTimestamp;
+    };
+
+    nsCOMPtr<nsIRunnable> runnable =
+      new OnSmsSentRunnable(nsJNIString(aReceiver, jenv),
+                            nsJNIString(aBody, jenv), aTimestamp);
+    NS_DispatchToMainThread(runnable);
+}
+
 #ifdef MOZ_JAVA_COMPOSITOR
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_bindWidgetTexture(JNIEnv* jenv, jclass)
 {
     nsWindow::BindToTexture();
 }