Bug 749856 - Part 3: handle WAP Push notification, r=philikon
authorVicamo Yang <vyang@mozilla.com>
Mon, 04 Jun 2012 13:04:49 +0800
changeset 99785 abb9036641e2f31f9b270bce23e03fabb7b98102
parent 99784 8d6c0457e630bda1675f6849afe62d5f6a041cf4
child 99786 728c759ed6544089c251612e12ce8ed2fa34c68a
push id1116
push userlsblakk@mozilla.com
push dateMon, 16 Jul 2012 19:38:18 +0000
treeherdermozilla-beta@95f959a8b4dc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilikon
bugs749856
milestone15.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 749856 - Part 3: handle WAP Push notification, r=philikon
b2g/chrome/content/shell.js
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
dom/mms/Makefile.in
dom/mms/interfaces/Makefile.in
dom/mms/interfaces/nsIWapPushApplication.idl
dom/mms/src/Makefile.in
dom/mms/src/ril/MmsService.js
dom/mms/src/ril/MmsService.manifest
dom/mms/src/ril/WapPushManager.js
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -646,15 +646,23 @@ SettingsListener.observe('language.curre
 
 (function RILSettingsToPrefs() {
   ['ril.data.enabled', 'ril.data.roaming.enabled'].forEach(function(key) {
     SettingsListener.observe(key, false, function(value) {
       Services.prefs.setBoolPref(key, value);
     });
   });
 
-  ['ril.data.apn', 'ril.data.user', 'ril.data.passwd'].forEach(function(key) {
+  let strPrefs = ['ril.data.apn', 'ril.data.user', 'ril.data.passwd',
+                  'ril.data.mmsc', 'ril.data.mmsproxy'];
+  strPrefs.forEach(function(key) {
     SettingsListener.observe(key, false, function(value) {
       Services.prefs.setCharPref(key, value);
     });
   });
+
+  ['ril.data.mmsport'].forEach(function(key) {
+    SettingsListener.observe(key, false, function(value) {
+      Services.prefs.setIntPref(key, value);
+    });
+  });
 })();
 
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -169,16 +169,19 @@
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
+#ifdef MOZ_B2G_RIL
+@BINPATH@/components/dom_mms.xpt
+#endif
 @BINPATH@/components/dom_power.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_settings.xpt
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_sms.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_threads.xpt
@@ -420,16 +423,18 @@
 @BINPATH@/components/SettingsService.manifest
 @BINPATH@/components/nsFilePicker.js
 @BINPATH@/components/nsFilePicker.manifest
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/NetworkManager.manifest
 @BINPATH@/components/NetworkManager.js
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
+@BINPATH@/components/MmsService.manifest
+@BINPATH@/components/MmsService.js
 @BINPATH@/components/RILContentHelper.js
 @BINPATH@/components/SmsDatabaseService.manifest
 @BINPATH@/components/SmsDatabaseService.js
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 #endif
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -174,16 +174,19 @@
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
+#ifdef MOZ_B2G_RIL
+@BINPATH@/components/dom_mms.xpt
+#endif
 @BINPATH@/components/dom_power.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_settings.xpt
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_sms.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_traversal.xpt
@@ -403,16 +406,18 @@
 @BINPATH@/components/nsInputListAutoComplete.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
+@BINPATH@/components/MmsService.manifest
+@BINPATH@/components/MmsService.js
 @BINPATH@/components/RILContentHelper.js
 @BINPATH@/components/SmsDatabaseService.manifest
 @BINPATH@/components/SmsDatabaseService.js
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 #endif
--- a/dom/mms/Makefile.in
+++ b/dom/mms/Makefile.in
@@ -6,17 +6,17 @@ DEPTH            = ../..
 topsrcdir        = @top_srcdir@
 srcdir           = @srcdir@
 VPATH            = @srcdir@
 
 relativesrcdir   = dom/mms
 
 include $(DEPTH)/config/autoconf.mk
 
-PARALLEL_DIRS = src
+PARALLEL_DIRS = interfaces src
 
 ifdef MOZ_B2G_RIL
 ifdef ENABLE_TESTS
 XPCSHELL_TESTS = tests
 endif
 endif
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/mms/interfaces/Makefile.in
@@ -0,0 +1,19 @@
+# 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/.
+
+DEPTH            = ../../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+XPIDL_MODULE = dom_mms
+
+XPIDLSRCS = \
+  nsIMmsService.idl \
+  nsIWapPushApplication.idl \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/mms/interfaces/nsIWapPushApplication.idl
@@ -0,0 +1,31 @@
+/* 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"
+
+/**
+ * Handle WAP Push notifications.
+ */
+[scriptable, uuid(fd6f7f6b-a67e-4892-930d-fca864df8fe7)]
+interface nsIWapPushApplication : nsISupports
+{
+  /**
+   * Receive WAP Push message.
+   *
+   * @param aData
+   *        An array containing raw PDU data.
+   * @param aLength
+   *        Length of aData.
+   * @param aOffset
+   *        Start offset of aData containing message body of the Push PDU.
+   * @param options
+   *        An object containing various attributes from lower transport layer.
+   */
+  [implicit_jscontext]
+  void receiveWapPush([array, size_is(aLength)] in octet aData,
+                      in unsigned long                   aLength,
+                      in unsigned long                   aOffset,
+                      in jsval                           aOptions);
+};
+
--- a/dom/mms/src/Makefile.in
+++ b/dom/mms/src/Makefile.in
@@ -7,16 +7,21 @@ topsrcdir        = @top_srcdir@
 srcdir           = @srcdir@
 VPATH            = \
   $(srcdir) \
   $(NULL)
 
 include $(DEPTH)/config/autoconf.mk
 
 ifdef MOZ_B2G_RIL
+EXTRA_COMPONENTS = \
+  ril/MmsService.js \
+  ril/MmsService.manifest \
+  $(NULL)
+
 EXTRA_JS_MODULES = \
   ril/mms_consts.js \
   ril/MmsPduHelper.jsm \
   ril/wap_consts.js \
   ril/WapPushManager.js \
   ril/WspPduHelper.jsm \
   $(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/MmsService.js
@@ -0,0 +1,270 @@
+/* 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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const RIL_MMSSERVICE_CONTRACTID = "@mozilla.org/mms/rilmmsservice;1";
+const RIL_MMSSERVICE_CID = Components.ID("{217ddd76-75db-4210-955d-8806cd8d87f9}");
+
+const DEBUG = false;
+
+const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
+const kXpcomShutdownObserverTopic        = "xpcom-shutdown";
+
+// HTTP status codes:
+// @see http://tools.ietf.org/html/rfc2616#page-39
+const HTTP_STATUS_OK = 200;
+
+XPCOMUtils.defineLazyServiceGetter(this, "gpps",
+                                   "@mozilla.org/network/protocol-proxy-service;1",
+                                   "nsIProtocolProxyService");
+
+XPCOMUtils.defineLazyGetter(this, "MMS", function () {
+  let MMS = {};
+  Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
+  return MMS;
+});
+
+/**
+ * MmsService
+ */
+function MmsService() {
+  Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
+  Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic, false);
+}
+MmsService.prototype = {
+
+  classID:   RIL_MMSSERVICE_CID,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMmsService,
+                                         Ci.nsIWapPushApplication,
+                                         Ci.nsIObserver,
+                                         Ci.nsIProtocolProxyFilter]),
+
+  proxyInfo: null,
+  MMSC: null,
+
+  /** MMS proxy filter reference count. */
+  proxyFilterRefCount: 0,
+
+  /**
+   * Acquire referece-counted MMS proxy filter.
+   */
+  acquireProxyFilter: function acquireProxyFilter() {
+    if (!this.proxyFilterRefCount) {
+      debug("Register proxy filter");
+      gpps.registerFilter(this, 0);
+    }
+    this.proxyFilterRefCount++;
+  },
+
+  /**
+   * Release referece-counted MMS proxy filter.
+   */
+  releaseProxyFilter: function releaseProxyFilter() {
+    this.proxyFilterRefCount--;
+    if (this.proxyFilterRefCount <= 0) {
+      this.proxyFilterRefCount = 0;
+
+      debug("Unregister proxy filter");
+      gpps.unregisterFilter(this);
+    }
+  },
+
+  /**
+   * Send MMS request to MMSC.
+   *
+   * @param method
+   *        "GET" or "POST".
+   * @param url
+   *        Target url string.
+   * @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, callback) {
+    let that = this;
+    function releaseProxyFilterAndCallback(status, data) {
+      // Always release proxy filter before callback.
+      that.releaseProxyFilter(false);
+      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";
+      xhr.setRequestHeader("Content-Length", 0);
+
+      // Setup event listeners
+      xhr.onerror = function () {
+        debug("xhr error, response headers: " + xhr.getAllResponseHeaders());
+        releaseProxyFilterAndCallback(xhr.status, null);
+      };
+      xhr.onreadystatechange = function () {
+        if (xhr.readyState != Ci.nsIXMLHttpRequest.DONE) {
+          return;
+        }
+
+        let data = null;
+        switch (xhr.status) {
+          case HTTP_STATUS_OK: {
+            debug("xhr success, response headers: " + xhr.getAllResponseHeaders());
+
+            let array = new Uint8Array(xhr.response);
+            if (false) {
+              for (let begin = 0; begin < array.length; begin += 20) {
+                debug("res: " + JSON.stringify(array.subarray(begin, begin + 20)));
+              }
+            }
+
+            data = {array: array, offset: 0};
+            break;
+          }
+          default: {
+            debug("xhr done, but status = " + xhr.status);
+            break;
+          }
+        }
+
+        releaseProxyFilterAndCallback(xhr.status, data);
+      }
+
+      // Send request
+      xhr.send();
+    } catch (e) {
+      debug("xhr error, can't send: " + e.message);
+      releaseProxyFilterAndCallback(0, null);
+    }
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   */
+  parseStreamAndDispatch: function parseStreamAndDispatch(data) {
+    let msg = MMS.PduHelper.parse(data, null);
+    if (!msg) {
+      return;
+    }
+    debug("parseStreamAndDispatch: msg = " + JSON.stringify(msg));
+
+    switch (msg.type) {
+      case MMS.MMS_PDU_TYPE_NOTIFICATION_IND:
+        this.handleNotificationIndication(msg);
+        break;
+      default:
+        debug("Unsupported X-MMS-Message-Type: " + msg.type);
+        break;
+    }
+  },
+
+  /**
+   * Handle incoming M-Notification.ind PDU.
+   *
+   * @param msg
+   *        The MMS message object.
+   */
+  handleNotificationIndication: function handleNotificationIndication(msg) {
+    let url = msg.headers["x-mms-content-location"].uri;
+    this.sendMmsRequest("GET", url, (function (status, data) {
+      if (data) {
+        this.parseStreamAndDispatch(data);
+      }
+    }).bind(this));
+  },
+
+  /**
+   * Update proxyInfo & MMSC from preferences.
+   *
+   * @param enabled
+   *        Enable or disable MMS proxy.
+   */
+  updateProxyInfo: function updateProxyInfo(enabled) {
+    try {
+      if (enabled) {
+        this.MMSC = Services.prefs.getCharPref("ril.data.mmsc");
+        this.proxyInfo = gpps.newProxyInfo("http",
+                                           Services.prefs.getCharPref("ril.data.mmsproxy"),
+                                           Services.prefs.getIntPref("ril.data.mmsport"),
+                                           Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST,
+                                           -1, null);
+        debug("updateProxyInfo: "
+              + JSON.stringify({MMSC: this.MMSC, proxyInfo: this.proxyInfo}));
+        return;
+      }
+    } catch (e) {
+      // Failed to refresh proxy info from settings. Fallback to disable.
+    }
+
+    this.MMSC = null;
+    this.proxyInfo = null;
+  },
+
+  // nsIMmsService
+
+  hasSupport: function hasSupport() {
+    return true;
+  },
+
+  // nsIWapPushApplication
+
+  receiveWapPush: function receiveWapPush(array, length, offset, options) {
+    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.updateProxyInfo(iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED);
+        }
+        break;
+      }
+      case kXpcomShutdownObserverTopic: {
+        Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
+        Services.obs.removeObserver(this, kNetworkInterfaceStateChangedTopic);
+        break;
+      }
+    }
+  },
+
+  // nsIProtocolProxyFilter
+
+  applyFilter: function applyFilter(service, uri, proxyInfo) {
+    if (uri.prePath == this.MMSC) {
+      debug("applyFilter: match " + uri.spec);
+      return this.proxyInfo;
+    }
+
+    return proxyInfo;
+  },
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([MmsService]);
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-@- MmsService: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/MmsService.manifest
@@ -0,0 +1,4 @@
+# MmsService.js
+component {217ddd76-75db-4210-955d-8806cd8d87f9} MmsService.js
+contract @mozilla.org/mms/rilmmsservice;1 {217ddd76-75db-4210-955d-8806cd8d87f9}
+category profile-after-change MmsService @mozilla.org/mms/rilmmsservice;1
--- a/dom/mms/src/ril/WapPushManager.js
+++ b/dom/mms/src/ril/WapPushManager.js
@@ -1,16 +1,19 @@
 /* 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/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
 Cu.import("resource://gre/modules/WspPduHelper.jsm");
 
 const DEBUG = false; // set to true to see debug messages
 
 /**
  * Helpers for WAP PDU processing.
  */
 let WapPushManager = {
@@ -33,19 +36,24 @@ let WapPushManager = {
     }
 
     let appid = options.headers["x-wap-application-id"];
     if (!appid) {
       debug("Push message doesn't contains X-Wap-Application-Id.");
       return;
     }
 
-    // FIXME: hand over to others
-
-    debug("No WAP Push application registered for " + appid);
+    if (appid == "x-wap-application:mms.ua") {
+      let mmsService = Cc["@mozilla.org/mms/rilmmsservice;1"]
+                       .getService(Ci.nsIMmsService);
+      mmsService.QueryInterface(Ci.nsIWapPushApplication)
+                .receiveWapPush(data.array, data.array.length, data.offset, options);
+    } else {
+      debug("No WAP Push application registered for " + appid);
+    }
   },
 
   /**
    * @param array
    *        A Uint8Array or an octet array representing raw PDU data.
    * @param length
    *        Length of the array.
    * @param offset