Bug 817474 - B2G MMS: handleNotificationIndication()->sendMmsRequest() cannot get the expected HTTP response (part 2). r=vicamo
authorGene Lian <clian@mozilla.com>
Thu, 13 Dec 2012 14:24:14 +0800
changeset 125352 ba26dc1c6267bd22235e3ffca31824dba5ada4ec
parent 125351 408694ed2d3ee9b975cb1c146a1bf75a873bec87
child 125353 163f1b12bdef4a50726f85830d0552f98d364ba6
child 126680 345a115d32384f6d199f203c826ae1fc7f59a142
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvicamo
bugs817474
milestone20.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 817474 - B2G MMS: handleNotificationIndication()->sendMmsRequest() cannot get the expected HTTP response (part 2). r=vicamo
dom/mms/src/ril/MmsService.js
--- a/dom/mms/src/ril/MmsService.js
+++ b/dom/mms/src/ril/MmsService.js
@@ -33,30 +33,38 @@ const STORAGE_STREAM_SEGMENT_SIZE = 4096
 // @see http://tools.ietf.org/html/rfc2616#page-39
 const HTTP_STATUS_OK = 200;
 
 const CONFIG_SEND_REPORT_NEVER       = 0;
 const CONFIG_SEND_REPORT_DEFAULT_NO  = 1;
 const CONFIG_SEND_REPORT_DEFAULT_YES = 2;
 const CONFIG_SEND_REPORT_ALWAYS      = 3;
 
+const TIME_TO_BUFFER_MMS_REQUESTS    = 30000;
+
 XPCOMUtils.defineLazyServiceGetter(this, "gpps",
                                    "@mozilla.org/network/protocol-proxy-service;1",
                                    "nsIProtocolProxyService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyGetter(this, "MMS", function () {
   let MMS = {};
   Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
   return MMS;
 });
 
+XPCOMUtils.defineLazyGetter(this, "gRIL", function () {
+  return Cc["@mozilla.org/telephony/system-worker-manager;1"].
+           getService(Ci.nsIInterfaceRequestor).
+           getInterface(Ci.nsIRadioInterfaceLayer);
+});
+
 /**
  * MmsService
  */
 function MmsService() {
   Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
   Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic, false);
   this.mmsProxySettings.forEach(function(name) {
     Services.prefs.addObserver(name, this, false);
@@ -103,23 +111,29 @@ MmsService.prototype = {
   mmsProxy: null,
   mmsPort: null,
   mmsProxyInfo: null,
   mmsProxySettings: ["ril.mms.mmsc",
                      "ril.mms.mmsproxy",
                      "ril.mms.mmsport"],
   mmsNetworkConnected: false,
 
-  /** MMS proxy filter reference count. */
-  proxyFilterRefCount: 0,
+  /** MMS network connection reference count. */
+  mmsConnRefCount: 0,
 
   // WebMMS
   urlUAProf: null,
   tagnameUAProf: null,
 
+  // A queue to buffer the MMS HTTP requests when the MMS network
+  // is not yet connected. The buffered requests will be cleared
+  // if the MMS network fails to be connected within a timer.
+  mmsRequestQueue: [],
+  timerToClearQueue: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
+
   /**
    * Calculate Whether or not should we enable X-Mms-Report-Allowed.
    *
    * @param config
    *        Current config value.
    * @param wish
    *        Sender wish. Could be undefined, false, or true.
    */
@@ -129,36 +143,77 @@ MmsService.prototype = {
       if (wish != null) {
         config += (wish ? 1 : -1);
       }
     }
     return config >= CONFIG_SEND_REPORT_DEFAULT_YES;
   },
 
   /**
-   * Acquire referece-counted MMS proxy filter.
+   * Acquire the MMS network connection.
+   *
+   * @param method
+   *        "GET" or "POST".
+   * @param url
+   *        Target url string.
+   * @param istream [optional]
+   *        An nsIInputStream instance as data source to be sent.
+   * @param callback
+   *        A callback function that takes two arguments: one for http status,
+   *        the other for wrapped PDU data for further parsing.
+   *
+   * @return true if the MMS network connection is acquired; false otherwise.
    */
-  acquireProxyFilter: function acquireProxyFilter() {
-    if (!this.proxyFilterRefCount) {
-      debug("Register proxy filter");
+  acquireMmsConnection: function acquireMmsConnection(method, url, istream, callback) {
+    // If the MMS network is not yet connected, buffer the
+    // MMS request and try to setup the MMS network first.
+    if (!this.mmsNetworkConnected) {
+      debug("acquireMmsConnection: " +
+            "buffer the MMS request and setup the MMS data call.");
+      this.mmsRequestQueue.push({method: method,
+                                 url: url,
+                                 istream: istream,
+                                 callback: callback});
+      gRIL.setupDataCallByType("mms");
+
+      // Set a timer to clear the buffered MMS requests if the
+      // MMS network fails to be connected within a time period.
+      this.timerToClearQueue.initWithCallback(function timerToClearQueueCb() {
+        debug("timerToClearQueueCb: clear the buffered MMS requests due to " +
+              "the timeout: number: " + this.mmsRequestQueue.length);
+        while (this.mmsRequestQueue.length) {
+          let mmsRequest = this.mmsRequestQueue.shift();
+          if (mmsRequest.callback) {
+            mmsRequest.callback(0, null);
+          }
+        }
+      }.bind(this), TIME_TO_BUFFER_MMS_REQUESTS, Ci.nsITimer.TYPE_ONE_SHOT);
+      return false;
+    }
+
+    if (!this.mmsConnRefCount) {
+      debug("acquireMmsConnection: register the MMS proxy filter.");
       gpps.registerFilter(this, 0);
     }
-    this.proxyFilterRefCount++;
+    this.mmsConnRefCount++;
+    return true;
   },
 
   /**
-   * Release referece-counted MMS proxy filter.
+   * Release the MMS network connection.
    */
-  releaseProxyFilter: function releaseProxyFilter() {
-    this.proxyFilterRefCount--;
-    if (this.proxyFilterRefCount <= 0) {
-      this.proxyFilterRefCount = 0;
+  releaseMmsConnection: function releaseMmsConnection() {
+    this.mmsConnRefCount--;
+    if (this.mmsConnRefCount <= 0) {
+      this.mmsConnRefCount = 0;
 
-      debug("Unregister proxy filter");
+      debug("releaseMmsConnection: " +
+            "unregister the MMS proxy filter and deactivate the MMS data call.");
       gpps.unregisterFilter(this);
+      gRIL.deactivateDataCallByType("mms");
     }
   },
 
   /**
    * Send MMS request to MMSC.
    *
    * @param method
    *        "GET" or "POST".
@@ -166,27 +221,32 @@ MmsService.prototype = {
    *        Target url string.
    * @param istream [optional]
    *        An nsIInputStream instance as data source to be sent.
    * @param callback
    *        A callback function that takes two arguments: one for http status,
    *        the other for wrapped PDU data for further parsing.
    */
   sendMmsRequest: function sendMmsRequest(method, url, istream, callback) {
+    debug("sendMmsRequest: method: " + method + "url: " + url +
+          "istream: " + istream + "callback: " + callback);
+
+    if (!this.acquireMmsConnection(method, url, istream, callback)) {
+      return;
+    }
+
     let that = this;
-    function releaseProxyFilterAndCallback(status, data) {
-      // Always release proxy filter before callback.
-      that.releaseProxyFilter();
+    function releaseMmsConnectionAndCallback(status, data) {
+      // Always release the MMS network connection before callback.
+      that.releaseMmsConnection();
       if (callback) {
         callback(status, data);
       }
     }
 
-    this.acquireProxyFilter();
-
     try {
       let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
                 .createInstance(Ci.nsIXMLHttpRequest);
 
       // Basic setups
       xhr.open(method, url, true);
       xhr.responseType = "arraybuffer";
       if (istream) {
@@ -198,17 +258,17 @@ MmsService.prototype = {
 
       if(this.urlUAProf !== "") {
         xhr.setRequestHeader(this.tagnameUAProf, this.urlUAProf);
       }
 
       // Setup event listeners
       xhr.onerror = function () {
         debug("xhr error, response headers: " + xhr.getAllResponseHeaders());
-        releaseProxyFilterAndCallback(xhr.status, null);
+        releaseMmsConnectionAndCallback(xhr.status, null);
       };
       xhr.onreadystatechange = function () {
         if (xhr.readyState != Ci.nsIXMLHttpRequest.DONE) {
           return;
         }
 
         let data = null;
         switch (xhr.status) {
@@ -226,24 +286,24 @@ MmsService.prototype = {
             break;
           }
           default: {
             debug("xhr done, but status = " + xhr.status);
             break;
           }
         }
 
-        releaseProxyFilterAndCallback(xhr.status, data);
+        releaseMmsConnectionAndCallback(xhr.status, data);
       }
 
       // Send request
       xhr.send(istream);
     } catch (e) {
       debug("xhr error, can't send: " + e.message);
-      releaseProxyFilterAndCallback(0, null);
+      releaseMmsConnectionAndCallback(0, null);
     }
   },
 
   /**
    * Send M-NotifyResp.ind back to MMSC.
    *
    * @param tid
    *        X-Mms-Transaction-ID of the message.
@@ -337,17 +397,19 @@ MmsService.prototype = {
     let istream = MMS.PduHelper.compose(null, msg);
     if (!istream) {
       debug("sendSendRequest: failed to compose M-Send.ind PDU");
       callback(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
       return;
     }
 
     this.sendMmsRequest("POST", this.MMSC, istream, (function (status, data) {
-      if (!data) {
+      if (status != HTTP_STATUS_OK) {
+        callback(MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE, null);
+      } else if (!data) {
         callback(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
       } else if (!this.parseStreamAndDispatch(data, {msg: msg, callback: callback})) {
         callback(MMS.MMS_PDU_RESPONSE_ERROR_UNSUPPORTED_MESSAGE, null);
       }
     }).bind(this));
   },
 
   /**
@@ -473,34 +535,37 @@ MmsService.prototype = {
     }
 
     if (status == MMS.MMS_PDU_ERROR_OK) {
       // `This ID SHALL always be present after the MMS Proxy-Relay accepted
       // the corresponding M-Send.req PDU. The ID enables a MMS Client to match
       // delivery reports or read-report PDUs with previously sent MM.`
       let messageId = msg.headers["message-id"];
       options.msg.headers["message-id"] = messageId;
-    } else if ((status >= MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE)
-               && (status < MMS.MMS_PDU_ERROR_PERMANENT_FAILURE)) {
+    } else if (this.isTransientError(status)) {
       return;
     }
 
     if (options.callback) {
       options.callback(status, msg);
     }
   },
 
   /**
    * Handle incoming M-Notification.ind PDU.
    *
    * @param msg
    *        The MMS message object.
    */
   handleNotificationIndication: function handleNotificationIndication(msg) {
     function callback(status, retr) {
+      if (this.isTransientError(status)) {
+        return;
+      }
+
       let tid = msg.headers["x-mms-transaction-id"];
 
       // For X-Mms-Report-Allowed
       let wish = msg.headers["x-mms-delivery-report"];
       // `The absence of the field does not indicate any default value.`
       // So we go checking the same field in retrieved message instead.
       if ((wish == null) && retr) {
         wish = retr.headers["x-mms-delivery-report"];
@@ -511,17 +576,19 @@ MmsService.prototype = {
     }
 
     function retrCallback(error, retr) {
       callback.call(this, MMS.translatePduErrorToStatus(error), retr);
     }
 
     let url = msg.headers["x-mms-content-location"].uri;
     this.sendMmsRequest("GET", url, null, (function (status, data) {
-      if (!data) {
+      if (status != HTTP_STATUS_OK) {
+        callback.call(this, MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE, null);
+      } else if (!data) {
         callback.call(this, MMS.MMS_PDU_STATUS_DEFERRED, null);
       } else if (!this.parseStreamAndDispatch(data, retrCallback.bind(this))) {
         callback.call(this, MMS.MMS_PDU_STATUS_UNRECOGNISED, null);
       }
     }).bind(this));
   },
 
   /**
@@ -584,16 +651,27 @@ MmsService.prototype = {
    */
   clearMmsProxySettings: function clearMmsProxySettings() {
     this.mmsc = null;
     this.mmsProxy = null;
     this.mmsPort = null;
     this.mmsProxyInfo = null;
   },
 
+  /**
+   * @param status
+   *        The MMS error type.
+   *
+   * @return true if it's a type of transient error; false otherwise.
+   */
+  isTransientError: function isTransientError(status) {
+    return (status >= MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE &&
+            status < MMS.MMS_PDU_ERROR_PERMANENT_FAILURE);
+  },
+
   // nsIMmsService
 
   hasSupport: function hasSupport() {
     return true;
   },
 
   // nsIWapPushApplication
 
@@ -601,31 +679,49 @@ MmsService.prototype = {
     this.parseStreamAndDispatch({array: array, offset: offset});
   },
 
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
     switch (topic) {
       case kNetworkInterfaceStateChangedTopic: {
-        let iface = subject.QueryInterface(Ci.nsINetworkInterface);
-        if (iface.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
-            iface.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) {
+        this.mmsNetworkConnected =
+          gRIL.getDataCallStateByType("mms") ==
+            Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
+
+        if (!this.mmsNetworkConnected) {
           return;
         }
-        this.mmsNetworkConnected =
-          iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
+
+        debug("Got the MMS network connected! Resend the buffered " +
+              "MMS requests: number: " + this.mmsRequestQueue.length);
+        this.timerToClearQueue.cancel();
+        while (this.mmsRequestQueue.length) {
+          let mmsRequest = this.mmsRequestQueue.shift();
+          this.sendMmsRequest(mmsRequest.method,
+                              mmsRequest.url,
+                              mmsRequest.istream,
+                              mmsRequest.callback);
+        }
         break;
       }
       case kXpcomShutdownObserverTopic: {
         Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
         Services.obs.removeObserver(this, kNetworkInterfaceStateChangedTopic);
         this.mmsProxySettings.forEach(function(name) {
           Services.prefs.removeObserver(name, this);
         }, this);
+        this.timerToClearQueue.cancel();
+        while (this.mmsRequestQueue.length) {
+          let mmsRequest = this.mmsRequestQueue.shift();
+          if (mmsRequest.callback) {
+            mmsRequest.callback(0, null);
+          }
+        }
         break;
       }
       case kPrefenceChangedObserverTopic: {
         try {
           switch (data) {
             case "ril.mms.mmsc":
               this.mmsc = Services.prefs.getCharPref("ril.mms.mmsc");
               break;