Bug 1212679 - Handle KitKat default messaging app intents. r=fabrice r=snorp
authorReuben Morais <reuben.morais@gmail.com>
Tue, 08 Dec 2015 14:43:09 -0500
changeset 310019 9a03868366df2f99c99a23356f6853eb6971ed8f
parent 310018 219dee6921727c3a6aa8417f827a5664431a5f78
child 310020 f91bcf823a70399d593c2945b7da1d179591de60
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice, snorp
bugs1212679
milestone45.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 1212679 - Handle KitKat default messaging app intents. r=fabrice r=snorp
dom/mobilemessage/android/SmsManager.cpp
dom/mobilemessage/android/SmsManager.h
mobile/android/b2gdroid/app/Makefile.in
mobile/android/b2gdroid/app/src/main/AndroidManifest.xml
mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/HeadlessSmsSendService.java
mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java
mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/MmsService.java
mobile/android/b2gdroid/components/MessagesBridge.jsm
mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
mobile/android/base/java/org/mozilla/gecko/GeckoSmsManager.java
mobile/android/base/java/org/mozilla/gecko/SmsManager.java
mozglue/android/jni-stubs.inc
widget/android/GeneratedJNIWrappers.h
--- a/dom/mobilemessage/android/SmsManager.cpp
+++ b/dom/mobilemessage/android/SmsManager.cpp
@@ -19,34 +19,36 @@
 
 using namespace mozilla::dom;
 using namespace mozilla::dom::mobilemessage;
 
 namespace mozilla {
 
 /*static*/
 void
-SmsManager::NotifySmsReceived(jni::String::Param aSender,
+SmsManager::NotifySmsReceived(int32_t aId,
+                              jni::String::Param aSender,
                               jni::String::Param aBody,
                               int32_t aMessageClass,
+                              int64_t aSentTimestamp,
                               int64_t aTimestamp)
 {
     // TODO Need to correct the message `threadId` parameter value. Bug 859098
     SmsMessageData message;
-    message.id() = 0;
+    message.id() = aId;
     message.threadId() = 0;
     message.iccId() = EmptyString();
     message.delivery() = eDeliveryState_Received;
     message.deliveryStatus() = eDeliveryStatus_Success;
     message.sender() = aSender ? nsString(aSender) : EmptyString();
     message.receiver() = EmptyString();
     message.body() = aBody ? nsString(aBody) : EmptyString();
     message.messageClass() = static_cast<MessageClass>(aMessageClass);
     message.timestamp() = aTimestamp;
-    message.sentTimestamp() = aTimestamp;
+    message.sentTimestamp() = aSentTimestamp;
     message.deliveryTimestamp() = aTimestamp;
     message.read() = false;
 
     nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=] () {
         nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
         if (!obs) {
             return;
         }
--- a/dom/mobilemessage/android/SmsManager.h
+++ b/dom/mobilemessage/android/SmsManager.h
@@ -11,19 +11,21 @@
 namespace mozilla {
 
 class SmsManager : public widget::GeckoSmsManager::Natives<SmsManager>
 {
 private:
     SmsManager();
 
 public:
-    static void NotifySmsReceived(jni::String::Param aSender,
+    static void NotifySmsReceived(int32_t aId,
+                                  jni::String::Param aSender,
                                   jni::String::Param aBody,
                                   int32_t aMessageClass,
+                                  int64_t aSentTimestamp,
                                   int64_t aTimestamp);
     static void NotifySmsSent(int32_t aId,
                               jni::String::Param aReceiver,
                               jni::String::Param aBody,
                               int64_t aTimestamp,
                               int32_t aRequestId);
     static void NotifySmsDelivery(int32_t aId,
                                   int32_t aDeliveryStatus,
--- a/mobile/android/b2gdroid/app/Makefile.in
+++ b/mobile/android/b2gdroid/app/Makefile.in
@@ -1,17 +1,22 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 ANDROID_MANIFEST_FILE := src/main/AndroidManifest.xml
 
 JAVAFILES := \
   src/main/java/org/mozilla/b2gdroid/Apps.java \
+  src/main/java/org/mozilla/b2gdroid/GeckoEventReceiver.java \
+  src/main/java/org/mozilla/b2gdroid/HeadlessSmsSendService.java \
   src/main/java/org/mozilla/b2gdroid/Launcher.java \
+  src/main/java/org/mozilla/b2gdroid/MmsService.java \
+  src/main/java/org/mozilla/b2gdroid/NotificationObserver.java \
+  src/main/java/org/mozilla/b2gdroid/RemoteGeckoEventProxy.java \
   src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \
   src/main/java/org/mozilla/b2gdroid/SettingsMapper.java \
   src/main/java/org/mozilla/b2gdroid/GeckoEventReceiver.java \
   src/main/java/org/mozilla/b2gdroid/NotificationObserver.java \
   src/main/java/org/mozilla/b2gdroid/RemoteGeckoEventProxy.java \
   src/main/java/com/google/android/mms/ContentType.java \
   src/main/java/com/google/android/mms/InvalidHeaderValueException.java \
   src/main/java/com/google/android/mms/MmsException.java \
--- a/mobile/android/b2gdroid/app/src/main/AndroidManifest.xml
+++ b/mobile/android/b2gdroid/app/src/main/AndroidManifest.xml
@@ -28,16 +28,19 @@
     <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
     <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
 
     <!-- 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-permission android:name="android.permission.BROADCAST_SMS"/>
+    <uses-permission android:name="android.permission.WAP_PUSH_DELIVER"/>
+    <uses-permission android:name="android.permission.SEND_RESPOND_VIA_MESSAGE"/>
 
     <uses-feature android:name="android.hardware.telephony"/>
 
     <!-- Tab Queue -->
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
     <!-- Android Beam support -->
     <uses-permission android:name="android.permission.NFC"/>
@@ -64,31 +67,74 @@
     <application android:label="@string/b2g"
                  android:icon="@drawable/b2g"
                  android:logo="@drawable/b2g"
                  android:hardwareAccelerated="true"
                  android:debuggable="true">
 
         <meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
 
+        <!-- Listen for incoming SMS messages -->
+        <receiver android:name="org.mozilla.gecko.GeckoSmsManager"
+                  android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Listen for incoming MMS messages -->
+        <receiver android:name=".MmsService"
+                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Service that delivers messages from the phone "quick response" -->
+        <service android:name=".HeadlessSmsSendService"
+                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+                 android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+
         <activity android:name="org.mozilla.b2gdroid.Launcher"
                   android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                   android:icon="@drawable/b2g"
                   android:label="@string/b2g"
                   android:launchMode="singleInstance"
                   android:clearTaskOnLaunch="true"
                   android:stateNotNeeded="true">
 
             <!-- Set up as a homescreen replacement -->
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.HOME" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
 
+            <!-- Handle SMS intents -->
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+
             <!-- Default browser intents -->
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
                 <data android:scheme="http" />
                 <data android:scheme="https" />
                 <data android:scheme="about" />
new file mode 100644
--- /dev/null
+++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/HeadlessSmsSendService.java
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.b2gdroid;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.mozilla.gecko.GeckoAppShell;
+
+public class HeadlessSmsSendService extends IntentService {
+    private final static String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
+
+    public HeadlessSmsSendService() {
+        super("HeadlessSmsSendService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        if (!ACTION_RESPOND_VIA_MESSAGE.equals(intent.getAction())) {
+            return;
+        }
+
+        Bundle extras = intent.getExtras();
+        if (extras == null) {
+            return;
+        }
+
+        String recipient = intent.getData().getPath();
+        String message = extras.getString(Intent.EXTRA_TEXT);
+
+        if (recipient.length() == 0 || message.length() == 0) {
+            return;
+        }
+
+        GeckoAppShell.sendMessage(recipient, message, 0, /* shouldNotify */ false);
+    }
+}
--- a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java
+++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java
@@ -230,17 +230,17 @@ public class Launcher extends FragmentAc
         final String action = intent.getAction();
         Log.w(LOGTAG, "onNewIntent " + action);
         if (Intent.ACTION_VIEW.equals(action)) {
             Log.w(LOGTAG, "Asking gecko to view " + intent.getDataString());
             JSONObject obj = new JSONObject();
             try {
                 obj.put("action", "view");
                 obj.put("url", intent.getDataString());
-            } catch(Exception ex) {
+            } catch(JSONException ex) {
                 Log.wtf(LOGTAG, "Error building Android:Launcher view message", ex);
             }
             GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Launcher", obj.toString());
             GeckoAppShell.sendEventToGecko(e);
         } else if (Intent.ACTION_MAIN.equals(action)) {
             String message = "home-key";
 
             // Check if we did a multiple home tap to trigger the task switcher.
@@ -264,16 +264,31 @@ public class Launcher extends FragmentAc
             JSONObject obj = new JSONObject();
             try {
                 obj.put("action", message);
             } catch(JSONException ex) {
                 Log.wtf(LOGTAG, "Error building Android:Launcher message", ex);
             }
             GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Launcher", obj.toString());
             GeckoAppShell.sendEventToGecko(e);
+        } else if (Intent.ACTION_SENDTO.equals(action) ||
+                   Intent.ACTION_SEND.equals(action)) {
+            Log.d(LOGTAG, "Sending new SMS intent to Gecko");
+            JSONObject obj = new JSONObject();
+            try {
+                obj.put("action", "send_sms");
+                if (Intent.ACTION_SENDTO.equals(action)) {
+                    obj.put("number", intent.getData().getPath());
+                }
+                obj.put("body", intent.getStringExtra("sms_body"));
+            } catch (JSONException ex) {
+                Log.wtf(LOGTAG, "Error building Android:Launcher message", ex);
+            }
+            GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Launcher", obj.toString());
+            GeckoAppShell.sendEventToGecko(e);
         }
     }
 
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         Log.d(LOGTAG, "onWindowFocusChanged hasFocus=" + hasFocus);
 
         super.onWindowFocusChanged(hasFocus);
new file mode 100644
--- /dev/null
+++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/MmsService.java
@@ -0,0 +1,202 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.b2gdroid;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Telephony;
+import android.telephony.SmsManager;
+import android.util.Log;
+
+import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND;
+import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
+import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND;
+
+import com.google.android.mms.MmsException;
+import com.google.android.mms.pdu.DeliveryInd;
+import com.google.android.mms.pdu.GenericPdu;
+import com.google.android.mms.pdu.NotificationInd;
+import com.google.android.mms.pdu.PduHeaders;
+import com.google.android.mms.pdu.PduParser;
+import com.google.android.mms.pdu.PduPersister;
+import com.google.android.mms.pdu.ReadOrigInd;
+import com.google.android.mms.util.SqliteWrapper;
+
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoSmsManager;
+
+public class MmsService extends BroadcastReceiver {
+    private final static String LOGTAG = "MmsService";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals(Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION)) {
+            final byte[] data = intent.getByteArrayExtra("data");
+            writeInboxMessage(data);
+        }
+    }
+
+    private static boolean shouldParseContentDisposition() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return SmsManager.getDefault().getCarrierConfigValues().getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
+        } else {
+            return false;
+        }
+    }
+
+    private void writeInboxMessage(byte[] pushData) {
+        final Context context = GeckoAppShell.getContext();
+        final GenericPdu pdu = new PduParser(pushData, shouldParseContentDisposition()).parse();
+        if (pdu == null) {
+            Log.e(LOGTAG, "Invalid PUSH PDU");
+        }
+        final PduPersister persister = PduPersister.getPduPersister(context);
+        final int type = pdu.getMessageType();
+        try {
+            switch (type) {
+                case MESSAGE_TYPE_DELIVERY_IND:
+                case MESSAGE_TYPE_READ_ORIG_IND: {
+                    final long threadId = getDeliveryOrReadReportThreadId(context, pdu);
+                    if (threadId == -1) {
+                        // The associated SendReq isn't found, therefore skip
+                        // processing this PDU.
+                        Log.e(LOGTAG, "Failed to find delivery or read report's thread id");
+                        break;
+                    }
+                    final Uri uri = persister.persist(
+                            pdu,
+                            Telephony.Mms.Inbox.CONTENT_URI,
+                            true/*createThreadId*/,
+                            true/*groupMmsEnabled*/,
+                            null/*preOpenedFiles*/);
+                    if (uri == null) {
+                        Log.e(LOGTAG, "Failed to persist delivery or read report");
+                        break;
+                    }
+                    // Update thread ID for ReadOrigInd & DeliveryInd.
+                    final ContentValues values = new ContentValues(1);
+                    values.put(Telephony.Mms.THREAD_ID, threadId);
+                    if (SqliteWrapper.update(
+                            context,
+                            context.getContentResolver(),
+                            uri,
+                            values,
+                            null/*where*/,
+                            null/*selectionArgs*/) != 1) {
+                        Log.e(LOGTAG, "Failed to update delivery or read report thread id");
+                    }
+                    break;
+                }
+                case MESSAGE_TYPE_NOTIFICATION_IND: {
+                    final NotificationInd nInd = (NotificationInd) pdu;
+                    if (!isDuplicateNotification(context, nInd)) {
+                        final Uri uri = persister.persist(
+                                pdu,
+                                Telephony.Mms.Inbox.CONTENT_URI,
+                                true/*createThreadId*/,
+                                true/*groupMmsEnabled*/,
+                                null/*preOpenedFiles*/);
+                        if (uri == null) {
+                            Log.e(LOGTAG, "Failed to save MMS WAP push notification ind");
+                        }
+                    } else {
+                        Log.d(LOGTAG, "Skip storing duplicate MMS WAP push notification ind: "
+                                + new String(nInd.getContentLocation()));
+                    }
+                    break;
+                }
+                default:
+                    Log.e(LOGTAG, "Received unrecognized WAP Push PDU.");
+            }
+        } catch (MmsException e) {
+            Log.e(LOGTAG, "Failed to save MMS WAP push data: type=" + type, e);
+        } catch (RuntimeException e) {
+            Log.e(LOGTAG, "Unexpected RuntimeException in persisting MMS WAP push data", e);
+        }
+    }
+
+    private static final String THREAD_ID_SELECTION =
+            Telephony.Mms.MESSAGE_ID + "=? AND " + Telephony.Mms.MESSAGE_TYPE + "=?";
+
+    private static long getDeliveryOrReadReportThreadId(Context context, GenericPdu pdu) {
+        String messageId;
+        if (pdu instanceof DeliveryInd) {
+            messageId = new String(((DeliveryInd) pdu).getMessageId());
+        } else if (pdu instanceof ReadOrigInd) {
+            messageId = new String(((ReadOrigInd) pdu).getMessageId());
+        } else {
+            Log.e(LOGTAG, "WAP Push data is neither delivery or read report type: "
+                    + pdu.getClass().getCanonicalName());
+            return -1L;
+        }
+        Cursor cursor = null;
+        try {
+            cursor = SqliteWrapper.query(
+                    context,
+                    context.getContentResolver(),
+                    Telephony.Mms.CONTENT_URI,
+                    new String[]{ Telephony.Mms.THREAD_ID },
+                    THREAD_ID_SELECTION,
+                    new String[]{
+                            DatabaseUtils.sqlEscapeString(messageId),
+                            Integer.toString(PduHeaders.MESSAGE_TYPE_SEND_REQ)
+                    },
+                    null/*sortOrder*/);
+            if (cursor != null && cursor.moveToFirst()) {
+                return cursor.getLong(0);
+            }
+        } catch (SQLiteException e) {
+            Log.e(LOGTAG, "Failed to query delivery or read report thread id", e);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return -1L;
+    }
+
+    private static final String LOCATION_SELECTION =
+            Telephony.Mms.MESSAGE_TYPE + "=? AND " + Telephony.Mms.CONTENT_LOCATION + " =?";
+
+    private static boolean isDuplicateNotification(Context context, NotificationInd nInd) {
+        final byte[] rawLocation = nInd.getContentLocation();
+        if (rawLocation != null) {
+            String location = new String(rawLocation);
+            String[] selectionArgs = new String[] { location };
+            Cursor cursor = null;
+            try {
+                cursor = SqliteWrapper.query(
+                        context,
+                        context.getContentResolver(),
+                        Telephony.Mms.CONTENT_URI,
+                        new String[]{Telephony.Mms._ID},
+                        LOCATION_SELECTION,
+                        new String[]{
+                                Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
+                                new String(rawLocation)
+                        },
+                        null/*sortOrder*/);
+                if (cursor != null && cursor.getCount() > 0) {
+                    // We already received the same notification before.
+                    return true;
+                }
+            } catch (SQLiteException e) {
+                Log.e(LOGTAG, "failed to query existing notification ind", e);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+        return false;
+    }
+}
--- a/mobile/android/b2gdroid/components/MessagesBridge.jsm
+++ b/mobile/android/b2gdroid/components/MessagesBridge.jsm
@@ -100,16 +100,26 @@ this.MessagesBridge = {
         window.dispatchEvent(new window.KeyboardEvent("keydown", { key: "Power" }));
         window.dispatchEvent(new window.KeyboardEvent("keyup", { key: "Power" }));
         break;
       case "view":
         let a = new window.MozActivity({ name: "view",
                                          data: { type: "url",
                                                  url: data.url } });
         break;
+      case "send_sms":
+        new window.MozActivity({
+          name: "new",
+          data: {
+            type: "websms/sms",
+            number: data.number,
+            body: data.body
+          }
+        });
+        break;
       case "home-key":
         window.dispatchEvent(new window.KeyboardEvent("keydown", { key: "Home" }));
         window.dispatchEvent(new window.KeyboardEvent("keyup", { key: "Home" }));
         break;
       case "task-switcher":
         window.dispatchEvent(new window.CustomEvent("taskmanagershow", {}));
         break;
       case "back-key":
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
@@ -2314,23 +2314,27 @@ public class GeckoAppShell
     @WrapForJNI
     static void hideProgressDialog() {
         // unused stub
     }
 
     /*
      * WebSMS related methods.
      */
-    @WrapForJNI(stubName = "SendMessageWrapper")
-    public static void sendMessage(String aNumber, String aMessage, int aRequestId) {
+    public static void sendMessage(String aNumber, String aMessage, int aRequestId, boolean aShouldNotify) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
-        SmsManager.getInstance().send(aNumber, aMessage, aRequestId);
+        SmsManager.getInstance().send(aNumber, aMessage, aRequestId, aShouldNotify);
+    }
+
+    @WrapForJNI(stubName = "SendMessageWrapper")
+    public static void sendMessage(String aNumber, String aMessage, int aRequestId) {
+        sendMessage(aNumber, aMessage, aRequestId, /* shouldNotify */ true);
     }
 
     @WrapForJNI(stubName = "GetMessageWrapper")
     public static void getMessage(int aMessageId, int aRequestId) {
         if (!SmsManager.isEnabled()) {
             return;
         }
 
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoSmsManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoSmsManager.java
@@ -16,16 +16,17 @@ import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.provider.Telephony;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.util.Log;
 
 import static android.telephony.SmsMessage.MessageClass;
 import static org.mozilla.gecko.SmsManager.ISmsManager;
 
 import java.util.ArrayList;
@@ -262,17 +263,16 @@ class MessagesListManager
     mCursors.clear();
   }
 }
 
 public class GeckoSmsManager
   extends BroadcastReceiver
   implements ISmsManager
 {
-  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";
 
   /*
    * Make sure that the following error codes are in sync with |ErrorType| in:
    * dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl
    * The error codes are owned by the DOM.
    */
@@ -292,22 +292,22 @@ public class GeckoSmsManager
   public final static int kGeneralProblemsError = 13;
   public final static int kServiceNotAvailableError      = 14;
   public final static int kMessageTooLongForNetworkError = 15;
   public final static int kServiceNotSupportedError      = 16;
   public final static int kRetryRequiredError   = 17;
 
   private final static int kMaxMessageSize    = 160;
 
-  private final static Uri kSmsContentUri        = Uri.parse("content://sms");
-  private final static Uri kSmsSentContentUri    = Uri.parse("content://sms/sent");
-  private final static Uri kSmsThreadsContentUri = Uri.parse("content://sms/conversations");
+  private final static Uri kSmsContentUri        = Telephony.Sms.Inbox.CONTENT_URI;
+  private final static Uri kSmsSentContentUri    = Telephony.Sms.Sent.CONTENT_URI;
+  private final static Uri kSmsThreadsContentUri = Telephony.Sms.Conversations.CONTENT_URI;
 
-  private final static int kSmsTypeInbox      = 1;
-  private final static int kSmsTypeSentbox    = 2;
+  private final static int kSmsTypeInbox      = Telephony.Sms.MESSAGE_TYPE_INBOX;
+  private final static int kSmsTypeSentbox    = Telephony.Sms.MESSAGE_TYPE_SENT;
 
   /*
    * Keep the following state codes in syng with |DeliveryState| in:
    * dom/mobilemessage/Types.h
    */
   private final static int kDeliveryStateSent          = 0;
   private final static int kDeliveryStateReceived      = 1;
   private final static int kDeliveryStateSending       = 2;
@@ -321,25 +321,16 @@ public class GeckoSmsManager
    * dom/mobilemessage/Types.h
    */
   private final static int kDeliveryStatusNotApplicable = 0;
   private final static int kDeliveryStatusSuccess       = 1;
   private final static int kDeliveryStatusPending       = 2;
   private final static int kDeliveryStatusError         = 3;
 
   /*
-   * android.provider.Telephony.Sms.STATUS_*. Duplicated because they're not
-   * part of Android public API.
-   */
-  private final static int kInternalDeliveryStatusNone     = -1;
-  private final static int kInternalDeliveryStatusComplete = 0;
-  private final static int kInternalDeliveryStatusPending  = 32;
-  private final static int kInternalDeliveryStatusFailed   = 64;
-
-  /*
    * Keep the following values in sync with |MessageClass| in:
    * dom/mobilemessage/Types.h
    */
   private final static int kMessageClassNormal  = 0;
   private final static int kMessageClassClass0  = 1;
   private final static int kMessageClassClass1  = 2;
   private final static int kMessageClassClass2  = 3;
   private final static int kMessageClassClass3  = 4;
@@ -350,55 +341,101 @@ public class GeckoSmsManager
 
   // Used to generate monotonically increasing GUIDs.
   private static final AtomicInteger pendingIntentGuid = new AtomicInteger(Integer.MIN_VALUE);
 
   // The maximum value of a 32 bit signed integer. Used to enforce a limit on ids.
   private static final long UNSIGNED_INTEGER_MAX_VALUE = Integer.MAX_VALUE * 2L + 1L;
 
   public GeckoSmsManager() {
-    SmsIOThread.getInstance().start();
+    if (SmsIOThread.getInstance().getState() == Thread.State.NEW) {
+      SmsIOThread.getInstance().start();
+    }
+  }
+
+  private boolean isDefaultSmsApp(Context context) {
+    String myPackageName = context.getPackageName();
+    return Telephony.Sms.getDefaultSmsPackage(context).equals(myPackageName);
   }
 
   @Override
   public void start() {
     IntentFilter smsFilter = new IntentFilter();
-    smsFilter.addAction(ACTION_SMS_RECEIVED);
+    smsFilter.addAction(Telephony.Sms.Intents.SMS_RECEIVED_ACTION);
     smsFilter.addAction(ACTION_SMS_SENT);
     smsFilter.addAction(ACTION_SMS_DELIVERED);
 
     GeckoAppShell.getContext().registerReceiver(this, smsFilter);
   }
 
+  /**
+   * Build up the SMS message body from the SmsMessage array of received SMS
+   *
+   * @param msgs The SmsMessage array of the received SMS
+   * @return The text message body
+   */
+  private static String buildMessageBodyFromPdus(SmsMessage[] msgs) {
+    if (msgs.length == 1) {
+      // There is only one part, so grab the body directly.
+      return replaceFormFeeds(msgs[0].getDisplayMessageBody());
+    } else {
+      // Build up the body from the parts.
+      StringBuilder body = new StringBuilder();
+      for (SmsMessage msg : msgs) {
+        // getDisplayMessageBody() can NPE if mWrappedMessage inside is null.
+        body.append(msg.getDisplayMessageBody());
+      }
+      return replaceFormFeeds(body.toString());
+    }
+  }
+
+  // Some providers send formfeeds in their messages. Convert those formfeeds to newlines.
+  private static String replaceFormFeeds(String s) {
+    return s == null ? "" : s.replace('\f', '\n');
+  }
+
   @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.
-      //       Using the stock app saved message require us to wait for it to
-      //       be saved which can lead to race conditions.
-
-      Bundle bundle = intent.getExtras();
-
-      if (bundle == null) {
+    if (intent.getAction().equals(Telephony.Sms.Intents.SMS_DELIVER_ACTION) ||
+        intent.getAction().equals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)) {
+      // If we're the default SMS, ignore SMS_RECEIVED intents since we'll handle
+      // the SMS_DELIVER intent instead.
+      if (isDefaultSmsApp(GeckoAppShell.getContext()) && intent.getAction().equals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)) {
         return;
       }
 
-      Object[] pdus = (Object[]) bundle.get("pdus");
+      // TODO: Try to find the receiver number to be able to populate
+      //       SmsMessage.receiver.
+      SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent);
+      if (messages == null || messages.length == 0) {
+        return;
+      }
 
-      for (int i=0; i<pdus.length; ++i) {
-        SmsMessage msg = SmsMessage.createFromPdu((byte[])pdus[i]);
+      SmsMessage sms = messages[0];
+      String body = buildMessageBodyFromPdus(messages);
+      long timestamp = System.currentTimeMillis();
 
-        notifySmsReceived(msg.getDisplayOriginatingAddress(),
-                          msg.getDisplayMessageBody(),
-                          getGeckoMessageClass(msg.getMessageClass()),
-                          System.currentTimeMillis());
+      int id = 0;
+      // We only need to save the message if we're the default SMS app
+      if (intent.getAction().equals(Telephony.Sms.Intents.SMS_DELIVER_ACTION)) {
+        id = saveReceivedMessage(context,
+                               sms.getDisplayOriginatingAddress(),
+                               body,
+                               sms.getTimestampMillis(),
+                               timestamp,
+                               sms.getPseudoSubject());
       }
 
+      notifySmsReceived(id,
+                        sms.getDisplayOriginatingAddress(),
+                        body,
+                        getGeckoMessageClass(sms.getMessageClass()),
+                        sms.getTimestampMillis(),
+                        timestamp);
+
       return;
     }
 
     if (intent.getAction().equals(ACTION_SMS_SENT) ||
         intent.getAction().equals(ACTION_SMS_DELIVERED)) {
       Bundle bundle = intent.getExtras();
 
       if (bundle == null || !bundle.containsKey("envelopeId") ||
@@ -442,42 +479,52 @@ public class GeckoSmsManager
       }
 
       if (envelope.arePartsRemaining(part)) {
         return;
       }
 
       if (envelope.isFailing(part)) {
         if (part == Envelope.SubParts.SENT_PART) {
-          notifySmsSendFailed(envelope.getError(), bundle.getInt("requestId"));
+          if (bundle.getBoolean("shouldNotify")) {
+            notifySmsSendFailed(envelope.getError(), bundle.getInt("requestId"));
+          }
           Log.i("GeckoSmsManager", "SMS sending failed!");
         } else {
           notifySmsDelivery(envelope.getMessageId(),
                             kDeliveryStatusError,
                             bundle.getString("number"),
                             bundle.getString("message"),
                             envelope.getMessageTimestamp());
           Log.i("GeckoSmsManager", "SMS delivery failed!");
         }
       } else {
         if (part == Envelope.SubParts.SENT_PART) {
           String number = bundle.getString("number");
           String message = bundle.getString("message");
           long timestamp = System.currentTimeMillis();
 
-          int id = saveSentMessage(number, message, timestamp);
+          // save message only if we're default SMS app, otherwise sendTextMessage does it for us
+          int id = 0;
+          if (isDefaultSmsApp(GeckoAppShell.getContext())) {
+            id = saveSentMessage(number, message, timestamp);
+          }
 
-          notifySmsSent(id, number, message, timestamp,
-                        bundle.getInt("requestId"));
+          if (bundle.getBoolean("shouldNotify")) {
+            notifySmsSent(id, number, message, timestamp, bundle.getInt("requestId"));
+          }
 
           envelope.setMessageId(id);
           envelope.setMessageTimestamp(timestamp);
 
           Log.i("GeckoSmsManager", "SMS sending was successful!");
         } else {
+          Uri id = ContentUris.withAppendedId(kSmsContentUri, envelope.getMessageId());
+          updateMessageStatus(id, Telephony.Sms.STATUS_COMPLETE);
+
           notifySmsDelivery(envelope.getMessageId(),
                             kDeliveryStatusSuccess,
                             bundle.getString("number"),
                             bundle.getString("message"),
                             envelope.getMessageTimestamp());
           Log.i("GeckoSmsManager", "SMS successfully delivered!");
         }
       }
@@ -486,29 +533,32 @@ public class GeckoSmsManager
       if (!envelope.arePartsRemaining(Envelope.SubParts.SENT_PART) &&
           !envelope.arePartsRemaining(Envelope.SubParts.DELIVERED_PART)) {
         postman.destroyEnvelope(envelopeId);
       }
     }
   }
 
   @Override
-  public void send(String aNumber, String aMessage, int aRequestId) {
+  public void send(String aNumber, String aMessage, int aRequestId, boolean aShouldNotify) {
     int envelopeId = Postman.kUnknownEnvelopeId;
 
     try {
       SmsManager sm = SmsManager.getDefault();
 
-      Intent sentIntent = new Intent(ACTION_SMS_SENT);
-      Intent deliveredIntent = new Intent(ACTION_SMS_DELIVERED);
+      Intent sentIntent = new Intent(GeckoAppShell.getContext(), GeckoSmsManager.class);
+      sentIntent.setAction(ACTION_SMS_SENT);
+      Intent deliveredIntent = new Intent(GeckoAppShell.getContext(), GeckoSmsManager.class);
+      deliveredIntent.setAction(ACTION_SMS_DELIVERED);
 
       Bundle bundle = new Bundle();
       bundle.putString("number", aNumber);
       bundle.putString("message", aMessage);
       bundle.putInt("requestId", aRequestId);
+      bundle.putBoolean("shouldNotify", aShouldNotify);
 
       if (aMessage.length() <= kMaxMessageSize) {
         envelopeId = Postman.getInstance().createEnvelope(1);
         bundle.putInt("envelopeId", envelopeId);
 
         sentIntent.putExtras(bundle);
         deliveredIntent.putExtras(bundle);
 
@@ -528,17 +578,17 @@ public class GeckoSmsManager
                                      pendingIntentGuid.incrementAndGet(), sentIntent,
                                      PendingIntent.FLAG_CANCEL_CURRENT);
 
         PendingIntent deliveredPendingIntent =
           PendingIntent.getBroadcast(GeckoAppShell.getContext(),
                                      pendingIntentGuid.incrementAndGet(), deliveredIntent,
                                      PendingIntent.FLAG_CANCEL_CURRENT);
 
-        sm.sendTextMessage(aNumber, "", aMessage,
+        sm.sendTextMessage(aNumber, null, aMessage,
                            sentPendingIntent, deliveredPendingIntent);
       } else {
         ArrayList<String> parts = sm.divideMessage(aMessage);
         envelopeId = Postman.getInstance().createEnvelope(parts.size());
         bundle.putInt("envelopeId", envelopeId);
 
         sentIntent.putExtras(bundle);
         deliveredIntent.putExtras(bundle);
@@ -557,38 +607,40 @@ public class GeckoSmsManager
 
           deliveredPendingIntents.add(
             PendingIntent.getBroadcast(GeckoAppShell.getContext(),
                                        pendingIntentGuid.incrementAndGet(), deliveredIntent,
                                        PendingIntent.FLAG_CANCEL_CURRENT)
           );
         }
 
-        sm.sendMultipartTextMessage(aNumber, "", parts, sentPendingIntents,
+        sm.sendMultipartTextMessage(aNumber, null, parts, sentPendingIntents,
                                     deliveredPendingIntents);
       }
     } catch (Exception e) {
       Log.e("GeckoSmsManager", "Failed to send an SMS: ", e);
 
       if (envelopeId != Postman.kUnknownEnvelopeId) {
         Postman.getInstance().destroyEnvelope(envelopeId);
       }
 
       notifySmsSendFailed(kUnknownError, aRequestId);
     }
   }
 
   public int saveSentMessage(String aRecipient, String aBody, long aDate) {
     try {
       ContentValues values = new ContentValues();
-      values.put("address", aRecipient);
-      values.put("body", aBody);
-      values.put("date", aDate);
+      values.put(Telephony.Sms.ADDRESS, aRecipient);
+      values.put(Telephony.Sms.BODY, aBody);
+      values.put(Telephony.Sms.DATE, aDate);
       // Always 'PENDING' because we always request status report.
-      values.put("status", kInternalDeliveryStatusPending);
+      values.put(Telephony.Sms.STATUS, Telephony.Sms.STATUS_PENDING);
+      values.put(Telephony.Sms.SEEN, 0);
+      values.put(Telephony.Sms.READ, 0);
 
       ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
       Uri uri = cr.insert(kSmsSentContentUri, 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.
@@ -601,16 +653,51 @@ public class GeckoSmsManager
       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;
     }
   }
 
+  public void updateMessageStatus(Uri aId, int aStatus) {
+    ContentValues values = new ContentValues(1);
+    values.put(Telephony.Sms.STATUS, aStatus);
+
+    ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
+    if (cr.update(aId, values, null, null) != 1) {
+      Log.i("GeckoSmsManager", "Failed to update message status!");
+    }
+  }
+
+  public int saveReceivedMessage(Context aContext, String aSender, String aBody, long aDateSent, long aDate, String aSubject) {
+    ContentValues values = new ContentValues();
+    values.put(Telephony.Sms.Inbox.ADDRESS, aSender);
+    values.put(Telephony.Sms.Inbox.BODY, aBody);
+    values.put(Telephony.Sms.Inbox.DATE_SENT, aDateSent);
+    values.put(Telephony.Sms.Inbox.DATE, aDate);
+    values.put(Telephony.Sms.Inbox.STATUS, Telephony.Sms.STATUS_COMPLETE);
+    values.put(Telephony.Sms.Inbox.READ, 0);
+    values.put(Telephony.Sms.Inbox.SEEN, 0);
+
+    ContentResolver cr = aContext.getContentResolver();
+    Uri uri = cr.insert(kSmsContentUri, 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 > UNSIGNED_INTEGER_MAX_VALUE) {
+      Log.i("GeckoSmsManager", "The id we received is higher than the higher allowed value.");
+      return -1;
+    }
+
+    return (int)id;
+  }
+
   @Override
   public void getMessage(int aMessageId, int aRequestId) {
     class GetMessageRunnable implements Runnable {
       private final int mMessageId;
       private final int mRequestId;
 
       GetMessageRunnable(int aMessageId, int aRequestId) {
         mMessageId = aMessageId;
@@ -1074,23 +1161,23 @@ public class GeckoSmsManager
 
   @Override
   public void shutdown() {
     SmsIOThread.getInstance().interrupt();
     MessagesListManager.getInstance().clear();
   }
 
   private int getGeckoDeliveryStatus(int aDeliveryStatus) {
-    if (aDeliveryStatus == kInternalDeliveryStatusNone) {
+    if (aDeliveryStatus == Telephony.Sms.STATUS_NONE) {
       return kDeliveryStatusNotApplicable;
     }
-    if (aDeliveryStatus >= kInternalDeliveryStatusFailed) {
+    if (aDeliveryStatus >= Telephony.Sms.STATUS_FAILED) {
       return kDeliveryStatusError;
     }
-    if (aDeliveryStatus >= kInternalDeliveryStatusPending) {
+    if (aDeliveryStatus >= Telephony.Sms.STATUS_PENDING) {
       return kDeliveryStatusPending;
     }
     return kDeliveryStatusSuccess;
   }
 
   private int getGeckoMessageClass(MessageClass aMessageClass) {
     switch (aMessageClass) {
       case CLASS_0:
@@ -1122,17 +1209,17 @@ public class GeckoSmsManager
     private static final long serialVersionUID = 51883196784325305L;
   }
 
   static class UnmatchingIdException extends Exception {
     private static final long serialVersionUID = 158467542575633280L;
   }
 
   @WrapForJNI
-  private static native void notifySmsReceived(String aSender, String aBody, int aMessageClass, long aTimestamp);
+  public static native void notifySmsReceived(int aId, String aSender, String aBody, int aMessageClass, long aSentTimestamp, long aTimestamp);
   @WrapForJNI
   private static native void notifySmsSent(int aId, String aReceiver, String aBody, long aTimestamp, int aRequestId);
   @WrapForJNI
   private static native void notifySmsDelivery(int aId, int aDeliveryStatus, String aReceiver, String aBody, long aTimestamp);
   @WrapForJNI
   private static native void notifySmsSendFailed(int aError, int aRequestId);
   @WrapForJNI
   private static native void notifyGetSms(int aId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, boolean aRead, int aRequestId);
--- a/mobile/android/base/java/org/mozilla/gecko/SmsManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SmsManager.java
@@ -23,17 +23,17 @@ public class SmsManager {
         return AppConstants.MOZ_WEBSMS_BACKEND;
     }
 
     public interface ISmsManager {
         void start();
         void stop();
         void shutdown();
 
-        void send(String aNumber, String aMessage, int aRequestId);
+        void send(String aNumber, String aMessage, int aRequestId, boolean aShouldNotify);
         void getMessage(int aMessageId, int aRequestId);
         void deleteMessage(int aMessageId, int aRequestId);
         void markMessageRead(int aMessageId, boolean aValue, boolean aSendReadReport, int aRequestId);
         void createMessageCursor(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, String aDelivery, boolean aHasRead, boolean aRead, boolean aHasThreadId, long aThreadId, boolean aReverse, int aRequestId);
         void createThreadCursor(int aRequestId);
         void getNextThread(int aRequestId);
         void getNextMessage(int aRequestId);
     }
--- a/mozglue/android/jni-stubs.inc
+++ b/mozglue/android/jni-stubs.inc
@@ -357,26 +357,26 @@ Java_org_mozilla_gecko_GeckoJavaSampler_
 #endif
 
 #ifdef JNI_BINDINGS
   xul_dlsym("Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime", &f_Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime);
 #endif
 
 #ifdef JNI_STUBS
 
-typedef void (*Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived_t)(JNIEnv *, jclass, jstring, jstring, jint, jlong);
+typedef void (*Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived_t)(JNIEnv *, jclass, jint, jstring, jstring, jint, jlong, jlong);
 static Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived_t f_Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived;
 extern "C" NS_EXPORT void MOZ_JNICALL
-Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived(JNIEnv * arg0, jclass arg1, jstring arg2, jstring arg3, jint arg4, jlong arg5) {
+Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived(JNIEnv * arg0, jclass arg1, jint arg2, jstring arg3, jstring arg4, jint arg5, jlong arg6, jlong arg7) {
     if (!f_Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived) {
         arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
                        "JNI Function called before it was loaded");
         return ;
     }
-     f_Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived(arg0, arg1, arg2, arg3, arg4, arg5);
+     f_Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
 }
 #endif
 
 #ifdef JNI_BINDINGS
   xul_dlsym("Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived", &f_Java_org_mozilla_gecko_GeckoSmsManager_notifySmsReceived);
 #endif
 
 #ifdef JNI_STUBS
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -2386,23 +2386,25 @@ public:
     };
 
 public:
     struct NotifySmsReceived_t {
         typedef GeckoSmsManager Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
+                int32_t,
                 mozilla::jni::String::Param,
                 mozilla::jni::String::Param,
                 int32_t,
+                int64_t,
                 int64_t> Args;
         static constexpr char name[] = "notifySmsReceived";
         static constexpr char signature[] =
-                "(Ljava/lang/String;Ljava/lang/String;IJ)V";
+                "(ILjava/lang/String;Ljava/lang/String;IJJ)V";
         static const bool isStatic = true;
         static const bool isMultithreaded = false;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
 public:
     struct NotifySmsSendFailed_t {