Bug 823010 - B2G SMS: We should not ack reception when there's a storage error. r=vicamo, ferjm a=blocking-b2g
☠☠ backed out by c30676933621 ☠ ☠
authorPhilipp von Weitershausen <philipp@weitershausen.de>
Fri, 18 Jan 2013 16:34:57 -0800
changeset 119325 8447875c6479a7ca5969c317b710de7c79b04b7e
parent 119324 fdeb0c833138eda51ed9b45ba8ca55a160eb5947
child 119326 097d044586c1d61706858077282ab647a2135c13
push id24195
push userMs2ger@gmail.com
push dateSat, 19 Jan 2013 16:10:11 +0000
treeherdermozilla-central@02e12a80aef9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvicamo, ferjm, blocking-b2g
bugs823010
milestone21.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 823010 - B2G SMS: We should not ack reception when there's a storage error. r=vicamo, ferjm a=blocking-b2g
dom/sms/interfaces/nsIRilSmsDatabaseService.idl
dom/sms/src/ril/SmsDatabaseService.js
dom/system/gonk/RadioInterfaceLayer.js
dom/system/gonk/ril_consts.js
dom/system/gonk/ril_worker.js
--- a/dom/sms/interfaces/nsIRilSmsDatabaseService.idl
+++ b/dom/sms/interfaces/nsIRilSmsDatabaseService.idl
@@ -1,13 +1,33 @@
 /* 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/. */
 
+#include "nsISupports.idl"
 #include "nsISmsDatabaseService.idl"
 
-[scriptable, uuid(71d7dd4e-5489-4e58-a489-171200378c3c)]
+interface nsIDOMMozSmsMessage;
+
+[scriptable, function, uuid(04a08668-c020-469e-a1ad-8626c951ab2b)]
+interface nsIRilSmsDatabaseCallback : nsISupports
+{
+  void notify(in nsresult aRv, in nsIDOMMozSmsMessage aSms);
+};
+
+[scriptable, uuid(8e2acd73-0332-4d16-82cc-ff5bac59d245)]
 interface nsIRilSmsDatabaseService : nsISmsDatabaseService
 {
-  long saveReceivedMessage(in DOMString aSender, in DOMString aBody, in DOMString aMessageClass, in unsigned long long aDate);
-  long saveSendingMessage(in DOMString aReceiver, in DOMString aBody, in unsigned long long aDate);
-  void setMessageDelivery(in long aMessageId, in DOMString aDelivery, in DOMString aDeliveryStatus);
+  long saveReceivedMessage(in DOMString aSender,
+                           in DOMString aBody,
+                           in DOMString aMessageClass,
+                           in unsigned long long aDate,
+                [optional] in nsIRilSmsDatabaseCallback aCallback);
+  long saveSendingMessage(in DOMString aReceiver,
+                          in DOMString aBody,
+                          in DOMString aDeliveryStatus,
+                          in unsigned long long aDate,
+               [optional] in nsIRilSmsDatabaseCallback aCallback);
+  void setMessageDelivery(in long aMessageId,
+                          in DOMString aDelivery,
+                          in DOMString aDeliveryStatus,
+               [optional] in nsIRilSmsDatabaseCallback aCallback);
 };
--- a/dom/sms/src/ril/SmsDatabaseService.js
+++ b/dom/sms/src/ril/SmsDatabaseService.js
@@ -174,44 +174,44 @@ SmsDatabaseService.prototype = {
             self.createSchema(db);
             break;
           case 1:
             if (DEBUG) debug("Upgrade to version 2. Including `read` index");
             let objectStore = event.target.transaction.objectStore(STORE_NAME);
             self.upgradeSchema(objectStore);
             break;
           case 2:
-            if (DEBUG) debug("Upgrade to version 3. Fix existing entries.")
+            if (DEBUG) debug("Upgrade to version 3. Fix existing entries.");
             objectStore = event.target.transaction.objectStore(STORE_NAME);
             self.upgradeSchema2(objectStore);
             break;
           case 3:
-            if (DEBUG) debug("Upgrade to version 4. Add quick threads view.")
+            if (DEBUG) debug("Upgrade to version 4. Add quick threads view.");
             self.upgradeSchema3(db, event.target.transaction);
             break;
           case 4:
-            if (DEBUG) debug("Upgrade to version 5. Populate quick threads view.")
+            if (DEBUG) debug("Upgrade to version 5. Populate quick threads view.");
             self.upgradeSchema4(event.target.transaction);
             break;
           case 5:
-            if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS.")
+            if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS.");
             self.upgradeSchema5(event.target.transaction);
             break;
           case 6:
-            if (DEBUG) debug("Upgrade to version 7. Use multiple entry indexes.")
+            if (DEBUG) debug("Upgrade to version 7. Use multiple entry indexes.");
             self.upgradeSchema6(event.target.transaction);
             break;
           default:
             event.target.transaction.abort();
             callback("Old database version: " + event.oldVersion, null);
             break;
         }
         currentVersion++;
       }
-    }
+    };
     request.onerror = function (event) {
       //TODO look at event.target.Code and change error constant accordingly
       callback("Error opening database!", null);
     };
     request.onblocked = function (event) {
       callback("Opening database request is blocked.", null);
     };
   },
@@ -293,17 +293,17 @@ SmsDatabaseService.prototype = {
         return;
       }
 
       let message = cursor.value;
       message.messageClass = MESSAGE_CLASS_NORMAL;
       message.deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
       cursor.update(message);
       cursor.continue();
-    }
+    };
   },
 
   upgradeSchema3: function upgradeSchema3(db, transaction) {
     // Delete redundant "id" index.
     let objectStore = transaction.objectStore(STORE_NAME);
     if (objectStore.indexNames.contains("id")) {
       objectStore.deleteIndex("id");
     }
@@ -353,20 +353,20 @@ SmsDatabaseService.prototype = {
         }
       } else {
         threads[contact] = {
           senderOrReceiver: contact,
           id: message.id,
           timestamp: message.timestamp,
           body: message.body,
           unreadCount: message.read ? 0 : 1
-        }
+        };
       }
       cursor.continue();
-    }
+    };
   },
 
   upgradeSchema5: function upgradeSchema5(transaction) {
     // Don't perform any upgrade. See Bug 819560.
   },
 
   upgradeSchema6: function upgradeSchema6(transaction) {
     let objectStore = transaction.objectStore(STORE_NAME);
@@ -405,17 +405,17 @@ SmsDatabaseService.prototype = {
       message.deliveryIndex = [message.delivery, timestamp];
       message.numberIndex = [
         [message.sender, timestamp],
         [message.receiver, timestamp]
       ];
       message.readIndex = [message.read, timestamp];
       cursor.update(message);
       cursor.continue();
-    }
+    };
   },
 
   createMessageFromRecord: function createMessageFromRecord(record) {
     if (DEBUG) debug("createMessageFromRecord: " + JSON.stringify(record));
     return gSmsService.createSmsMessage(record.id,
                                         record.delivery,
                                         record.deliveryStatus,
                                         record.sender,
@@ -494,24 +494,24 @@ SmsDatabaseService.prototype = {
         aMessageList.listId = self.lastMessageListId;
         self.messageLists[self.lastMessageListId] = aMessageList;
         if (DEBUG) {
           debug("notifyMessageListCreated - listId: "
                 + aMessageList.listId + ", messageId: " + firstMessageId);
         }
         smsRequest.notifyMessageListCreated(aMessageList.listId, sms);
       }
-    }
+    };
     getRequest.onerror = function onerror(event) {
       if (DEBUG) {
         debug("notifyReadMessageListFailed - listId: "
               + aMessageList.listId + ", messageId: " + firstMessageId);
       }
       smsRequest.notifyReadMessageListFailed(Ci.nsISmsRequest.INTERNAL_ERROR);
-    }
+    };
   },
 
   /**
    * Queue up {aMessageId, aTimestamp} pairs, find out intersections and report
    * to onNextMessageInListGot. Return true if it is still possible to have
    * another match.
    */
   onNextMessageInMultiFiltersGot: function onNextMessageInMultiFiltersGot(
@@ -642,31 +642,51 @@ SmsDatabaseService.prototype = {
                                             tres[i].id, tres[i].timestamp);
       }
       this.onNextMessageInMultiFiltersGot(aObjectStore, aMessageList,
                                           aContextIndex, 0, 0);
     }
     return false;
   },
 
-  saveMessage: function saveMessage(message) {
+  saveMessage: function saveMessage(message, callback) {
     this.lastKey += 1;
     message.id = this.lastKey;
     if (DEBUG) debug("Going to store " + JSON.stringify(message));
+
+    let self = this;
+    function notifyResult(rv) {
+      if (!callback) {
+        return;
+      }
+      let sms = self.createMessageFromRecord(message);
+      callback.notify(rv, sms);
+    }
+
     this.newTxn(READ_WRITE, function(error, txn, stores) {
       if (error) {
+        // TODO bug 832140 check event.target.errorCode
+        notifyResult(Cr.NS_ERROR_FAILURE);
         return;
       }
+      txn.oncomplete = function oncomplete(event) {
+        notifyResult(Cr.NS_OK);
+      };
+      txn.onabort = function onabort(event) {
+        // TODO bug 832140 check event.target.errorCode
+        notifyResult(Cr.NS_ERROR_FAILURE);
+      };
+
       // First add to main objectStore.
       stores[0].put(message);
 
       let number = numberFromMessage(message);
 
       // Next update the other objectStore.
-      stores[1].get(number).onsuccess = function(event) {
+      stores[1].get(number).onsuccess = function onsuccess(event) {
         let mostRecentEntry = event.target.result;
         if (mostRecentEntry) {
           let needsUpdate = false;
 
           if (mostRecentEntry.timestamp <= message.timestamp) {
             mostRecentEntry.timestamp = message.timestamp;
             mostRecentEntry.body = message.body;
             needsUpdate = true;
@@ -682,29 +702,32 @@ SmsDatabaseService.prototype = {
           }
         } else {
           event.target.source.add({ senderOrReceiver: number,
                                     timestamp: message.timestamp,
                                     body: message.body,
                                     id: message.id,
                                     unreadCount: message.read ? 0 : 1 });
         }
-      }
+      };
     }, [STORE_NAME, MOST_RECENT_STORE_NAME]);
     // We return the key that we expect to store in the db
     return message.id;
   },
 
 
   /**
    * nsIRilSmsDatabaseService API
    */
 
-  saveReceivedMessage: function saveReceivedMessage(aSender, aBody, aMessageClass, aDate) {
-    let receiver = this.mRIL.rilContext.icc ? this.mRIL.rilContext.icc.msisdn : null;
+  saveReceivedMessage: function saveReceivedMessage(
+      aSender, aBody, aMessageClass, aDate, aCallback) {
+    let receiver = this.mRIL.rilContext.icc
+                 ? this.mRIL.rilContext.icc.msisdn
+                 : null;
 
     // Workaround an xpconnect issue with undefined string objects.
     // See bug 808220
     if (receiver === undefined || receiver === "undefined") {
       receiver = null;
     }
 
     if (receiver) {
@@ -731,29 +754,32 @@ SmsDatabaseService.prototype = {
       deliveryStatus: DELIVERY_STATUS_SUCCESS,
       sender:         sender,
       receiver:       receiver,
       body:           aBody,
       messageClass:   aMessageClass,
       timestamp:      aDate,
       read:           FILTER_READ_UNREAD
     };
-    return this.saveMessage(message);
+    return this.saveMessage(message, aCallback);
   },
 
-  saveSendingMessage: function saveSendingMessage(aReceiver, aBody, aDate) {
-    let sender = this.mRIL.rilContext.icc ? this.mRIL.rilContext.icc.msisdn : null;
+  saveSendingMessage: function saveSendingMessage(
+      aReceiver, aBody, aDeliveryStatus, aDate, aCallback) {
+    let sender = this.mRIL.rilContext.icc
+               ? this.mRIL.rilContext.icc.msisdn
+               : null;
 
     // Workaround an xpconnect issue with undefined string objects.
     // See bug 808220
     if (sender === undefined || sender === "undefined") {
       sender = null;
     }
 
-    let receiver = aReceiver
+    let receiver = aReceiver;
     if (receiver) {
       let parsedNumber = PhoneNumberUtils.parse(receiver.toString());
       receiver = (parsedNumber && parsedNumber.internationalNumber)
                  ? parsedNumber.internationalNumber
                  : receiver;
     }
 
     if (sender) {
@@ -764,41 +790,64 @@ SmsDatabaseService.prototype = {
     }
 
     let message = {
       deliveryIndex:  [DELIVERY_SENDING, aDate],
       numberIndex:    [[sender, aDate], [receiver, aDate]],
       readIndex:      [FILTER_READ_READ, aDate],
 
       delivery:       DELIVERY_SENDING,
-      deliveryStatus: DELIVERY_STATUS_PENDING,
+      deliveryStatus: aDeliveryStatus,
       sender:         sender,
       receiver:       receiver,
       body:           aBody,
       messageClass:   MESSAGE_CLASS_NORMAL,
       timestamp:      aDate,
       read:           FILTER_READ_READ
     };
-    return this.saveMessage(message);
+    return this.saveMessage(message, aCallback);
   },
 
-  setMessageDelivery: function setMessageDelivery(messageId, delivery, deliveryStatus) {
+  setMessageDelivery: function setMessageDelivery(
+      messageId, delivery, deliveryStatus, callback) {
     if (DEBUG) {
       debug("Setting message " + messageId + " delivery to " + delivery
             + ", and deliveryStatus to " + deliveryStatus);
     }
+
+    let self = this;
+    let message;
+    function notifyResult(rv) {
+      if (!callback) {
+        return;
+      }
+      let sms = null;
+      if (message) {
+        sms = self.createMessageFromRecord(message);
+      }
+      callback.notify(rv, sms);
+    }
+
     this.newTxn(READ_WRITE, function (error, txn, store) {
       if (error) {
-        if (DEBUG) debug(error);
+        // TODO bug 832140 check event.target.errorCode
+        notifyResult(Cr.NS_ERROR_FAILURE);
         return;
       }
+      txn.oncomplete = function oncomplete(event) {
+        notifyResult(Cr.NS_OK);
+      };
+      txn.onabort = function onabort(event) {
+        // TODO bug 832140 check event.target.errorCode
+        notifyResult(Cr.NS_ERROR_FAILURE);
+      };
 
       let getRequest = store.get(messageId);
       getRequest.onsuccess = function onsuccess(event) {
-        let message = event.target.result;
+        message = event.target.result;
         if (!message) {
           if (DEBUG) debug("Message ID " + messageId + " not found");
           return;
         }
         if (message.id != messageId) {
           if (DEBUG) {
             debug("Retrieve message ID (" + messageId + ") is " +
                   "different from the one we got");
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -148,17 +148,17 @@ function convertRILCallState(state) {
     case RIL.CALL_STATE_HOLDING:
       return nsIRadioInterfaceLayer.CALL_STATE_HELD;
     case RIL.CALL_STATE_DIALING:
       return nsIRadioInterfaceLayer.CALL_STATE_DIALING;
     case RIL.CALL_STATE_ALERTING:
       return nsIRadioInterfaceLayer.CALL_STATE_ALERTING;
     case RIL.CALL_STATE_INCOMING:
     case RIL.CALL_STATE_WAITING:
-      return nsIRadioInterfaceLayer.CALL_STATE_INCOMING; 
+      return nsIRadioInterfaceLayer.CALL_STATE_INCOMING;
     case RIL.CALL_STATE_BUSY:
       return nsIRadioInterfaceLayer.CALL_STATE_BUSY;
     default:
       throw new Error("Unknown rilCallState: " + state);
   }
 }
 
 /**
@@ -1103,23 +1103,23 @@ RadioInterfaceLayer.prototype = {
     }
   },
 
   updateRILNetworkInterface: function updateRILNetworkInterface() {
     if (this._dataCallSettingsToRead.length) {
       debug("We haven't read completely the APN data from the " +
             "settings DB yet. Wait for that.");
       return;
-    } 
+    }
 
-    // This check avoids data call connection if the radio is not ready 
-    // yet after toggling off airplane mode. 
+    // This check avoids data call connection if the radio is not ready
+    // yet after toggling off airplane mode.
     if (this.rilContext.radioState != RIL.GECKO_RADIOSTATE_READY) {
       debug("RIL is not ready for data connection: radio's not ready");
-      return; 
+      return;
     }
 
     // We only watch at "ril.data.enabled" flag changes for connecting or
     // disconnecting the data call. If the value of "ril.data.enabled" is
     // true and any of the remaining flags change the setting application
     // should turn this flag to false and then to true in order to reload
     // the new values and reconnect the data call.
     if (this._oldRilDataEnabledState == this.dataCallSettings["enabled"]) {
@@ -1365,44 +1365,67 @@ RadioInterfaceLayer.prototype = {
     let mwi = message.mwi;
     if (mwi) {
       mwi.returnNumber = message.sender || null;
       mwi.returnMessage = message.fullBody || null;
       this._sendTargetMessage("voicemail", "RIL:VoicemailNotification", mwi);
       return;
     }
 
-    let id = -1;
+    let notifyReceived = function notifyReceived(rv, sms) {
+      let success = Components.isSuccessCode(rv);
+
+      // Acknowledge the reception of the SMS.
+      message.rilMessageType = "ackSMS";
+      if (!success) {
+        message.result = RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED;
+      }
+      this.worker.postMessage(message);
+
+      if (!success) {
+        // At this point we could send a message to content to notify the user
+        // that storing an incoming SMS failed, most likely due to a full disk.
+        debug("Could not store SMS " + message.id + ", error code " + rv);
+        return;
+      }
+
+      gSystemMessenger.broadcastMessage("sms-received", {
+          id: message.id,
+          delivery: DOM_SMS_DELIVERY_RECEIVED,
+          deliveryStatus: RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS,
+          sender: message.sender || null,
+          receiver: message.receiver || null,
+          body: message.fullBody || null,
+          messageClass: message.messageClass,
+          timestamp: message.timestamp,
+          read: false
+      });
+      Services.obs.notifyObservers(sms, kSmsReceivedObserverTopic, null);
+    }.bind(this);
+
     if (message.messageClass != RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0]) {
-      id = gSmsDatabaseService.saveReceivedMessage(message.sender || null,
-                                                   message.fullBody || null,
-                                                   message.messageClass,
-                                                   message.timestamp);
+      message.id = gSmsDatabaseService.saveReceivedMessage(
+          message.sender || null,
+          message.fullBody || null,
+          message.messageClass,
+          message.timestamp,
+          notifyReceived);
+    } else {
+      message.id = -1;
+      let sms = gSmsService.createSmsMessage(message.id,
+                                             DOM_SMS_DELIVERY_RECEIVED,
+                                             RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS,
+                                             message.sender || null,
+                                             message.receiver || null,
+                                             message.fullBody || null,
+                                             message.messageClass,
+                                             message.timestamp,
+                                             false);
+      notifyReceived(Cr.NS_OK, sms);
     }
-    let sms = gSmsService.createSmsMessage(id,
-                                           DOM_SMS_DELIVERY_RECEIVED,
-                                           RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS,
-                                           message.sender || null,
-                                           message.receiver || null,
-                                           message.fullBody || null,
-                                           message.messageClass,
-                                           message.timestamp,
-                                           false);
-
-    gSystemMessenger.broadcastMessage("sms-received",
-                                      {id: id,
-                                       delivery: DOM_SMS_DELIVERY_RECEIVED,
-                                       deliveryStatus: RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS,
-                                       sender: message.sender || null,
-                                       receiver: message.receiver || null,
-                                       body: message.fullBody || null,
-                                       messageClass: message.messageClass,
-                                       timestamp: message.timestamp,
-                                       read: false});
-    Services.obs.notifyObservers(sms, kSmsReceivedObserverTopic, null);
   },
 
   /**
    * Local storage for sent SMS messages.
    */
   _sentSmsEnvelopes: null,
   createSmsEnvelope: function createSmsEnvelope(options) {
     let i;
@@ -1420,78 +1443,62 @@ RadioInterfaceLayer.prototype = {
 
     let options = this._sentSmsEnvelopes[message.envelopeId];
     if (!options) {
       return;
     }
 
     gSmsDatabaseService.setMessageDelivery(options.sms.id,
                                            DOM_SMS_DELIVERY_SENT,
-                                           options.sms.deliveryStatus);
-
-    let sms = gSmsService.createSmsMessage(options.sms.id,
-                                           DOM_SMS_DELIVERY_SENT,
                                            options.sms.deliveryStatus,
-                                           null,
-                                           options.sms.receiver,
-                                           options.sms.body,
-                                           options.sms.messageClass,
-                                           options.sms.timestamp,
-                                           true);
+                                           function notifyResult(rv, sms) {
+      //TODO bug 832140 handle !Components.isSuccessCode(rv)
+      gSystemMessenger.broadcastMessage("sms-sent",
+                                        {id: options.sms.id,
+                                         delivery: DOM_SMS_DELIVERY_SENT,
+                                         deliveryStatus: options.sms.deliveryStatus,
+                                         sender: message.sender || null,
+                                         receiver: options.sms.receiver,
+                                         body: options.sms.body,
+                                         messageClass: options.sms.messageClass,
+                                         timestamp: options.sms.timestamp,
+                                         read: true});
 
-    gSystemMessenger.broadcastMessage("sms-sent",
-                                      {id: options.sms.id,
-                                       delivery: DOM_SMS_DELIVERY_SENT,
-                                       deliveryStatus: options.sms.deliveryStatus,
-                                       sender: message.sender || null,
-                                       receiver: options.sms.receiver,
-                                       body: options.sms.body,
-                                       messageClass: options.sms.messageClass,
-                                       timestamp: options.sms.timestamp,
-                                       read: true});
+      if (!options.requestStatusReport) {
+        // No more used if STATUS-REPORT not requested.
+        delete this._sentSmsEnvelopes[message.envelopeId];
+      } else {
+        options.sms = sms;
+      }
 
-    if (!options.requestStatusReport) {
-      // No more used if STATUS-REPORT not requested.
-      delete this._sentSmsEnvelopes[message.envelopeId];
-    } else {
-      options.sms = sms;
-    }
+      options.request.notifyMessageSent(sms);
 
-    options.request.notifyMessageSent(sms);
-
-    Services.obs.notifyObservers(sms, kSmsSentObserverTopic, null);
+      Services.obs.notifyObservers(sms, kSmsSentObserverTopic, null);
+    }.bind(this));
   },
 
   handleSmsDelivery: function handleSmsDelivery(message) {
     debug("handleSmsDelivery: " + JSON.stringify(message));
 
     let options = this._sentSmsEnvelopes[message.envelopeId];
     if (!options) {
       return;
     }
     delete this._sentSmsEnvelopes[message.envelopeId];
 
     gSmsDatabaseService.setMessageDelivery(options.sms.id,
                                            options.sms.delivery,
-                                           message.deliveryStatus);
-
-    let sms = gSmsService.createSmsMessage(options.sms.id,
-                                           options.sms.delivery,
                                            message.deliveryStatus,
-                                           null,
-                                           options.sms.receiver,
-                                           options.sms.body,
-                                           options.sms.messageClass,
-                                           options.sms.timestamp,
-                                           true);
-
-    let topic = (message.deliveryStatus == RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS)
-                ? kSmsDeliverySuccessObserverTopic
-                : kSmsDeliveryErrorObserverTopic;
-    Services.obs.notifyObservers(sms, topic, null);
+                                           function notifyResult(rv, sms) {
+      //TODO bug 832140 handle !Components.isSuccessCode(rv)
+      let topic = (message.deliveryStatus == RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS)
+                  ? kSmsDeliverySuccessObserverTopic
+                  : kSmsDeliveryErrorObserverTopic;
+      Services.obs.notifyObservers(sms, topic, null);
+    });
   },
 
   handleSmsSendFailed: function handleSmsSendFailed(message) {
     debug("handleSmsSendFailed: " + JSON.stringify(message));
 
     let options = this._sentSmsEnvelopes[message.envelopeId];
     if (!options) {
       return;
@@ -1502,31 +1509,22 @@ RadioInterfaceLayer.prototype = {
     switch (message.error) {
       case RIL.ERROR_RADIO_NOT_AVAILABLE:
         error = Ci.nsISmsRequest.NO_SIGNAL_ERROR;
         break;
     }
 
     gSmsDatabaseService.setMessageDelivery(options.sms.id,
                                            DOM_SMS_DELIVERY_ERROR,
-                                           RIL.GECKO_SMS_DELIVERY_STATUS_ERROR);
-
-    let sms = gSmsService.createSmsMessage(options.sms.id,
-                                           DOM_SMS_DELIVERY_ERROR,
                                            RIL.GECKO_SMS_DELIVERY_STATUS_ERROR,
-                                           null,
-                                           options.sms.receiver,
-                                           options.sms.body,
-                                           options.sms.messageClass,
-                                           options.sms.timestamp,
-                                           true);
-
-    options.request.notifySendMessageFailed(error);
-
-    Services.obs.notifyObservers(sms, kSmsFailedObserverTopic, null);
+                                           function notifyResult(rv, sms) {
+      //TODO bug 832140 handle !Components.isSuccessCode(rv)
+      options.request.notifySendMessageFailed(error);
+      Services.obs.notifyObservers(sms, kSmsFailedObserverTopic, null);
+    });
   },
 
   /**
    * Handle data call state changes.
    */
   handleDataCallState: function handleDataCallState(datacall) {
     let data = this.rilContext.data;
 
@@ -1594,20 +1592,20 @@ RadioInterfaceLayer.prototype = {
     voicemailInfo.displayName = message.alphaId;
 
     this._sendTargetMessage("voicemail", "RIL:VoicemailInfoChanged", voicemailInfo);
   },
 
   handleICCInfoChange: function handleICCInfoChange(message) {
     let oldIcc = this.rilContext.icc;
     this.rilContext.icc = message;
-   
+
     let iccInfoChanged = !oldIcc ||
                          oldIcc.iccid != message.iccid ||
-                         oldIcc.mcc != message.mcc || 
+                         oldIcc.mcc != message.mcc ||
                          oldIcc.mnc != message.mnc ||
                          oldIcc.spn != message.spn ||
                          oldIcc.isDisplayNetworkNameRequired != message.isDisplayNetworkNameRequired ||
                          oldIcc.isDisplaySpnRequired != message.isDisplaySpnRequired ||
                          oldIcc.msisdn != message.msisdn;
     if (!iccInfoChanged) {
       return;
     }
@@ -1724,17 +1722,17 @@ RadioInterfaceLayer.prototype = {
 
   // Flag to determine the radio state to start with when we boot up. It
   // corresponds to the 'ril.radio.disabled' setting from the UI.
   _radioEnabled: null,
 
   // Flag to ignore any radio power change requests during We're changing
   // the radio power.
   _changingRadioPower: false,
-  
+
   // Flag to determine whether we reject a waiting call directly or we
   // notify the UI of a waiting call. It corresponds to the
   // 'ril.callwaiting.enbled' setting from the UI.
   _callWaitingEnabled: null,
 
   // APN data for making data calls.
   dataCallSettings: {},
   dataCallSettingsMMS: {},
@@ -1823,19 +1821,19 @@ RadioInterfaceLayer.prototype = {
 
   handleError: function handleError(aErrorMessage) {
     debug("There was an error while reading RIL settings.");
 
     // Default radio to on.
     this._radioEnabled = true;
     this._ensureRadioState();
 
-    // Clean data call setting. 
+    // Clean data call setting.
     this.dataCallSettings = {};
-    this.dataCallSettings["enabled"] = false; 
+    this.dataCallSettings["enabled"] = false;
   },
 
   // nsIRadioWorker
 
   worker: null,
 
   // nsIRadioInterfaceLayer
 
@@ -1890,17 +1888,17 @@ RadioInterfaceLayer.prototype = {
     this.worker.postMessage({rilMessageType: "answerCall",
                              callIndex: callIndex});
   },
 
   rejectCall: function rejectCall(callIndex) {
     this.worker.postMessage({rilMessageType: "rejectCall",
                              callIndex: callIndex});
   },
- 
+
   holdCall: function holdCall(callIndex) {
     this.worker.postMessage({rilMessageType: "holdCall",
                              callIndex: callIndex});
   },
 
   resumeCall: function resumeCall(callIndex) {
     this.worker.postMessage({rilMessageType: "resumeCall",
                              callIndex: callIndex});
@@ -2430,38 +2428,32 @@ RadioInterfaceLayer.prototype = {
     options.number = number;
     options.requestStatusReport = true;
     if (options.segmentMaxSeq > 1) {
       options.segmentRef16Bit = this.segmentRef16Bit;
       options.segmentRef = this.nextSegmentRef;
     }
 
     let timestamp = Date.now();
-    let id = gSmsDatabaseService.saveSendingMessage(number, message, timestamp);
-    let messageClass = RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_NORMAL];
     let deliveryStatus = options.requestStatusReport
                        ? RIL.GECKO_SMS_DELIVERY_STATUS_PENDING
                        : RIL.GECKO_SMS_DELIVERY_STATUS_NOT_APPLICABLE;
-    let sms = gSmsService.createSmsMessage(id,
-                                           DOM_SMS_DELIVERY_SENDING,
-                                           deliveryStatus,
-                                           null,
-                                           number,
-                                           message,
-                                           messageClass,
-                                           timestamp,
-                                           true);
-    Services.obs.notifyObservers(sms, kSmsSendingObserverTopic, null);
+    let id = gSmsDatabaseService.saveSendingMessage(number, message, deliveryStatus, timestamp,
+                                                    function notifyResult(rv, sms) {
+      //TODO bug 832140 handle !Components.isSuccessCode(rv)
+      Services.obs.notifyObservers(sms, kSmsSendingObserverTopic, null);
 
-    // Keep current SMS message info for sent/delivered notifications
-    options.envelopeId = this.createSmsEnvelope({request: request,
-                                                 sms: sms,
-                                                 requestStatusReport: options.requestStatusReport});
-
-    this.worker.postMessage(options);
+      // Keep current SMS message info for sent/delivered notifications
+      options.envelopeId = this.createSmsEnvelope({
+          request: request,
+          sms: sms,
+          requestStatusReport: options.requestStatusReport
+      });
+      this.worker.postMessage(options);
+    }.bind(this));
   },
 
   registerDataCallCallback: function registerDataCallCallback(callback) {
     if (this._datacall_callbacks) {
       if (this._datacall_callbacks.indexOf(callback) != -1) {
         throw new Error("Already registered this callback!");
       }
     } else {
@@ -2802,18 +2794,18 @@ RILNetworkInterface.prototype = {
       return;
     }
 
     this.httpProxyHost = this.dataCallSettings["httpProxyHost"];
     this.httpProxyPort = this.dataCallSettings["httpProxyPort"];
 
     debug("Going to set up data connection with APN " + this.dataCallSettings["apn"]);
     this.mRIL.setupDataCall(RIL.DATACALL_RADIOTECHNOLOGY_GSM,
-                            this.dataCallSettings["apn"], 
-                            this.dataCallSettings["user"], 
+                            this.dataCallSettings["apn"],
+                            this.dataCallSettings["user"],
                             this.dataCallSettings["passwd"],
                             RIL.DATACALL_AUTH_PAP_OR_CHAP, "IP");
     this.connecting = true;
   },
 
   reset: function reset() {
     let apnRetryTimer;
     this.connecting = false;
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -1202,16 +1202,21 @@ this.PDU_PI_RESERVED            = 0x78;
 // others    see 3GPP TS 27.005 clause 3.2.5
 this.PDU_FCS_OK                       = 0x00;
 this.PDU_FCS_PROTOCOL_ERROR           = 0x6F;
 this.PDU_FCS_MEMORY_CAPACITY_EXCEEDED = 0XD3;
 this.PDU_FCS_USAT_BUSY                = 0XD4;
 this.PDU_FCS_USIM_DATA_DOWNLOAD_ERROR = 0xD5;
 this.PDU_FCS_RESERVED                 = 0xE0;
 this.PDU_FCS_UNSPECIFIED              = 0xFF;
+// Special internal value that means we should not acknowledge an
+// incoming text right away, but need to wait for other components
+// (e.g. storage) to complete. This can be any value, so long it
+// doesn't conflict with the PDU_FCS_* constants above.
+this.MOZ_FCS_WAIT_FOR_EXPLICIT_ACK    = 0x0F;
 
 // ST - Status
 // Bit 7..0 = 000xxxxx, short message transaction completed
 this.PDU_ST_0_RECEIVED             = 0x00;
 this.PDU_ST_0_FORWARDED_NO_CONFIRM = 0x01;
 this.PDU_ST_0_REPLACED_BY_SC       = 0x02;
 this.PDU_ST_0_RESERVED_BEGIN       = 0x03;
 this.PDU_ST_0_SC_SPECIFIC_BEGIN    = 0x10;
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -1708,16 +1708,29 @@ let RIL = {
   acknowledgeSMS: function acknowledgeSMS(success, cause) {
     let token = Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE);
     Buf.writeUint32(2);
     Buf.writeUint32(success ? 1 : 0);
     Buf.writeUint32(cause);
     Buf.sendParcel();
   },
 
+  /**
+   * Acknowledge the receipt and handling of an SMS.
+   *
+   * @param success
+   *        Boolean indicating whether the message was successfuly handled.
+   */
+  ackSMS: function ackSMS(options) {
+    if (options.result == PDU_FCS_RESERVED) {
+      return;
+    }
+    this.acknowledgeSMS(options.result == PDU_FCS_OK, options.result);
+  },
+
   setCellBroadcastSearchList: function setCellBroadcastSearchList(options) {
     try {
       let str = options.searchListStr;
       this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str);
     } catch (e) {
       if (DEBUG) {
         debug("Invalid Cell Broadcast search list: " + e);
       }
@@ -3505,16 +3518,17 @@ let RIL = {
             // ME shall pass the message transparently to the UICC using the
             // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1
             this.dataDownloadViaSMSPP(message);
 
             // `the ME shall not display the message, or alert the user of a
             // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1
             return PDU_FCS_RESERVED;
           }
+          // Fall through!
 
           // If the service "data download via SMS-PP" is not available in the
           // (U)SIM Service Table, ..., then the ME shall store the message in
           // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1
         default:
           this.writeSmsToSIM(message);
           break;
       }
@@ -3544,24 +3558,28 @@ let RIL = {
         delete message.data;
       } else {
         message.fullBody = message.body;
         delete message.body;
       }
     }
 
     if (message) {
+      message.result = PDU_FCS_OK;
+      if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
+        // `MS shall ensure that the message has been to the SMS data field in
+        // the (U)SIM before sending an ACK to the SC.`  ~ 3GPP 23.038 clause 4
+        message.result = PDU_FCS_RESERVED;
+      }
       message.rilMessageType = "sms-received";
       this.sendDOMMessage(message);
-    }
-
-    if (message && message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
-      // `MS shall ensure that the message has been to the SMS data field in
-      // the (U)SIM before sending an ACK to the SC.`  ~ 3GPP 23.038 clause 4
-      return PDU_FCS_RESERVED;
+
+      // We will acknowledge receipt of the SMS after we try to store it
+      // in the database.
+      return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
     }
 
     return PDU_FCS_OK;
   },
 
   /**
    * Helper for processing SMS-STATUS-REPORT PDUs.
    *
@@ -4959,20 +4977,21 @@ RIL[UNSOLICITED_RESPONSE_CALL_STATE_CHAN
 };
 RIL[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() {
   if (DEBUG) debug("Network state changed, re-requesting phone state and ICC status");
   this.getICCStatus();
   this.requestNetworkInfo();
 };
 RIL[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) {
   let result = this._processSmsDeliver(length);
-  if (result != PDU_FCS_RESERVED) {
-    // Not reserved FCS values, send ACK now.
-    this.acknowledgeSMS(result == PDU_FCS_OK, result);
+  if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
+    return;
   }
+  // Not reserved FCS values, send ACK now.
+  this.acknowledgeSMS(result == PDU_FCS_OK, result);
 };
 RIL[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) {
   let result = this._processSmsStatusReport(length);
   this.acknowledgeSMS(result == PDU_FCS_OK, result);
 };
 RIL[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
   let info = Buf.readUint32List();
   //TODO