dom/mobilemessage/src/gonk/MmsService.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  * vim: sw=2 ts=2 sts=2 et filetype=javascript
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 "use strict";
     9 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    12 Cu.import("resource://gre/modules/Services.jsm");
    14 Cu.import("resource://gre/modules/NetUtil.jsm");
    15 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
    17 const RIL_MMSSERVICE_CONTRACTID = "@mozilla.org/mms/rilmmsservice;1";
    18 const RIL_MMSSERVICE_CID = Components.ID("{217ddd76-75db-4210-955d-8806cd8d87f9}");
    20 let DEBUG = false;
    21 function debug(s) {
    22   dump("-@- MmsService: " + s + "\n");
    23 };
    25 // Read debug setting from pref.
    26 try {
    27   let debugPref = Services.prefs.getBoolPref("mms.debugging.enabled");
    28   DEBUG = DEBUG || debugPref;
    29 } catch (e) {}
    31 const kSmsSendingObserverTopic           = "sms-sending";
    32 const kSmsSentObserverTopic              = "sms-sent";
    33 const kSmsFailedObserverTopic            = "sms-failed";
    34 const kSmsReceivedObserverTopic          = "sms-received";
    35 const kSmsRetrievingObserverTopic        = "sms-retrieving";
    36 const kSmsDeliverySuccessObserverTopic   = "sms-delivery-success";
    37 const kSmsDeliveryErrorObserverTopic     = "sms-delivery-error";
    38 const kSmsReadSuccessObserverTopic       = "sms-read-success";
    39 const kSmsReadErrorObserverTopic         = "sms-read-error";
    41 const NS_XPCOM_SHUTDOWN_OBSERVER_ID      = "xpcom-shutdown";
    42 const kNetworkConnStateChangedTopic      = "network-connection-state-changed";
    43 const kMobileMessageDeletedObserverTopic = "mobile-message-deleted";
    45 const kPrefRilRadioDisabled              = "ril.radio.disabled";
    47 // HTTP status codes:
    48 // @see http://tools.ietf.org/html/rfc2616#page-39
    49 const HTTP_STATUS_OK = 200;
    51 // Non-standard HTTP status for internal use.
    52 const _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS  =  0;
    53 const _HTTP_STATUS_USER_CANCELLED              = -1;
    54 const _HTTP_STATUS_RADIO_DISABLED              = -2;
    55 const _HTTP_STATUS_NO_SIM_CARD                 = -3;
    56 const _HTTP_STATUS_ACQUIRE_TIMEOUT             = -4;
    58 // Non-standard MMS status for internal use.
    59 const _MMS_ERROR_MESSAGE_DELETED               = -1;
    60 const _MMS_ERROR_RADIO_DISABLED                = -2;
    61 const _MMS_ERROR_NO_SIM_CARD                   = -3;
    62 const _MMS_ERROR_SIM_CARD_CHANGED              = -4;
    63 const _MMS_ERROR_SHUTDOWN                      = -5;
    64 const _MMS_ERROR_USER_CANCELLED_NO_REASON      = -6;
    65 const _MMS_ERROR_SIM_NOT_MATCHED               = -7;
    67 const CONFIG_SEND_REPORT_NEVER       = 0;
    68 const CONFIG_SEND_REPORT_DEFAULT_NO  = 1;
    69 const CONFIG_SEND_REPORT_DEFAULT_YES = 2;
    70 const CONFIG_SEND_REPORT_ALWAYS      = 3;
    72 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
    74 const TIME_TO_BUFFER_MMS_REQUESTS    = 30000;
    75 const PREF_TIME_TO_RELEASE_MMS_CONNECTION =
    76   Services.prefs.getIntPref("network.gonk.ms-release-mms-connection");
    78 const kPrefRetrievalMode       = 'dom.mms.retrieval_mode';
    79 const RETRIEVAL_MODE_MANUAL    = "manual";
    80 const RETRIEVAL_MODE_AUTOMATIC = "automatic";
    81 const RETRIEVAL_MODE_AUTOMATIC_HOME = "automatic-home";
    82 const RETRIEVAL_MODE_NEVER     = "never";
    84 //Internal const values.
    85 const DELIVERY_RECEIVED       = "received";
    86 const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
    87 const DELIVERY_SENDING        = "sending";
    88 const DELIVERY_SENT           = "sent";
    89 const DELIVERY_ERROR          = "error";
    91 const DELIVERY_STATUS_SUCCESS        = "success";
    92 const DELIVERY_STATUS_PENDING        = "pending";
    93 const DELIVERY_STATUS_ERROR          = "error";
    94 const DELIVERY_STATUS_REJECTED       = "rejected";
    95 const DELIVERY_STATUS_MANUAL         = "manual";
    96 const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
    98 const PREF_SEND_RETRY_COUNT =
    99   Services.prefs.getIntPref("dom.mms.sendRetryCount");
   101 const PREF_SEND_RETRY_INTERVAL =
   102   Services.prefs.getIntPref("dom.mms.sendRetryInterval");
   104 const PREF_RETRIEVAL_RETRY_COUNT =
   105   Services.prefs.getIntPref("dom.mms.retrievalRetryCount");
   107 const PREF_RETRIEVAL_RETRY_INTERVALS = (function() {
   108   let intervals =
   109     Services.prefs.getCharPref("dom.mms.retrievalRetryIntervals").split(",");
   110   for (let i = 0; i < PREF_RETRIEVAL_RETRY_COUNT; ++i) {
   111     intervals[i] = parseInt(intervals[i], 10);
   112     // If one of the intervals isn't valid (e.g., 0 or NaN),
   113     // assign a 10-minute interval to it as a default.
   114     if (!intervals[i]) {
   115       intervals[i] = 600000;
   116     }
   117   }
   118   intervals.length = PREF_RETRIEVAL_RETRY_COUNT;
   119   return intervals;
   120 })();
   122 const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
   123 const kPrefDefaultServiceId = "dom.mms.defaultServiceId";
   125 XPCOMUtils.defineLazyServiceGetter(this, "gpps",
   126                                    "@mozilla.org/network/protocol-proxy-service;1",
   127                                    "nsIProtocolProxyService");
   129 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
   130                                    "@mozilla.org/uuid-generator;1",
   131                                    "nsIUUIDGenerator");
   133 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageDatabaseService",
   134                                    "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1",
   135                                    "nsIRilMobileMessageDatabaseService");
   137 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
   138                                    "@mozilla.org/mobilemessage/mobilemessageservice;1",
   139                                    "nsIMobileMessageService");
   141 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
   142                                    "@mozilla.org/system-message-internal;1",
   143                                    "nsISystemMessagesInternal");
   145 XPCOMUtils.defineLazyServiceGetter(this, "gRil",
   146                                    "@mozilla.org/ril;1",
   147                                    "nsIRadioInterfaceLayer");
   149 XPCOMUtils.defineLazyGetter(this, "MMS", function() {
   150   let MMS = {};
   151   Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
   152   return MMS;
   153 });
   155 // Internal Utilities
   157 /**
   158  * Return default service Id for MMS.
   159  */
   160 function getDefaultServiceId() {
   161   let id = Services.prefs.getIntPref(kPrefDefaultServiceId);
   162   let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces);
   164   if (id >= numRil || id < 0) {
   165     id = 0;
   166   }
   168   return id;
   169 }
   171 /**
   172  * Return Radio disabled state.
   173  */
   174 function getRadioDisabledState() {
   175   let state;
   176   try {
   177     state = Services.prefs.getBoolPref(kPrefRilRadioDisabled);
   178   } catch (e) {
   179     if (DEBUG) debug("Getting preference 'ril.radio.disabled' fails.");
   180     state = false;
   181   }
   183   return state;
   184 }
   186 /**
   187  * Helper Class to control MMS Data Connection.
   188  */
   189 function MmsConnection(aServiceId) {
   190   this.serviceId = aServiceId;
   191   this.radioInterface = gRil.getRadioInterface(aServiceId);
   192 };
   194 MmsConnection.prototype = {
   195   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
   197   /** MMS proxy settings. */
   198   mmsc:     "",
   199   mmsProxy: "",
   200   mmsPort:  -1,
   202   setApnSetting: function(network) {
   203     this.mmsc = network.mmsc;
   204     this.mmsProxy = network.mmsProxy;
   205     this.mmsPort = network.mmsPort;
   206   },
   208   get proxyInfo() {
   209     if (!this.mmsProxy) {
   210       if (DEBUG) debug("getProxyInfo: MMS proxy is not available.");
   211       return null;
   212     }
   214     let port = this.mmsPort;
   216     if (port <= 0) {
   217       port = 80;
   218       if (DEBUG) debug("getProxyInfo: port is not valid. Set to defult (80).");
   219     }
   221     let proxyInfo =
   222       gpps.newProxyInfo("http", this.mmsProxy, port,
   223                         Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST,
   224                         -1, null);
   225     if (DEBUG) debug("getProxyInfo: " + JSON.stringify(proxyInfo));
   227     return proxyInfo;
   228   },
   230   connected: false,
   232   //A queue to buffer the MMS HTTP requests when the MMS network
   233   //is not yet connected. The buffered requests will be cleared
   234   //if the MMS network fails to be connected within a timer.
   235   pendingCallbacks: [],
   237   /** MMS network connection reference count. */
   238   refCount: 0,
   240   connectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
   242   disconnectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
   244   /**
   245    * Callback when |connectTimer| is timeout or cancelled by shutdown.
   246    */
   247   flushPendingCallbacks: function(status) {
   248     if (DEBUG) debug("flushPendingCallbacks: " + this.pendingCallbacks.length
   249                      + " pending callbacks with status: " + status);
   250     while (this.pendingCallbacks.length) {
   251       let callback = this.pendingCallbacks.shift();
   252       let connected = (status == _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS);
   253       callback(connected, status);
   254     }
   255   },
   257   /**
   258    * Callback when |disconnectTimer| is timeout or cancelled by shutdown.
   259    */
   260   onDisconnectTimerTimeout: function() {
   261     if (DEBUG) debug("onDisconnectTimerTimeout: deactivate the MMS data call.");
   262     if (this.connected) {
   263       this.radioInterface.deactivateDataCallByType("mms");
   264     }
   265   },
   267   init: function() {
   268     Services.obs.addObserver(this, kNetworkConnStateChangedTopic,
   269                              false);
   270     Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
   272     this.connected = this.radioInterface.getDataCallStateByType("mms") ==
   273       Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
   274     // If the MMS network is connected during the initialization, it means the
   275     // MMS network must share the same APN with the mobile network by default.
   276     // Under this case, |networkManager.active| should keep the mobile network,
   277     // which is supposed be an instance of |nsIRilNetworkInterface| for sure.
   278     if (this.connected) {
   279       let networkManager =
   280         Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
   281       let activeNetwork = networkManager.active;
   283       let rilNetwork = activeNetwork.QueryInterface(Ci.nsIRilNetworkInterface);
   284       if (rilNetwork.serviceId != this.serviceId) {
   285         if (DEBUG) debug("Sevice ID between active/MMS network doesn't match.");
   286         return;
   287       }
   289       // Set up the MMS APN setting based on the connected MMS network,
   290       // which is going to be used for the HTTP requests later.
   291       this.setApnSetting(rilNetwork);
   292     }
   293   },
   295   /**
   296    * Return the roaming status of voice call.
   297    *
   298    * @return true if voice call is roaming.
   299    */
   300   isVoiceRoaming: function() {
   301     let isRoaming = this.radioInterface.rilContext.voice.roaming;
   302     if (DEBUG) debug("isVoiceRoaming = " + isRoaming);
   303     return isRoaming;
   304   },
   306   /**
   307    * Get phone number from iccInfo.
   308    *
   309    * If the icc card is gsm card, the phone number is in msisdn.
   310    * @see nsIDOMMozGsmIccInfo
   311    *
   312    * Otherwise, the phone number is in mdn.
   313    * @see nsIDOMMozCdmaIccInfo
   314    */
   315   getPhoneNumber: function() {
   316     let iccInfo = this.radioInterface.rilContext.iccInfo;
   318     if (!iccInfo) {
   319       return null;
   320     }
   322     let number = (iccInfo instanceof Ci.nsIDOMMozGsmIccInfo)
   323                ? iccInfo.msisdn : iccInfo.mdn;
   325     // Workaround an xpconnect issue with undefined string objects.
   326     // See bug 808220
   327     if (number === undefined || number === "undefined") {
   328       return null;
   329     }
   331     return number;
   332   },
   334   /**
   335   * A utility function to get the ICC ID of the SIM card (if installed).
   336   */
   337   getIccId: function() {
   338     let iccInfo = this.radioInterface.rilContext.iccInfo;
   340     if (!iccInfo) {
   341       return null;
   342     }
   344     let iccId = iccInfo.iccid;
   346     // Workaround an xpconnect issue with undefined string objects.
   347     // See bug 808220
   348     if (iccId === undefined || iccId === "undefined") {
   349       return null;
   350     }
   352     return iccId;
   353   },
   355   /**
   356    * Acquire the MMS network connection.
   357    *
   358    * @param callback
   359    *        Callback function when either the connection setup is done,
   360    *        timeout, or failed. Parameters are:
   361    *        - A boolean value indicates whether the connection is ready.
   362    *        - Acquire connection status: _HTTP_STATUS_ACQUIRE_*.
   363    *
   364    * @return true if the callback for MMS network connection is done; false
   365    *         otherwise.
   366    */
   367   acquire: function(callback) {
   368     this.refCount++;
   369     this.connectTimer.cancel();
   370     this.disconnectTimer.cancel();
   372     // If the MMS network is not yet connected, buffer the
   373     // MMS request and try to setup the MMS network first.
   374     if (!this.connected) {
   375       this.pendingCallbacks.push(callback);
   377       let errorStatus;
   378       if (getRadioDisabledState()) {
   379         if (DEBUG) debug("Error! Radio is disabled when sending MMS.");
   380         errorStatus = _HTTP_STATUS_RADIO_DISABLED;
   381       } else if (this.radioInterface.rilContext.cardState != "ready") {
   382         if (DEBUG) debug("Error! SIM card is not ready when sending MMS.");
   383         errorStatus = _HTTP_STATUS_NO_SIM_CARD;
   384       }
   385       if (errorStatus != null) {
   386         this.flushPendingCallbacks(errorStatus);
   387         return true;
   388       }
   390       if (DEBUG) debug("acquire: buffer the MMS request and setup the MMS data call.");
   391       this.radioInterface.setupDataCallByType("mms");
   393       // Set a timer to clear the buffered MMS requests if the
   394       // MMS network fails to be connected within a time period.
   395       this.connectTimer.
   396         initWithCallback(this.flushPendingCallbacks.bind(this, _HTTP_STATUS_ACQUIRE_TIMEOUT),
   397                          TIME_TO_BUFFER_MMS_REQUESTS,
   398                          Ci.nsITimer.TYPE_ONE_SHOT);
   399       return false;
   400     }
   402     callback(true, _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS);
   403     return true;
   404   },
   406   /**
   407    * Release the MMS network connection.
   408    */
   409   release: function() {
   410     this.refCount--;
   411     if (this.refCount <= 0) {
   412       this.refCount = 0;
   414       // The waiting is too small, just skip the timer creation.
   415       if (PREF_TIME_TO_RELEASE_MMS_CONNECTION < 1000) {
   416         this.onDisconnectTimerTimeout();
   417         return;
   418       }
   420       // Set a timer to delay the release of MMS network connection,
   421       // since the MMS requests often come consecutively in a short time.
   422       this.disconnectTimer.
   423         initWithCallback(this.onDisconnectTimerTimeout.bind(this),
   424                          PREF_TIME_TO_RELEASE_MMS_CONNECTION,
   425                          Ci.nsITimer.TYPE_ONE_SHOT);
   426     }
   427   },
   429   shutdown: function() {
   430     Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
   431     Services.obs.removeObserver(this, kNetworkConnStateChangedTopic);
   433     this.connectTimer.cancel();
   434     this.flushPendingCallbacks(_HTTP_STATUS_RADIO_DISABLED);
   435     this.disconnectTimer.cancel();
   436     this.onDisconnectTimerTimeout();
   437   },
   439   // nsIObserver
   441   observe: function(subject, topic, data) {
   442     switch (topic) {
   443       case kNetworkConnStateChangedTopic: {
   444         // The network for MMS connection must be nsIRilNetworkInterface.
   445         if (!(subject instanceof Ci.nsIRilNetworkInterface)) {
   446           return;
   447         }
   449         // Check if the network state change belongs to this service.
   450         let network = subject.QueryInterface(Ci.nsIRilNetworkInterface);
   451         if (network.serviceId != this.serviceId) {
   452           return;
   453         }
   455         // We only need to capture the state change of MMS network. Using
   456         // |network.state| isn't reliable due to the possibilty of shared APN.
   457         let connected =
   458           this.radioInterface.getDataCallStateByType("mms") ==
   459             Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
   461         // Return if the MMS network state doesn't change, where the network
   462         // state change can come from other non-MMS networks.
   463         if (connected == this.connected) {
   464           return;
   465         }
   467         this.connected = connected;
   468         if (!this.connected) {
   469           return;
   470         }
   472         // Set up the MMS APN setting based on the connected MMS network,
   473         // which is going to be used for the HTTP requests later.
   474         this.setApnSetting(network);
   476         if (DEBUG) debug("Got the MMS network connected! Resend the buffered " +
   477                          "MMS requests: number: " + this.pendingCallbacks.length);
   478         this.connectTimer.cancel();
   479         this.flushPendingCallbacks(_HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS)
   480         break;
   481       }
   482       case NS_XPCOM_SHUTDOWN_OBSERVER_ID: {
   483         this.shutdown();
   484       }
   485     }
   486   }
   487 };
   489 XPCOMUtils.defineLazyGetter(this, "gMmsConnections", function() {
   490   return {
   491     _connections: null,
   492     getConnByServiceId: function(id) {
   493       if (!this._connections) {
   494         this._connections = [];
   495       }
   497       let conn = this._connections[id];
   498       if (conn) {
   499         return conn;
   500       }
   502       conn = this._connections[id] = new MmsConnection(id);
   503       conn.init();
   504       return conn;
   505     },
   506     getConnByIccId: function(aIccId) {
   507       if (!aIccId) {
   508         // If the ICC ID isn't available, it means the MMS has been received
   509         // during the previous version that didn't take the DSDS scenario
   510         // into consideration. Tentatively, get connection from serviceId(0) by
   511         // default is better than nothing. Although it might use the wrong
   512         // SIM to download the desired MMS, eventually it would still fail to
   513         // download due to the wrong MMSC and proxy settings.
   514         return this.getConnByServiceId(0);
   515       }
   517       let numCardAbsent = 0;
   518       let numRadioInterfaces = gRil.numRadioInterfaces;
   519       for (let clientId = 0; clientId < numRadioInterfaces; clientId++) {
   520         let mmsConnection = this.getConnByServiceId(clientId);
   521         let iccId = mmsConnection.getIccId();
   522         if (iccId === null) {
   523           numCardAbsent++;
   524           continue;
   525         }
   527         if (iccId === aIccId) {
   528           return mmsConnection;
   529         }
   530       }
   532       throw ((numCardAbsent === numRadioInterfaces)?
   533                _MMS_ERROR_NO_SIM_CARD: _MMS_ERROR_SIM_NOT_MATCHED);
   534     },
   535   };
   536 });
   538 /**
   539  * Implementation of nsIProtocolProxyFilter for MMS Proxy
   540  */
   541 function MmsProxyFilter(mmsConnection, url) {
   542   this.mmsConnection = mmsConnection;
   543   this.uri = Services.io.newURI(url, null, null);
   544 }
   545 MmsProxyFilter.prototype = {
   547   QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolProxyFilter]),
   549   // nsIProtocolProxyFilter
   551   applyFilter: function(proxyService, uri, proxyInfo) {
   552     if (!this.uri.equals(uri)) {
   553       if (DEBUG) debug("applyFilter: content uri = " + JSON.stringify(this.uri) +
   554                        " is not matched with uri = " + JSON.stringify(uri) + " .");
   555       return proxyInfo;
   556     }
   558     // Fall-through, reutrn the MMS proxy info.
   559     let mmsProxyInfo = this.mmsConnection.proxyInfo;
   561     if (DEBUG) {
   562       debug("applyFilter: MMSC/Content Location is matched with: " +
   563             JSON.stringify({ uri: JSON.stringify(this.uri),
   564                              mmsProxyInfo: mmsProxyInfo }));
   565     }
   567     return mmsProxyInfo ? mmsProxyInfo : proxyInfo;
   568   }
   569 };
   571 XPCOMUtils.defineLazyGetter(this, "gMmsTransactionHelper", function() {
   572   let helper = {
   573     /**
   574      * Send MMS request to MMSC.
   575      *
   576      * @param mmsConnection
   577      *        The MMS connection.
   578      * @param method
   579      *        "GET" or "POST".
   580      * @param url
   581      *        Target url string or null to be replaced by mmsc url.
   582      * @param istream
   583      *        An nsIInputStream instance as data source to be sent or null.
   584      * @param callback
   585      *        A callback function that takes two arguments: one for http
   586      *        status, the other for wrapped PDU data for further parsing.
   587      */
   588     sendRequest: function(mmsConnection, method, url, istream, callback) {
   589       // TODO: bug 810226 - Support GPRS bearer for MMS transmission and reception.
   590       let cancellable = {
   591         callback: callback,
   593         isDone: false,
   594         isCancelled: false,
   596         cancel: function() {
   597           if (this.isDone) {
   598             // It's too late to cancel.
   599             return;
   600           }
   602           this.isCancelled = true;
   603           if (this.isAcquiringConn) {
   604             // We cannot cancel data connection setup here, so we invoke done()
   605             // here and handle |cancellable.isDone| in callback function of
   606             // |mmsConnection.acquire|.
   607             this.done(_HTTP_STATUS_USER_CANCELLED, null);
   608           } else if (this.xhr) {
   609             // Client has already sent the HTTP request. Try to abort it.
   610             this.xhr.abort();
   611           }
   612         },
   614         done: function(httpStatus, data) {
   615           this.isDone = true;
   616           if (!this.callback) {
   617             return;
   618           }
   620           if (this.isCancelled) {
   621             this.callback(_HTTP_STATUS_USER_CANCELLED, null);
   622           } else {
   623             this.callback(httpStatus, data);
   624           }
   625         }
   626       };
   628       cancellable.isAcquiringConn =
   629         !mmsConnection.acquire((function(connected, errorCode) {
   631         cancellable.isAcquiringConn = false;
   633         if (!connected || cancellable.isCancelled) {
   634           mmsConnection.release();
   636           if (!cancellable.isDone) {
   637             cancellable.done(cancellable.isCancelled ?
   638                              _HTTP_STATUS_USER_CANCELLED : errorCode, null);
   639           }
   640           return;
   641         }
   643         // MMSC is available after an MMS connection is successfully acquired.
   644         if (!url) {
   645           url = mmsConnection.mmsc;
   646         }
   648         if (DEBUG) debug("sendRequest: register proxy filter to " + url);
   649         let proxyFilter = new MmsProxyFilter(mmsConnection, url);
   650         gpps.registerFilter(proxyFilter, 0);
   652         cancellable.xhr = this.sendHttpRequest(mmsConnection, method,
   653                                                url, istream, proxyFilter,
   654                                                cancellable.done.bind(cancellable));
   655       }).bind(this));
   657       return cancellable;
   658     },
   660     sendHttpRequest: function(mmsConnection, method, url, istream, proxyFilter,
   661                               callback) {
   662       let releaseMmsConnectionAndCallback = function(httpStatus, data) {
   663         gpps.unregisterFilter(proxyFilter);
   664         // Always release the MMS network connection before callback.
   665         mmsConnection.release();
   666         callback(httpStatus, data);
   667       };
   669       try {
   670         let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
   671                   .createInstance(Ci.nsIXMLHttpRequest);
   673         // Basic setups
   674         xhr.open(method, url, true);
   675         xhr.responseType = "arraybuffer";
   676         if (istream) {
   677           xhr.setRequestHeader("Content-Type",
   678                                "application/vnd.wap.mms-message");
   679           xhr.setRequestHeader("Content-Length", istream.available());
   680         }
   682         // UAProf headers.
   683         let uaProfUrl, uaProfTagname = "x-wap-profile";
   684         try {
   685           uaProfUrl = Services.prefs.getCharPref('wap.UAProf.url');
   686           uaProfTagname = Services.prefs.getCharPref('wap.UAProf.tagname');
   687         } catch (e) {}
   689         if (uaProfUrl) {
   690           xhr.setRequestHeader(uaProfTagname, uaProfUrl);
   691         }
   693         // Setup event listeners
   694         xhr.onreadystatechange = function() {
   695           if (xhr.readyState != Ci.nsIXMLHttpRequest.DONE) {
   696             return;
   697           }
   698           let data = null;
   699           switch (xhr.status) {
   700             case HTTP_STATUS_OK: {
   701               if (DEBUG) debug("xhr success, response headers: "
   702                                + xhr.getAllResponseHeaders());
   703               let array = new Uint8Array(xhr.response);
   704               if (false) {
   705                 for (let begin = 0; begin < array.length; begin += 20) {
   706                   let partial = array.subarray(begin, begin + 20);
   707                   if (DEBUG) debug("res: " + JSON.stringify(partial));
   708                 }
   709               }
   711               data = {array: array, offset: 0};
   712               break;
   713             }
   715             default: {
   716               if (DEBUG) debug("xhr done, but status = " + xhr.status +
   717                                ", statusText = " + xhr.statusText);
   718               break;
   719             }
   720           }
   721           releaseMmsConnectionAndCallback(xhr.status, data);
   722         };
   723         // Send request
   724         xhr.send(istream);
   725         return xhr;
   726       } catch (e) {
   727         if (DEBUG) debug("xhr error, can't send: " + e.message);
   728         releaseMmsConnectionAndCallback(0, null);
   729         return null;
   730       }
   731     },
   733     /**
   734      * Count number of recipients(to, cc, bcc fields).
   735      *
   736      * @param recipients
   737      *        The recipients in MMS message object.
   738      * @return the number of recipients
   739      * @see OMA-TS-MMS_CONF-V1_3-20110511-C section 10.2.5
   740      */
   741     countRecipients: function(recipients) {
   742       if (recipients && recipients.address) {
   743         return 1;
   744       }
   745       let totalRecipients = 0;
   746       if (!Array.isArray(recipients)) {
   747         return 0;
   748       }
   749       totalRecipients += recipients.length;
   750       for (let ix = 0; ix < recipients.length; ++ix) {
   751         if (recipients[ix].address.length > MMS.MMS_MAX_LENGTH_RECIPIENT) {
   752           throw new Error("MMS_MAX_LENGTH_RECIPIENT error");
   753         }
   754         if (recipients[ix].type === "email") {
   755           let found = recipients[ix].address.indexOf("<");
   756           let lenMailbox = recipients[ix].address.length - found;
   757           if(lenMailbox > MMS.MMS_MAX_LENGTH_MAILBOX_PORTION) {
   758             throw new Error("MMS_MAX_LENGTH_MAILBOX_PORTION error");
   759           }
   760         }
   761       }
   762       return totalRecipients;
   763     },
   765     /**
   766      * Check maximum values of MMS parameters.
   767      *
   768      * @param msg
   769      *        The MMS message object.
   770      * @return true if the lengths are less than the maximum values of MMS
   771      *         parameters.
   772      * @see OMA-TS-MMS_CONF-V1_3-20110511-C section 10.2.5
   773      */
   774     checkMaxValuesParameters: function(msg) {
   775       let subject = msg.headers["subject"];
   776       if (subject && subject.length > MMS.MMS_MAX_LENGTH_SUBJECT) {
   777         return false;
   778       }
   780       let totalRecipients = 0;
   781       try {
   782         totalRecipients += this.countRecipients(msg.headers["to"]);
   783         totalRecipients += this.countRecipients(msg.headers["cc"]);
   784         totalRecipients += this.countRecipients(msg.headers["bcc"]);
   785       } catch (ex) {
   786         if (DEBUG) debug("Exception caught : " + ex);
   787         return false;
   788       }
   790       if (totalRecipients < 1 ||
   791           totalRecipients > MMS.MMS_MAX_TOTAL_RECIPIENTS) {
   792         return false;
   793       }
   795       if (!Array.isArray(msg.parts)) {
   796         return true;
   797       }
   798       for (let i = 0; i < msg.parts.length; i++) {
   799         if (msg.parts[i].headers["content-type"] &&
   800           msg.parts[i].headers["content-type"].params) {
   801           let name = msg.parts[i].headers["content-type"].params["name"];
   802           if (name && name.length > MMS.MMS_MAX_LENGTH_NAME_CONTENT_TYPE) {
   803             return false;
   804           }
   805         }
   806       }
   807       return true;
   808     },
   810     translateHttpStatusToMmsStatus: function(httpStatus, cancelledReason,
   811                                              defaultStatus) {
   812       switch(httpStatus) {
   813         case _HTTP_STATUS_USER_CANCELLED:
   814           return cancelledReason;
   815         case _HTTP_STATUS_RADIO_DISABLED:
   816           return _MMS_ERROR_RADIO_DISABLED;
   817         case _HTTP_STATUS_NO_SIM_CARD:
   818           return _MMS_ERROR_NO_SIM_CARD;
   819         case HTTP_STATUS_OK:
   820           return MMS.MMS_PDU_ERROR_OK;
   821         default:
   822           return defaultStatus;
   823       }
   824     }
   825   };
   827   return helper;
   828 });
   830 /**
   831  * Send M-NotifyResp.ind back to MMSC.
   832  *
   833  * @param mmsConnection
   834  *        The MMS connection.
   835  * @param transactionId
   836  *        X-Mms-Transaction-ID of the message.
   837  * @param status
   838  *        X-Mms-Status of the response.
   839  * @param reportAllowed
   840  *        X-Mms-Report-Allowed of the response.
   841  *
   842  * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.2
   843  */
   844 function NotifyResponseTransaction(mmsConnection, transactionId, status,
   845                                    reportAllowed) {
   846   this.mmsConnection = mmsConnection;
   847   let headers = {};
   849   // Mandatory fields
   850   headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_NOTIFYRESP_IND;
   851   headers["x-mms-transaction-id"] = transactionId;
   852   headers["x-mms-mms-version"] = MMS.MMS_VERSION;
   853   headers["x-mms-status"] = status;
   854   // Optional fields
   855   headers["x-mms-report-allowed"] = reportAllowed;
   857   this.istream = MMS.PduHelper.compose(null, {headers: headers});
   858 }
   859 NotifyResponseTransaction.prototype = {
   860   /**
   861    * @param callback [optional]
   862    *        A callback function that takes one argument -- the http status.
   863    */
   864   run: function(callback) {
   865     let requestCallback;
   866     if (callback) {
   867       requestCallback = function(httpStatus, data) {
   868         // `The MMS Client SHOULD ignore the associated HTTP POST response
   869         // from the MMS Proxy-Relay.` ~ OMA-TS-MMS_CTR-V1_3-20110913-A
   870         // section 8.2.2 "Notification".
   871         callback(httpStatus);
   872       };
   873     }
   874     gMmsTransactionHelper.sendRequest(this.mmsConnection,
   875                                       "POST",
   876                                       null,
   877                                       this.istream,
   878                                       requestCallback);
   879   }
   880 };
   882 /**
   883  * CancellableTransaction - base class inherited by [Send|Retrieve]Transaction.
   884  * We can call |cancelRunning(reason)| to cancel the on-going transaction.
   885  * @param cancellableId
   886  *        An ID used to keep track of if an message is deleted from DB.
   887  * @param serviceId
   888  *        An ID used to keep track of if the primary SIM service is changed.
   889  */
   890 function CancellableTransaction(cancellableId, serviceId) {
   891   this.cancellableId = cancellableId;
   892   this.serviceId = serviceId;
   893   this.isCancelled = false;
   894 }
   895 CancellableTransaction.prototype = {
   896   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
   898   // The timer for retrying sending or retrieving process.
   899   timer: null,
   901   // Keep a reference to the callback when calling
   902   // |[Send|Retrieve]Transaction.run(callback)|.
   903   runCallback: null,
   905   isObserversAdded: false,
   907   cancelledReason: _MMS_ERROR_USER_CANCELLED_NO_REASON,
   909   registerRunCallback: function(callback) {
   910     if (!this.isObserversAdded) {
   911       Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
   912       Services.obs.addObserver(this, kMobileMessageDeletedObserverTopic, false);
   913       Services.prefs.addObserver(kPrefRilRadioDisabled, this, false);
   914       Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
   915       this.isObserversAdded = true;
   916     }
   918     this.runCallback = callback;
   919     this.isCancelled = false;
   920   },
   922   removeObservers: function() {
   923     if (this.isObserversAdded) {
   924       Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
   925       Services.obs.removeObserver(this, kMobileMessageDeletedObserverTopic);
   926       Services.prefs.removeObserver(kPrefRilRadioDisabled, this);
   927       Services.prefs.removeObserver(kPrefDefaultServiceId, this);
   928       this.isObserversAdded = false;
   929     }
   930   },
   932   runCallbackIfValid: function(mmsStatus, msg) {
   933     this.removeObservers();
   935     if (this.runCallback) {
   936       this.runCallback(mmsStatus, msg);
   937       this.runCallback = null;
   938     }
   939   },
   941   // Keep a reference to the cancellable when calling
   942   // |gMmsTransactionHelper.sendRequest(...)|.
   943   cancellable: null,
   945   cancelRunning: function(reason) {
   946     this.isCancelled = true;
   947     this.cancelledReason = reason;
   949     if (this.timer) {
   950       // The sending or retrieving process is waiting for the next retry.
   951       // What we only need to do is to cancel the timer.
   952       this.timer.cancel();
   953       this.timer = null;
   954       this.runCallbackIfValid(reason, null);
   955       return;
   956     }
   958     if (this.cancellable) {
   959       // The sending or retrieving process is still running. We attempt to
   960       // abort the HTTP request.
   961       this.cancellable.cancel();
   962       this.cancellable = null;
   963     }
   964   },
   966   // nsIObserver
   968   observe: function(subject, topic, data) {
   969     switch (topic) {
   970       case NS_XPCOM_SHUTDOWN_OBSERVER_ID: {
   971         this.cancelRunning(_MMS_ERROR_SHUTDOWN);
   972         break;
   973       }
   974       case kMobileMessageDeletedObserverTopic: {
   975         data = JSON.parse(data);
   976         if (data.id != this.cancellableId) {
   977           return;
   978         }
   980         this.cancelRunning(_MMS_ERROR_MESSAGE_DELETED);
   981         break;
   982       }
   983       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: {
   984         if (data == kPrefRilRadioDisabled) {
   985           if (getRadioDisabledState()) {
   986             this.cancelRunning(_MMS_ERROR_RADIO_DISABLED);
   987           }
   988         } else if (data === kPrefDefaultServiceId &&
   989                    this.serviceId != getDefaultServiceId()) {
   990           this.cancelRunning(_MMS_ERROR_SIM_CARD_CHANGED);
   991         }
   992         break;
   993       }
   994     }
   995   }
   996 };
   998 /**
   999  * Class for retrieving message from MMSC, which inherits CancellableTransaction.
  1001  * @param contentLocation
  1002  *        X-Mms-Content-Location of the message.
  1003  */
  1004 function RetrieveTransaction(mmsConnection, cancellableId, contentLocation) {
  1005   this.mmsConnection = mmsConnection;
  1007   // Call |CancellableTransaction| constructor.
  1008   CancellableTransaction.call(this, cancellableId, mmsConnection.serviceId);
  1010   this.contentLocation = contentLocation;
  1012 RetrieveTransaction.prototype = Object.create(CancellableTransaction.prototype, {
  1013   /**
  1014    * @param callback [optional]
  1015    *        A callback function that takes two arguments: one for X-Mms-Status,
  1016    *        the other for the parsed M-Retrieve.conf message.
  1017    */
  1018   run: {
  1019     value: function(callback) {
  1020       this.registerRunCallback(callback);
  1022       this.retryCount = 0;
  1023       let retryCallback = (function(mmsStatus, msg) {
  1024         if (MMS.MMS_PDU_STATUS_DEFERRED == mmsStatus &&
  1025             this.retryCount < PREF_RETRIEVAL_RETRY_COUNT) {
  1026           let time = PREF_RETRIEVAL_RETRY_INTERVALS[this.retryCount];
  1027           if (DEBUG) debug("Fail to retrieve. Will retry after: " + time);
  1029           if (this.timer == null) {
  1030             this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1033           this.timer.initWithCallback(this.retrieve.bind(this, retryCallback),
  1034                                       time, Ci.nsITimer.TYPE_ONE_SHOT);
  1035           this.retryCount++;
  1036           return;
  1038         this.runCallbackIfValid(mmsStatus, msg);
  1039       }).bind(this);
  1041       this.retrieve(retryCallback);
  1042     },
  1043     enumerable: true,
  1044     configurable: true,
  1045     writable: true
  1046   },
  1048   /**
  1049    * @param callback
  1050    *        A callback function that takes two arguments: one for X-Mms-Status,
  1051    *        the other for the parsed M-Retrieve.conf message.
  1052    */
  1053   retrieve: {
  1054     value: function(callback) {
  1055       this.timer = null;
  1057       this.cancellable =
  1058         gMmsTransactionHelper.sendRequest(this.mmsConnection,
  1059                                           "GET", this.contentLocation, null,
  1060                                           (function(httpStatus, data) {
  1061         let mmsStatus = gMmsTransactionHelper
  1062                         .translateHttpStatusToMmsStatus(httpStatus,
  1063                                                         this.cancelledReason,
  1064                                                         MMS.MMS_PDU_STATUS_DEFERRED);
  1065         if (mmsStatus != MMS.MMS_PDU_ERROR_OK) {
  1066            callback(mmsStatus, null);
  1067            return;
  1069         if (!data) {
  1070           callback(MMS.MMS_PDU_STATUS_DEFERRED, null);
  1071           return;
  1074         let retrieved = MMS.PduHelper.parse(data, null);
  1075         if (!retrieved || (retrieved.type != MMS.MMS_PDU_TYPE_RETRIEVE_CONF)) {
  1076           callback(MMS.MMS_PDU_STATUS_UNRECOGNISED, null);
  1077           return;
  1080         // Fix default header field values.
  1081         if (retrieved.headers["x-mms-delivery-report"] == null) {
  1082           retrieved.headers["x-mms-delivery-report"] = false;
  1085         let retrieveStatus = retrieved.headers["x-mms-retrieve-status"];
  1086         if ((retrieveStatus != null) &&
  1087             (retrieveStatus != MMS.MMS_PDU_ERROR_OK)) {
  1088           callback(MMS.translatePduErrorToStatus(retrieveStatus), retrieved);
  1089           return;
  1092         callback(MMS.MMS_PDU_STATUS_RETRIEVED, retrieved);
  1093       }).bind(this));
  1094     },
  1095     enumerable: true,
  1096     configurable: true,
  1097     writable: true
  1099 });
  1101 /**
  1102  * SendTransaction.
  1103  *   Class for sending M-Send.req to MMSC, which inherits CancellableTransaction.
  1104  *   @throws Error("Check max values parameters fail.")
  1105  */
  1106 function SendTransaction(mmsConnection, cancellableId, msg, requestDeliveryReport) {
  1107   this.mmsConnection = mmsConnection;
  1109   // Call |CancellableTransaction| constructor.
  1110   CancellableTransaction.call(this, cancellableId, mmsConnection.serviceId);
  1112   msg.headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_SEND_REQ;
  1113   if (!msg.headers["x-mms-transaction-id"]) {
  1114     // Create an unique transaction id
  1115     let tid = gUUIDGenerator.generateUUID().toString();
  1116     msg.headers["x-mms-transaction-id"] = tid;
  1118   msg.headers["x-mms-mms-version"] = MMS.MMS_VERSION;
  1120   // Let MMS Proxy Relay insert from address automatically for us
  1121   msg.headers["from"] = null;
  1123   msg.headers["date"] = new Date();
  1124   msg.headers["x-mms-message-class"] = "personal";
  1125   msg.headers["x-mms-expiry"] = 7 * 24 * 60 * 60;
  1126   msg.headers["x-mms-priority"] = 129;
  1127   try {
  1128     msg.headers["x-mms-read-report"] =
  1129       Services.prefs.getBoolPref("dom.mms.requestReadReport");
  1130   } catch (e) {
  1131     msg.headers["x-mms-read-report"] = true;
  1133   msg.headers["x-mms-delivery-report"] = requestDeliveryReport;
  1135   if (!gMmsTransactionHelper.checkMaxValuesParameters(msg)) {
  1136     //We should notify end user that the header format is wrong.
  1137     if (DEBUG) debug("Check max values parameters fail.");
  1138     throw new Error("Check max values parameters fail.");
  1141   if (msg.parts) {
  1142     let contentType = {
  1143       params: {
  1144         // `The type parameter must be specified and its value is the MIME
  1145         // media type of the "root" body part.` ~ RFC 2387 clause 3.1
  1146         type: msg.parts[0].headers["content-type"].media,
  1147       },
  1148     };
  1150     // `The Content-Type in M-Send.req and M-Retrieve.conf SHALL be
  1151     // application/vnd.wap.multipart.mixed when there is no presentation, and
  1152     // application/vnd.wap.multipart.related SHALL be used when there is SMIL
  1153     // presentation available.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1
  1154     if (contentType.params.type === "application/smil") {
  1155       contentType.media = "application/vnd.wap.multipart.related";
  1157       // `The start parameter, if given, is the content-ID of the compound
  1158       // object's "root".` ~ RFC 2387 clause 3.2
  1159       contentType.params.start = msg.parts[0].headers["content-id"];
  1160     } else {
  1161       contentType.media = "application/vnd.wap.multipart.mixed";
  1164     // Assign to Content-Type
  1165     msg.headers["content-type"] = contentType;
  1168   if (DEBUG) debug("msg: " + JSON.stringify(msg));
  1170   this.msg = msg;
  1172 SendTransaction.prototype = Object.create(CancellableTransaction.prototype, {
  1173   istreamComposed: {
  1174     value: false,
  1175     enumerable: true,
  1176     configurable: true,
  1177     writable: true
  1178   },
  1180   /**
  1181    * @param parts
  1182    *        'parts' property of a parsed MMS message.
  1183    * @param callback [optional]
  1184    *        A callback function that takes zero argument.
  1185    */
  1186   loadBlobs: {
  1187     value: function(parts, callback) {
  1188       let callbackIfValid = function callbackIfValid() {
  1189         if (DEBUG) debug("All parts loaded: " + JSON.stringify(parts));
  1190         if (callback) {
  1191           callback();
  1195       if (!parts || !parts.length) {
  1196         callbackIfValid();
  1197         return;
  1200       let numPartsToLoad = parts.length;
  1201       for each (let part in parts) {
  1202         if (!(part.content instanceof Ci.nsIDOMBlob)) {
  1203           numPartsToLoad--;
  1204           if (!numPartsToLoad) {
  1205             callbackIfValid();
  1206             return;
  1208           continue;
  1210         let fileReader = Cc["@mozilla.org/files/filereader;1"]
  1211                          .createInstance(Ci.nsIDOMFileReader);
  1212         fileReader.addEventListener("loadend",
  1213           (function onloadend(part, event) {
  1214           let arrayBuffer = event.target.result;
  1215           part.content = new Uint8Array(arrayBuffer);
  1216           numPartsToLoad--;
  1217           if (!numPartsToLoad) {
  1218             callbackIfValid();
  1220         }).bind(null, part));
  1221         fileReader.readAsArrayBuffer(part.content);
  1222       };
  1223     },
  1224     enumerable: true,
  1225     configurable: true,
  1226     writable: true
  1227   },
  1229   /**
  1230    * @param callback [optional]
  1231    *        A callback function that takes two arguments: one for
  1232    *        X-Mms-Response-Status, the other for the parsed M-Send.conf message.
  1233    */
  1234   run: {
  1235     value: function(callback) {
  1236       this.registerRunCallback(callback);
  1238       if (!this.istreamComposed) {
  1239         this.loadBlobs(this.msg.parts, (function() {
  1240           this.istream = MMS.PduHelper.compose(null, this.msg);
  1241           this.istreamSize = this.istream.available();
  1242           this.istreamComposed = true;
  1243           if (this.isCancelled) {
  1244             this.runCallbackIfValid(_MMS_ERROR_MESSAGE_DELETED, null);
  1245           } else {
  1246             this.run(callback);
  1248         }).bind(this));
  1249         return;
  1252       if (!this.istream) {
  1253         this.runCallbackIfValid(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
  1254         return;
  1257       this.retryCount = 0;
  1258       let retryCallback = (function(mmsStatus, msg) {
  1259         if ((MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE == mmsStatus ||
  1260               MMS.MMS_PDU_ERROR_PERMANENT_FAILURE == mmsStatus) &&
  1261             this.retryCount < PREF_SEND_RETRY_COUNT) {
  1262           if (DEBUG) {
  1263             debug("Fail to send. Will retry after: " + PREF_SEND_RETRY_INTERVAL);
  1266           if (this.timer == null) {
  1267             this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1270           this.retryCount++;
  1272           // the input stream may be read in the previous failure request so
  1273           // we have to re-compose it.
  1274           if (this.istreamSize == null ||
  1275               this.istreamSize != this.istream.available()) {
  1276             this.istream = MMS.PduHelper.compose(null, this.msg);
  1279           this.timer.initWithCallback(this.send.bind(this, retryCallback),
  1280                                       PREF_SEND_RETRY_INTERVAL,
  1281                                       Ci.nsITimer.TYPE_ONE_SHOT);
  1282           return;
  1285         this.runCallbackIfValid(mmsStatus, msg);
  1286       }).bind(this);
  1288       // This is the entry point to start sending.
  1289       this.send(retryCallback);
  1290     },
  1291     enumerable: true,
  1292     configurable: true,
  1293     writable: true
  1294   },
  1296   /**
  1297    * @param callback
  1298    *        A callback function that takes two arguments: one for
  1299    *        X-Mms-Response-Status, the other for the parsed M-Send.conf message.
  1300    */
  1301   send: {
  1302     value: function(callback) {
  1303       this.timer = null;
  1305       this.cancellable =
  1306         gMmsTransactionHelper.sendRequest(this.mmsConnection,
  1307                                           "POST",
  1308                                           null,
  1309                                           this.istream,
  1310                                           (function(httpStatus, data) {
  1311         let mmsStatus = gMmsTransactionHelper.
  1312                           translateHttpStatusToMmsStatus(
  1313                             httpStatus,
  1314                             this.cancelledReason,
  1315                             MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE);
  1316         if (httpStatus != HTTP_STATUS_OK) {
  1317           callback(mmsStatus, null);
  1318           return;
  1321         if (!data) {
  1322           callback(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
  1323           return;
  1326         let response = MMS.PduHelper.parse(data, null);
  1327         if (!response || (response.type != MMS.MMS_PDU_TYPE_SEND_CONF)) {
  1328           callback(MMS.MMS_PDU_RESPONSE_ERROR_UNSUPPORTED_MESSAGE, null);
  1329           return;
  1332         let responseStatus = response.headers["x-mms-response-status"];
  1333         callback(responseStatus, response);
  1334       }).bind(this));
  1335     },
  1336     enumerable: true,
  1337     configurable: true,
  1338     writable: true
  1340 });
  1342 /**
  1343  * Send M-acknowledge.ind back to MMSC.
  1345  * @param mmsConnection
  1346  *        The MMS connection.
  1347  * @param transactionId
  1348  *        X-Mms-Transaction-ID of the message.
  1349  * @param reportAllowed
  1350  *        X-Mms-Report-Allowed of the response.
  1352  * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.4
  1353  */
  1354 function AcknowledgeTransaction(mmsConnection, transactionId, reportAllowed) {
  1355   this.mmsConnection = mmsConnection;
  1356   let headers = {};
  1358   // Mandatory fields
  1359   headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_ACKNOWLEDGE_IND;
  1360   headers["x-mms-transaction-id"] = transactionId;
  1361   headers["x-mms-mms-version"] = MMS.MMS_VERSION;
  1362   // Optional fields
  1363   headers["x-mms-report-allowed"] = reportAllowed;
  1365   this.istream = MMS.PduHelper.compose(null, {headers: headers});
  1367 AcknowledgeTransaction.prototype = {
  1368   /**
  1369    * @param callback [optional]
  1370    *        A callback function that takes one argument -- the http status.
  1371    */
  1372   run: function(callback) {
  1373     let requestCallback;
  1374     if (callback) {
  1375       requestCallback = function(httpStatus, data) {
  1376         // `The MMS Client SHOULD ignore the associated HTTP POST response
  1377         // from the MMS Proxy-Relay.` ~ OMA-TS-MMS_CTR-V1_3-20110913-A
  1378         // section 8.2.3 "Retrieving an MM".
  1379         callback(httpStatus);
  1380       };
  1382     gMmsTransactionHelper.sendRequest(this.mmsConnection,
  1383                                       "POST",
  1384                                       null,
  1385                                       this.istream,
  1386                                       requestCallback);
  1388 };
  1390 /**
  1391  * Return M-Read-Rec.ind back to MMSC
  1393  * @param messageID
  1394  *        Message-ID of the message.
  1395  * @param toAddress
  1396  *        The address of the recipient of the Read Report, i.e. the originator
  1397  *        of the original multimedia message.
  1399  * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.7.2
  1400  */
  1401 function ReadRecTransaction(mmsConnection, messageID, toAddress) {
  1402   this.mmsConnection = mmsConnection;
  1403   let headers = {};
  1405   // Mandatory fields
  1406   headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_READ_REC_IND;
  1407   headers["x-mms-mms-version"] = MMS.MMS_VERSION;
  1408   headers["message-id"] = messageID;
  1409   let type = MMS.Address.resolveType(toAddress);
  1410   let to = {address: toAddress,
  1411             type: type}
  1412   headers["to"] = to;
  1413   headers["from"] = null;
  1414   headers["x-mms-read-status"] = MMS.MMS_PDU_READ_STATUS_READ;
  1416   this.istream = MMS.PduHelper.compose(null, {headers: headers});
  1417   if (!this.istream) {
  1418     throw Cr.NS_ERROR_FAILURE;
  1421 ReadRecTransaction.prototype = {
  1422   run: function() {
  1423     gMmsTransactionHelper.sendRequest(this.mmsConnection,
  1424                                       "POST",
  1425                                       null,
  1426                                       this.istream,
  1427                                       null);
  1429 };
  1431 /**
  1432  * MmsService
  1433  */
  1434 function MmsService() {
  1435   if (DEBUG) {
  1436     let macro = (MMS.MMS_VERSION >> 4) & 0x0f;
  1437     let minor = MMS.MMS_VERSION & 0x0f;
  1438     debug("Running protocol version: " + macro + "." + minor);
  1441   Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
  1442   this.mmsDefaultServiceId = getDefaultServiceId();
  1444   // TODO: bug 810084 - support application identifier
  1446 MmsService.prototype = {
  1448   classID:   RIL_MMSSERVICE_CID,
  1449   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMmsService,
  1450                                          Ci.nsIWapPushApplication,
  1451                                          Ci.nsIObserver]),
  1452   /*
  1453    * Whether or not should we enable X-Mms-Report-Allowed in M-NotifyResp.ind
  1454    * and M-Acknowledge.ind PDU.
  1455    */
  1456   confSendDeliveryReport: CONFIG_SEND_REPORT_DEFAULT_YES,
  1458   /**
  1459    * Calculate Whether or not should we enable X-Mms-Report-Allowed.
  1461    * @param config
  1462    *        Current configure value.
  1463    * @param wish
  1464    *        Sender wish. Could be undefined, false, or true.
  1465    */
  1466   getReportAllowed: function(config, wish) {
  1467     if ((config == CONFIG_SEND_REPORT_DEFAULT_NO)
  1468         || (config == CONFIG_SEND_REPORT_DEFAULT_YES)) {
  1469       if (wish != null) {
  1470         config += (wish ? 1 : -1);
  1473     return config >= CONFIG_SEND_REPORT_DEFAULT_YES;
  1474   },
  1476   /**
  1477    * Convert intermediate message to indexedDB savable object.
  1479    * @param mmsConnection
  1480    *        The MMS connection.
  1481    * @param retrievalMode
  1482    *        Retrieval mode for MMS receiving setting.
  1483    * @param intermediate
  1484    *        Intermediate MMS message parsed from PDU.
  1485    */
  1486   convertIntermediateToSavable: function(mmsConnection, intermediate,
  1487                                          retrievalMode) {
  1488     intermediate.type = "mms";
  1489     intermediate.delivery = DELIVERY_NOT_DOWNLOADED;
  1491     let deliveryStatus;
  1492     switch (retrievalMode) {
  1493       case RETRIEVAL_MODE_MANUAL:
  1494         deliveryStatus = DELIVERY_STATUS_MANUAL;
  1495         break;
  1496       case RETRIEVAL_MODE_NEVER:
  1497         deliveryStatus = DELIVERY_STATUS_REJECTED;
  1498         break;
  1499       case RETRIEVAL_MODE_AUTOMATIC:
  1500         deliveryStatus = DELIVERY_STATUS_PENDING;
  1501         break;
  1502       case RETRIEVAL_MODE_AUTOMATIC_HOME:
  1503         if (mmsConnection.isVoiceRoaming()) {
  1504           deliveryStatus = DELIVERY_STATUS_MANUAL;
  1505         } else {
  1506           deliveryStatus = DELIVERY_STATUS_PENDING;
  1508         break;
  1509       default:
  1510         deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
  1511         break;
  1513     // |intermediate.deliveryStatus| will be deleted after being stored in db.
  1514     intermediate.deliveryStatus = deliveryStatus;
  1516     intermediate.timestamp = Date.now();
  1517     intermediate.receivers = [];
  1518     intermediate.phoneNumber = mmsConnection.getPhoneNumber();
  1519     intermediate.iccId = mmsConnection.getIccId();
  1520     return intermediate;
  1521   },
  1523   /**
  1524    * Merge the retrieval confirmation into the savable message.
  1526    * @param mmsConnection
  1527    *        The MMS connection.
  1528    * @param intermediate
  1529    *        Intermediate MMS message parsed from PDU, which carries
  1530    *        the retrieval confirmation.
  1531    * @param savable
  1532    *        The indexedDB savable MMS message, which is going to be
  1533    *        merged with the extra retrieval confirmation.
  1534    */
  1535   mergeRetrievalConfirmation: function(mmsConnection, intermediate, savable) {
  1536     // Prepare timestamp/sentTimestamp.
  1537     savable.timestamp = Date.now();
  1538     savable.sentTimestamp = intermediate.headers["date"].getTime();
  1540     savable.receivers = [];
  1541     // We don't have Bcc in recevied MMS message.
  1542     for each (let type in ["cc", "to"]) {
  1543       if (intermediate.headers[type]) {
  1544         if (intermediate.headers[type] instanceof Array) {
  1545           for (let index in intermediate.headers[type]) {
  1546             savable.receivers.push(intermediate.headers[type][index].address);
  1548         } else {
  1549           savable.receivers.push(intermediate.headers[type].address);
  1554     savable.delivery = DELIVERY_RECEIVED;
  1555     // |savable.deliveryStatus| will be deleted after being stored in db.
  1556     savable.deliveryStatus = DELIVERY_STATUS_SUCCESS;
  1557     for (let field in intermediate.headers) {
  1558       savable.headers[field] = intermediate.headers[field];
  1560     if (intermediate.parts) {
  1561       savable.parts = intermediate.parts;
  1563     if (intermediate.content) {
  1564       savable.content = intermediate.content;
  1566     return savable;
  1567   },
  1569   /**
  1570    * @param aMmsConnection
  1571    *        The MMS connection.
  1572    * @param aContentLocation
  1573    *        X-Mms-Content-Location of the message.
  1574    * @param aCallback [optional]
  1575    *        A callback function that takes two arguments: one for X-Mms-Status,
  1576    *        the other parsed MMS message.
  1577    * @param aDomMessage
  1578    *        The nsIDOMMozMmsMessage object.
  1579    */
  1580   retrieveMessage: function(aMmsConnection, aContentLocation, aCallback,
  1581                             aDomMessage) {
  1582     // Notifying observers an MMS message is retrieving.
  1583     Services.obs.notifyObservers(aDomMessage, kSmsRetrievingObserverTopic, null);
  1585     let transaction = new RetrieveTransaction(aMmsConnection,
  1586                                               aDomMessage.id,
  1587                                               aContentLocation);
  1588     transaction.run(aCallback);
  1589   },
  1591   /**
  1592    * A helper to broadcast the system message to launch registered apps
  1593    * like Costcontrol, Notification and Message app... etc.
  1595    * @param aName
  1596    *        The system message name.
  1597    * @param aDomMessage
  1598    *        The nsIDOMMozMmsMessage object.
  1599    */
  1600   broadcastMmsSystemMessage: function(aName, aDomMessage) {
  1601     if (DEBUG) debug("Broadcasting the MMS system message: " + aName);
  1603     // Sadly we cannot directly broadcast the aDomMessage object
  1604     // because the system message mechamism will rewrap the object
  1605     // based on the content window, which needs to know the properties.
  1606     gSystemMessenger.broadcastMessage(aName, {
  1607       iccId:               aDomMessage.iccId,
  1608       type:                aDomMessage.type,
  1609       id:                  aDomMessage.id,
  1610       threadId:            aDomMessage.threadId,
  1611       delivery:            aDomMessage.delivery,
  1612       deliveryInfo:        aDomMessage.deliveryInfo,
  1613       sender:              aDomMessage.sender,
  1614       receivers:           aDomMessage.receivers,
  1615       timestamp:           aDomMessage.timestamp,
  1616       sentTimestamp:       aDomMessage.sentTimestamp,
  1617       read:                aDomMessage.read,
  1618       subject:             aDomMessage.subject,
  1619       smil:                aDomMessage.smil,
  1620       attachments:         aDomMessage.attachments,
  1621       expiryDate:          aDomMessage.expiryDate,
  1622       readReportRequested: aDomMessage.readReportRequested
  1623     });
  1624   },
  1626   /**
  1627    * A helper function to broadcast system message and notify observers that
  1628    * an MMS is sent.
  1630    * @params aDomMessage
  1631    *         The nsIDOMMozMmsMessage object.
  1632    */
  1633   broadcastSentMessageEvent: function(aDomMessage) {
  1634     // Broadcasting a 'sms-sent' system message to open apps.
  1635     this.broadcastMmsSystemMessage(kSmsSentObserverTopic, aDomMessage);
  1637     // Notifying observers an MMS message is sent.
  1638     Services.obs.notifyObservers(aDomMessage, kSmsSentObserverTopic, null);
  1639   },
  1641   /**
  1642    * A helper function to broadcast system message and notify observers that
  1643    * an MMS is received.
  1645    * @params aDomMessage
  1646    *         The nsIDOMMozMmsMessage object.
  1647    */
  1648   broadcastReceivedMessageEvent :function broadcastReceivedMessageEvent(aDomMessage) {
  1649     // Broadcasting a 'sms-received' system message to open apps.
  1650     this.broadcastMmsSystemMessage(kSmsReceivedObserverTopic, aDomMessage);
  1652     // Notifying observers an MMS message is received.
  1653     Services.obs.notifyObservers(aDomMessage, kSmsReceivedObserverTopic, null);
  1654   },
  1656   /**
  1657    * Callback for retrieveMessage.
  1658    */
  1659   retrieveMessageCallback: function(mmsConnection, wish, savableMessage,
  1660                                     mmsStatus, retrievedMessage) {
  1661     if (DEBUG) debug("retrievedMessage = " + JSON.stringify(retrievedMessage));
  1663     let transactionId = savableMessage.headers["x-mms-transaction-id"];
  1665     // The absence of the field does not indicate any default
  1666     // value. So we go check the same field in the retrieved
  1667     // message instead.
  1668     if (wish == null && retrievedMessage) {
  1669       wish = retrievedMessage.headers["x-mms-delivery-report"];
  1672     let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
  1673                                               wish);
  1674     // If the mmsStatus isn't MMS_PDU_STATUS_RETRIEVED after retrieving,
  1675     // something must be wrong with MMSC, so stop updating the DB record.
  1676     // We could send a message to content to notify the user the MMS
  1677     // retrieving failed. The end user has to retrieve the MMS again.
  1678     if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) {
  1679       if (mmsStatus != _MMS_ERROR_RADIO_DISABLED &&
  1680           mmsStatus != _MMS_ERROR_NO_SIM_CARD &&
  1681           mmsStatus != _MMS_ERROR_SIM_CARD_CHANGED) {
  1682         let transaction = new NotifyResponseTransaction(mmsConnection,
  1683                                                         transactionId,
  1684                                                         mmsStatus,
  1685                                                         reportAllowed);
  1686         transaction.run();
  1688       // Retrieved fail after retry, so we update the delivery status in DB and
  1689       // notify this domMessage that error happen.
  1690       gMobileMessageDatabaseService
  1691         .setMessageDeliveryByMessageId(savableMessage.id,
  1692                                        null,
  1693                                        null,
  1694                                        DELIVERY_STATUS_ERROR,
  1695                                        null,
  1696                                        (function(rv, domMessage) {
  1697         this.broadcastReceivedMessageEvent(domMessage);
  1698       }).bind(this));
  1699       return;
  1702     savableMessage = this.mergeRetrievalConfirmation(mmsConnection,
  1703                                                      retrievedMessage,
  1704                                                      savableMessage);
  1705     gMobileMessageDatabaseService.saveReceivedMessage(savableMessage,
  1706         (function(rv, domMessage) {
  1707       let success = Components.isSuccessCode(rv);
  1709       // Cite 6.2.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A:
  1710       // The M-NotifyResp.ind response PDU SHALL provide a message retrieval
  1711       // status code. The status ‘retrieved’ SHALL be used only if the MMS
  1712       // Client has successfully retrieved the MM prior to sending the
  1713       // NotifyResp.ind response PDU.
  1714       let transaction =
  1715         new NotifyResponseTransaction(mmsConnection,
  1716                                       transactionId,
  1717                                       success ? MMS.MMS_PDU_STATUS_RETRIEVED
  1718                                               : MMS.MMS_PDU_STATUS_DEFERRED,
  1719                                       reportAllowed);
  1720       transaction.run();
  1722       if (!success) {
  1723         // At this point we could send a message to content to notify the user
  1724         // that storing an incoming MMS failed, most likely due to a full disk.
  1725         // The end user has to retrieve the MMS again.
  1726         if (DEBUG) debug("Could not store MMS , error code " + rv);
  1727         return;
  1730       this.broadcastReceivedMessageEvent(domMessage);
  1731     }).bind(this));
  1732   },
  1734   /**
  1735    * Callback for saveReceivedMessage.
  1736    */
  1737   saveReceivedMessageCallback: function(mmsConnection, retrievalMode,
  1738                                         savableMessage, rv, domMessage) {
  1739     let success = Components.isSuccessCode(rv);
  1740     if (!success) {
  1741       // At this point we could send a message to content to notify the
  1742       // user that storing an incoming MMS notification indication failed,
  1743       // ost likely due to a full disk.
  1744       if (DEBUG) debug("Could not store MMS " + JSON.stringify(savableMessage) +
  1745             ", error code " + rv);
  1746       // Because MMSC will resend the notification indication once we don't
  1747       // response the notification. Hope the end user will clean some space
  1748       // for the resent notification indication.
  1749       return;
  1752     // For X-Mms-Report-Allowed and X-Mms-Transaction-Id
  1753     let wish = savableMessage.headers["x-mms-delivery-report"];
  1754     let transactionId = savableMessage.headers["x-mms-transaction-id"];
  1756     this.broadcastReceivedMessageEvent(domMessage);
  1758     // To avoid costing money, we only send notify response when it's under
  1759     // the "automatic" retrieval mode or it's not in the roaming environment.
  1760     if (retrievalMode !== RETRIEVAL_MODE_AUTOMATIC &&
  1761         mmsConnection.isVoiceRoaming()) {
  1762       return;
  1765     if (RETRIEVAL_MODE_MANUAL === retrievalMode ||
  1766         RETRIEVAL_MODE_NEVER === retrievalMode) {
  1767       let mmsStatus = RETRIEVAL_MODE_NEVER === retrievalMode
  1768                     ? MMS.MMS_PDU_STATUS_REJECTED
  1769                     : MMS.MMS_PDU_STATUS_DEFERRED;
  1771       // For X-Mms-Report-Allowed
  1772       let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
  1773                                                 wish);
  1775       let transaction = new NotifyResponseTransaction(mmsConnection,
  1776                                                       transactionId,
  1777                                                       mmsStatus,
  1778                                                       reportAllowed);
  1779       transaction.run();
  1780       return;
  1783     let url = savableMessage.headers["x-mms-content-location"].uri;
  1785     // For RETRIEVAL_MODE_AUTOMATIC or RETRIEVAL_MODE_AUTOMATIC_HOME but not
  1786     // roaming, proceed to retrieve MMS.
  1787     this.retrieveMessage(mmsConnection,
  1788                          url,
  1789                          this.retrieveMessageCallback.bind(this,
  1790                                                            mmsConnection,
  1791                                                            wish,
  1792                                                            savableMessage),
  1793                          domMessage);
  1794   },
  1796   /**
  1797    * Handle incoming M-Notification.ind PDU.
  1799    * @param serviceId
  1800    *        The ID of the service for receiving the PDU data.
  1801    * @param notification
  1802    *        The parsed MMS message object.
  1803    */
  1804   handleNotificationIndication: function(serviceId, notification) {
  1805     let transactionId = notification.headers["x-mms-transaction-id"];
  1806     gMobileMessageDatabaseService.getMessageRecordByTransactionId(transactionId,
  1807         (function(aRv, aMessageRecord) {
  1808       if (Components.isSuccessCode(aRv) && aMessageRecord) {
  1809         if (DEBUG) debug("We already got the NotificationIndication with transactionId = "
  1810                          + transactionId + " before.");
  1811         return;
  1814       let retrievalMode = RETRIEVAL_MODE_MANUAL;
  1815       try {
  1816         retrievalMode = Services.prefs.getCharPref(kPrefRetrievalMode);
  1817       } catch (e) {}
  1819       // Under the "automatic"/"automatic-home" retrieval mode, we switch to
  1820       // the "manual" retrieval mode to download MMS for non-active SIM.
  1821       if ((retrievalMode == RETRIEVAL_MODE_AUTOMATIC ||
  1822            retrievalMode == RETRIEVAL_MODE_AUTOMATIC_HOME) &&
  1823           serviceId != this.mmsDefaultServiceId) {
  1824         if (DEBUG) {
  1825           debug("Switch to 'manual' mode to download MMS for non-active SIM: " +
  1826                 "serviceId = " + serviceId + " doesn't equal to " +
  1827                 "mmsDefaultServiceId = " + this.mmsDefaultServiceId);
  1830         retrievalMode = RETRIEVAL_MODE_MANUAL;
  1833       let mmsConnection = gMmsConnections.getConnByServiceId(serviceId);
  1835       let savableMessage = this.convertIntermediateToSavable(mmsConnection,
  1836                                                              notification,
  1837                                                              retrievalMode);
  1839       gMobileMessageDatabaseService
  1840         .saveReceivedMessage(savableMessage,
  1841                              this.saveReceivedMessageCallback
  1842                                  .bind(this,
  1843                                        mmsConnection,
  1844                                        retrievalMode,
  1845                                        savableMessage));
  1846     }).bind(this));
  1847   },
  1849   /**
  1850    * Handle incoming M-Delivery.ind PDU.
  1852    * @param aMsg
  1853    *        The MMS message object.
  1854    */
  1855   handleDeliveryIndication: function(aMsg) {
  1856     let headers = aMsg.headers;
  1857     let envelopeId = headers["message-id"];
  1858     let address = headers.to.address;
  1859     let mmsStatus = headers["x-mms-status"];
  1860     if (DEBUG) {
  1861       debug("Start updating the delivery status for envelopeId: " + envelopeId +
  1862             " address: " + address + " mmsStatus: " + mmsStatus);
  1865     // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.3 "X-Mms-Status",
  1866     // in the M-Delivery.ind the X-Mms-Status could be MMS.MMS_PDU_STATUS_{
  1867     // EXPIRED, RETRIEVED, REJECTED, DEFERRED, UNRECOGNISED, INDETERMINATE,
  1868     // FORWARDED, UNREACHABLE }.
  1869     let deliveryStatus;
  1870     switch (mmsStatus) {
  1871       case MMS.MMS_PDU_STATUS_RETRIEVED:
  1872         deliveryStatus = DELIVERY_STATUS_SUCCESS;
  1873         break;
  1874       case MMS.MMS_PDU_STATUS_EXPIRED:
  1875       case MMS.MMS_PDU_STATUS_REJECTED:
  1876       case MMS.MMS_PDU_STATUS_UNRECOGNISED:
  1877       case MMS.MMS_PDU_STATUS_UNREACHABLE:
  1878         deliveryStatus = DELIVERY_STATUS_REJECTED;
  1879         break;
  1880       case MMS.MMS_PDU_STATUS_DEFERRED:
  1881         deliveryStatus = DELIVERY_STATUS_PENDING;
  1882         break;
  1883       case MMS.MMS_PDU_STATUS_INDETERMINATE:
  1884         deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
  1885         break;
  1886       default:
  1887         if (DEBUG) debug("Cannot handle this MMS status. Returning.");
  1888         return;
  1891     if (DEBUG) debug("Updating the delivery status to: " + deliveryStatus);
  1892     gMobileMessageDatabaseService
  1893       .setMessageDeliveryStatusByEnvelopeId(envelopeId, address, deliveryStatus,
  1894                                             (function(aRv, aDomMessage) {
  1895       if (DEBUG) debug("Marking the delivery status is done.");
  1896       // TODO bug 832140 handle !Components.isSuccessCode(aRv)
  1898       let topic;
  1899       if (mmsStatus === MMS.MMS_PDU_STATUS_RETRIEVED) {
  1900         topic = kSmsDeliverySuccessObserverTopic;
  1902         // Broadcasting a 'sms-delivery-success' system message to open apps.
  1903         this.broadcastMmsSystemMessage(topic, aDomMessage);
  1904       } else if (mmsStatus === MMS.MMS_PDU_STATUS_REJECTED) {
  1905         topic = kSmsDeliveryErrorObserverTopic;
  1906       } else {
  1907         if (DEBUG) debug("Needn't fire event for this MMS status. Returning.");
  1908         return;
  1911       // Notifying observers the delivery status is updated.
  1912       Services.obs.notifyObservers(aDomMessage, topic, null);
  1913     }).bind(this));
  1914   },
  1916   /**
  1917    * Handle incoming M-Read-Orig.ind PDU.
  1919    * @param aIndication
  1920    *        The MMS message object.
  1921    */
  1922   handleReadOriginateIndication: function(aIndication) {
  1924     let headers = aIndication.headers;
  1925     let envelopeId = headers["message-id"];
  1926     let address = headers.from.address;
  1927     let mmsReadStatus = headers["x-mms-read-status"];
  1928     if (DEBUG) {
  1929       debug("Start updating the read status for envelopeId: " + envelopeId +
  1930             ", address: " + address + ", mmsReadStatus: " + mmsReadStatus);
  1933     // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.4 "X-Mms-Read-Status",
  1934     // in M-Read-Rec-Orig.ind the X-Mms-Read-Status could be
  1935     // MMS.MMS_READ_STATUS_{ READ, DELETED_WITHOUT_BEING_READ }.
  1936     let readStatus = mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ
  1937                    ? MMS.DOM_READ_STATUS_SUCCESS
  1938                    : MMS.DOM_READ_STATUS_ERROR;
  1939     if (DEBUG) debug("Updating the read status to: " + readStatus);
  1941     gMobileMessageDatabaseService
  1942       .setMessageReadStatusByEnvelopeId(envelopeId, address, readStatus,
  1943                                         (function(aRv, aDomMessage) {
  1944       if (!Components.isSuccessCode(aRv)) {
  1945         // Notifying observers the read status is error.
  1946         Services.obs.notifyObservers(aDomMessage, kSmsReadSuccessObserverTopic, null);
  1947         return;
  1950       if (DEBUG) debug("Marking the read status is done.");
  1951       let topic;
  1952       if (mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ) {
  1953         topic = kSmsReadSuccessObserverTopic;
  1955         // Broadcasting a 'sms-read-success' system message to open apps.
  1956         this.broadcastMmsSystemMessage(topic, aDomMessage);
  1957       } else {
  1958         topic = kSmsReadErrorObserverTopic;
  1961       // Notifying observers the read status is updated.
  1962       Services.obs.notifyObservers(aDomMessage, topic, null);
  1963     }).bind(this));
  1964   },
  1966   /**
  1967    * A utility function to convert the MmsParameters dictionary object
  1968    * to a database-savable message.
  1970    * @param aMmsConnection
  1971    *        The MMS connection.
  1972    * @param aParams
  1973    *        The MmsParameters dictionay object.
  1974    * @param aMessage (output)
  1975    *        The database-savable message.
  1976    * Return the error code by veryfying if the |aParams| is valid or not.
  1978    * Notes:
  1980    * OMA-TS-MMS-CONF-V1_3-20110913-A section 10.2.2 "Message Content Encoding":
  1982    * A name for multipart object SHALL be encoded using name-parameter for Content-Type
  1983    * header in WSP multipart headers. In decoding, name-parameter of Content-Type SHALL
  1984    * be used if available. If name-parameter of Content-Type is not available, filename
  1985    * parameter of Content-Disposition header SHALL be used if available. If neither
  1986    * name-parameter of Content-Type header nor filename parameter of Content-Disposition
  1987    * header is available, Content-Location header SHALL be used if available.
  1988    */
  1989   createSavableFromParams: function(aMmsConnection, aParams, aMessage) {
  1990     if (DEBUG) debug("createSavableFromParams: aParams: " + JSON.stringify(aParams));
  1992     let isAddrValid = true;
  1993     let smil = aParams.smil;
  1995     // |aMessage.headers|
  1996     let headers = aMessage["headers"] = {};
  1997     let receivers = aParams.receivers;
  1998     if (receivers.length != 0) {
  1999       let headersTo = headers["to"] = [];
  2000       for (let i = 0; i < receivers.length; i++) {
  2001         let receiver = receivers[i];
  2002         let type = MMS.Address.resolveType(receiver);
  2003         let address;
  2004         if (type == "PLMN") {
  2005           address = PhoneNumberUtils.normalize(receiver, false);
  2006           if (!PhoneNumberUtils.isPlainPhoneNumber(address)) {
  2007             isAddrValid = false;
  2009           if (DEBUG) debug("createSavableFromParams: normalize phone number " +
  2010                            "from " + receiver + " to " + address);
  2011         } else {
  2012           address = receiver;
  2013           isAddrValid = false;
  2014           if (DEBUG) debug("Error! Address is invalid to send MMS: " + address);
  2016         headersTo.push({"address": address, "type": type});
  2019     if (aParams.subject) {
  2020       headers["subject"] = aParams.subject;
  2023     // |aMessage.parts|
  2024     let attachments = aParams.attachments;
  2025     if (attachments.length != 0 || smil) {
  2026       let parts = aMessage["parts"] = [];
  2028       // Set the SMIL part if needed.
  2029       if (smil) {
  2030         let part = {
  2031           "headers": {
  2032             "content-type": {
  2033               "media": "application/smil",
  2034               "params": {
  2035                 "name": "smil.xml",
  2036                 "charset": {
  2037                   "charset": "utf-8"
  2040             },
  2041             "content-location": "smil.xml",
  2042             "content-id": "<smil>"
  2043           },
  2044           "content": smil
  2045         };
  2046         parts.push(part);
  2049       // Set other parts for attachments if needed.
  2050       for (let i = 0; i < attachments.length; i++) {
  2051         let attachment = attachments[i];
  2052         let content = attachment.content;
  2053         let location = attachment.location;
  2055         let params = {
  2056           "name": location
  2057         };
  2059         if (content.type && content.type.indexOf("text/") == 0) {
  2060           params.charset = {
  2061             "charset": "utf-8"
  2062           };
  2065         let part = {
  2066           "headers": {
  2067             "content-type": {
  2068               "media": content.type,
  2069               "params": params
  2070             },
  2071             "content-location": location,
  2072             "content-id": attachment.id
  2073           },
  2074           "content": content
  2075         };
  2076         parts.push(part);
  2080     // The following attributes are needed for saving message into DB.
  2081     aMessage["type"] = "mms";
  2082     aMessage["timestamp"] = Date.now();
  2083     aMessage["receivers"] = receivers;
  2084     aMessage["sender"] = aMmsConnection.getPhoneNumber();
  2085     aMessage["iccId"] = aMmsConnection.getIccId();
  2086     try {
  2087       aMessage["deliveryStatusRequested"] =
  2088         Services.prefs.getBoolPref("dom.mms.requestStatusReport");
  2089     } catch (e) {
  2090       aMessage["deliveryStatusRequested"] = false;
  2093     if (DEBUG) debug("createSavableFromParams: aMessage: " +
  2094                      JSON.stringify(aMessage));
  2096     return isAddrValid ? Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR
  2097                        : Ci.nsIMobileMessageCallback.INVALID_ADDRESS_ERROR;
  2098   },
  2100   // nsIMmsService
  2102   mmsDefaultServiceId: 0,
  2104   send: function(aServiceId, aParams, aRequest) {
  2105     if (DEBUG) debug("send: aParams: " + JSON.stringify(aParams));
  2107     // Note that the following sanity checks for |aParams| should be consistent
  2108     // with the checks in SmsIPCService.GetSendMmsMessageRequestFromParams.
  2110     // Check if |aParams| is valid.
  2111     if (aParams == null || typeof aParams != "object") {
  2112       if (DEBUG) debug("Error! 'aParams' should be a non-null object.");
  2113       throw Cr.NS_ERROR_INVALID_ARG;
  2114       return;
  2117     // Check if |receivers| is valid.
  2118     if (!Array.isArray(aParams.receivers)) {
  2119       if (DEBUG) debug("Error! 'receivers' should be an array.");
  2120       throw Cr.NS_ERROR_INVALID_ARG;
  2121       return;
  2124     // Check if |subject| is valid.
  2125     if (aParams.subject != null && typeof aParams.subject != "string") {
  2126       if (DEBUG) debug("Error! 'subject' should be a string if passed.");
  2127       throw Cr.NS_ERROR_INVALID_ARG;
  2128       return;
  2131     // Check if |smil| is valid.
  2132     if (aParams.smil != null && typeof aParams.smil != "string") {
  2133       if (DEBUG) debug("Error! 'smil' should be a string if passed.");
  2134       throw Cr.NS_ERROR_INVALID_ARG;
  2135       return;
  2138     // Check if |attachments| is valid.
  2139     if (!Array.isArray(aParams.attachments)) {
  2140       if (DEBUG) debug("Error! 'attachments' should be an array.");
  2141       throw Cr.NS_ERROR_INVALID_ARG;
  2142       return;
  2145     let self = this;
  2147     let sendTransactionCb = function sendTransactionCb(aDomMessage,
  2148                                                        aErrorCode,
  2149                                                        aEnvelopeId) {
  2150       if (DEBUG) {
  2151         debug("The returned status of sending transaction: " +
  2152               "aErrorCode: " + aErrorCode + " aEnvelopeId: " + aEnvelopeId);
  2155       // If the messsage has been deleted (because the sending process is
  2156       // cancelled), we don't need to reset the its delievery state/status.
  2157       if (aErrorCode == Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR) {
  2158         aRequest.notifySendMessageFailed(aErrorCode);
  2159         Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
  2160         return;
  2163       let isSentSuccess = (aErrorCode == Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR);
  2164       gMobileMessageDatabaseService
  2165         .setMessageDeliveryByMessageId(aDomMessage.id,
  2166                                        null,
  2167                                        isSentSuccess ? DELIVERY_SENT : DELIVERY_ERROR,
  2168                                        isSentSuccess ? null : DELIVERY_STATUS_ERROR,
  2169                                        aEnvelopeId,
  2170                                        function notifySetDeliveryResult(aRv, aDomMessage) {
  2171         if (DEBUG) debug("Marking the delivery state/staus is done. Notify sent or failed.");
  2172         // TODO bug 832140 handle !Components.isSuccessCode(aRv)
  2173         if (!isSentSuccess) {
  2174           if (DEBUG) debug("Sending MMS failed.");
  2175           aRequest.notifySendMessageFailed(aErrorCode);
  2176           Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
  2177           return;
  2180         if (DEBUG) debug("Sending MMS succeeded.");
  2182         // Notifying observers the MMS message is sent.
  2183         self.broadcastSentMessageEvent(aDomMessage);
  2185         // Return the request after sending the MMS message successfully.
  2186         aRequest.notifyMessageSent(aDomMessage);
  2187       });
  2188     };
  2190     let mmsConnection = gMmsConnections.getConnByServiceId(aServiceId);
  2192     let savableMessage = {};
  2193     let errorCode = this.createSavableFromParams(mmsConnection, aParams,
  2194                                                  savableMessage);
  2195     gMobileMessageDatabaseService
  2196       .saveSendingMessage(savableMessage,
  2197                           function notifySendingResult(aRv, aDomMessage) {
  2198       if (!Components.isSuccessCode(aRv)) {
  2199         if (DEBUG) debug("Error! Fail to save sending message! rv = " + aRv);
  2200         aRequest.notifySendMessageFailed(
  2201           gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(aRv));
  2202         Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
  2203         return;
  2206       if (DEBUG) debug("Saving sending message is done. Start to send.");
  2208       Services.obs.notifyObservers(aDomMessage, kSmsSendingObserverTopic, null);
  2210       if (errorCode !== Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR) {
  2211         if (DEBUG) debug("Error! The params for sending MMS are invalid.");
  2212         sendTransactionCb(aDomMessage, errorCode, null);
  2213         return;
  2216       // Check radio state in prior to default service Id.
  2217       if (getRadioDisabledState()) {
  2218         if (DEBUG) debug("Error! Radio is disabled when sending MMS.");
  2219         sendTransactionCb(aDomMessage,
  2220                           Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR,
  2221                           null);
  2222         return;
  2225       // To support DSDS, we have to stop users sending MMS when the selected
  2226       // SIM is not active, thus avoiding the data disconnection of the current
  2227       // SIM. Users have to manually swith the default SIM before sending.
  2228       if (mmsConnection.serviceId != self.mmsDefaultServiceId) {
  2229         if (DEBUG) debug("RIL service is not active to send MMS.");
  2230         sendTransactionCb(aDomMessage,
  2231                           Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR,
  2232                           null);
  2233         return;
  2236       // This is the entry point starting to send MMS.
  2237       let sendTransaction;
  2238       try {
  2239         sendTransaction =
  2240           new SendTransaction(mmsConnection, aDomMessage.id, savableMessage,
  2241                               savableMessage["deliveryStatusRequested"]);
  2242       } catch (e) {
  2243         if (DEBUG) debug("Exception: fail to create a SendTransaction instance.");
  2244         sendTransactionCb(aDomMessage,
  2245                           Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null);
  2246         return;
  2248       sendTransaction.run(function callback(aMmsStatus, aMsg) {
  2249         if (DEBUG) debug("The sending status of sendTransaction.run(): " + aMmsStatus);
  2250         let errorCode;
  2251         if (aMmsStatus == _MMS_ERROR_MESSAGE_DELETED) {
  2252           errorCode = Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR;
  2253         } else if (aMmsStatus == _MMS_ERROR_RADIO_DISABLED) {
  2254           errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR;
  2255         } else if (aMmsStatus == _MMS_ERROR_NO_SIM_CARD) {
  2256           errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
  2257         } else if (aMmsStatus == _MMS_ERROR_SIM_CARD_CHANGED) {
  2258           errorCode = Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR;
  2259         } else if (aMmsStatus != MMS.MMS_PDU_ERROR_OK) {
  2260           errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR;
  2261         } else {
  2262           errorCode = Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR;
  2264         let envelopeId =
  2265           aMsg && aMsg.headers && aMsg.headers["message-id"] || null;
  2266         sendTransactionCb(aDomMessage, errorCode, envelopeId);
  2267       });
  2268     });
  2269   },
  2271   retrieve: function(aMessageId, aRequest) {
  2272     if (DEBUG) debug("Retrieving message with ID " + aMessageId);
  2273     gMobileMessageDatabaseService.getMessageRecordById(aMessageId,
  2274         (function notifyResult(aRv, aMessageRecord, aDomMessage) {
  2275       if (!Components.isSuccessCode(aRv)) {
  2276         if (DEBUG) debug("Function getMessageRecordById() return error: " + aRv);
  2277         aRequest.notifyGetMessageFailed(
  2278           gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(aRv));
  2279         return;
  2281       if ("mms" != aMessageRecord.type) {
  2282         if (DEBUG) debug("Type of message record is not 'mms'.");
  2283         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  2284         return;
  2286       if (!aMessageRecord.headers) {
  2287         if (DEBUG) debug("Must need the MMS' headers to proceed the retrieve.");
  2288         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  2289         return;
  2291       if (!aMessageRecord.headers["x-mms-content-location"]) {
  2292         if (DEBUG) debug("Can't find mms content url in database.");
  2293         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  2294         return;
  2296       if (DELIVERY_NOT_DOWNLOADED != aMessageRecord.delivery) {
  2297         if (DEBUG) debug("Delivery of message record is not 'not-downloaded'.");
  2298         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  2299         return;
  2301       let deliveryStatus = aMessageRecord.deliveryInfo[0].deliveryStatus;
  2302       if (DELIVERY_STATUS_PENDING == deliveryStatus) {
  2303         if (DEBUG) debug("Delivery status of message record is 'pending'.");
  2304         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  2305         return;
  2308       // Cite 6.2 "Multimedia Message Notification" in OMA-TS-MMS_ENC-V1_3-20110913-A:
  2309       // The field has only one format, relative. The recipient client calculates
  2310       // this length of time relative to the time it receives the notification.
  2311       if (aMessageRecord.headers["x-mms-expiry"] != undefined) {
  2312         let expiryDate = aMessageRecord.timestamp +
  2313                          aMessageRecord.headers["x-mms-expiry"] * 1000;
  2314         if (expiryDate < Date.now()) {
  2315           if (DEBUG) debug("The message to be retrieved is expired.");
  2316           aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR);
  2317           return;
  2321       // IccInfo in RadioInterface is not available when radio is off and
  2322       // NO_SIM_CARD_ERROR will be replied instead of RADIO_DISABLED_ERROR.
  2323       // Hence, for manual retrieving, instead of checking radio state later
  2324       // in MmsConnection.acquire(), We have to check radio state in prior to
  2325       // iccId to return the error correctly.
  2326       if (getRadioDisabledState()) {
  2327         if (DEBUG) debug("Error! Radio is disabled when retrieving MMS.");
  2328         aRequest.notifyGetMessageFailed(
  2329           Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR);
  2330         return;
  2333       // Get MmsConnection based on the saved MMS message record's ICC ID,
  2334       // which could fail when the corresponding SIM card isn't installed.
  2335       let mmsConnection;
  2336       try {
  2337         mmsConnection = gMmsConnections.getConnByIccId(aMessageRecord.iccId);
  2338       } catch (e) {
  2339         if (DEBUG) debug("Failed to get connection by IccId. e= " + e);
  2340         let error = (e === _MMS_ERROR_SIM_NOT_MATCHED) ?
  2341                       Ci.nsIMobileMessageCallback.SIM_NOT_MATCHED_ERROR :
  2342                       Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
  2343         aRequest.notifyGetMessageFailed(error);
  2344         return;
  2347       // To support DSDS, we have to stop users retrieving MMS when the needed
  2348       // SIM is not active, thus avoiding the data disconnection of the current
  2349       // SIM. Users have to manually swith the default SIM before retrieving.
  2350       if (mmsConnection.serviceId != this.mmsDefaultServiceId) {
  2351         if (DEBUG) debug("RIL service is not active to retrieve MMS.");
  2352         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR);
  2353         return;
  2356       let url =  aMessageRecord.headers["x-mms-content-location"].uri;
  2357       // For X-Mms-Report-Allowed
  2358       let wish = aMessageRecord.headers["x-mms-delivery-report"];
  2359       let responseNotify = function responseNotify(mmsStatus, retrievedMsg) {
  2360         // If the messsage has been deleted (because the retrieving process is
  2361         // cancelled), we don't need to reset the its delievery state/status.
  2362         if (mmsStatus == _MMS_ERROR_MESSAGE_DELETED) {
  2363           aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR);
  2364           return;
  2367         // If the mmsStatus is still MMS_PDU_STATUS_DEFERRED after retry,
  2368         // we should not store it into database and update its delivery
  2369         // status to 'error'.
  2370         if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) {
  2371           if (DEBUG) debug("RetrieveMessage fail after retry.");
  2372           let errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR;
  2373           if (mmsStatus == _MMS_ERROR_RADIO_DISABLED) {
  2374             errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR;
  2375           } else if (mmsStatus == _MMS_ERROR_NO_SIM_CARD) {
  2376             errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
  2377           } else if (mmsStatus == _MMS_ERROR_SIM_CARD_CHANGED) {
  2378             errorCode = Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR;
  2380           gMobileMessageDatabaseService
  2381             .setMessageDeliveryByMessageId(aMessageId,
  2382                                            null,
  2383                                            null,
  2384                                            DELIVERY_STATUS_ERROR,
  2385                                            null,
  2386                                            function() {
  2387             aRequest.notifyGetMessageFailed(errorCode);
  2388           });
  2389           return;
  2391         // In OMA-TS-MMS_ENC-V1_3, Table 5 in page 25. This header field
  2392         // (x-mms-transaction-id) SHALL be present when the MMS Proxy relay
  2393         // seeks an acknowledgement for the MM delivered though M-Retrieve.conf
  2394         // PDU during deferred retrieval. This transaction ID is used by the MMS
  2395         // Client and MMS Proxy-Relay to provide linkage between the originated
  2396         // M-Retrieve.conf and the response M-Acknowledge.ind PDUs.
  2397         let transactionId = retrievedMsg.headers["x-mms-transaction-id"];
  2399         // The absence of the field does not indicate any default
  2400         // value. So we go checking the same field in retrieved
  2401         // message instead.
  2402         if (wish == null && retrievedMsg) {
  2403           wish = retrievedMsg.headers["x-mms-delivery-report"];
  2405         let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
  2406                                                   wish);
  2408         if (DEBUG) debug("retrievedMsg = " + JSON.stringify(retrievedMsg));
  2409         aMessageRecord = this.mergeRetrievalConfirmation(mmsConnection,
  2410                                                          retrievedMsg,
  2411                                                          aMessageRecord);
  2413         gMobileMessageDatabaseService.saveReceivedMessage(aMessageRecord,
  2414                                                           (function(rv, domMessage) {
  2415           let success = Components.isSuccessCode(rv);
  2416           if (!success) {
  2417             // At this point we could send a message to content to
  2418             // notify the user that storing an incoming MMS failed, most
  2419             // likely due to a full disk.
  2420             if (DEBUG) debug("Could not store MMS, error code " + rv);
  2421             aRequest.notifyGetMessageFailed(
  2422               gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv));
  2423             return;
  2426           // Notifying observers a new MMS message is retrieved.
  2427           this.broadcastReceivedMessageEvent(domMessage);
  2429           // Return the request after retrieving the MMS message successfully.
  2430           aRequest.notifyMessageGot(domMessage);
  2432           // Cite 6.3.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A:
  2433           // If an acknowledgement is requested, the MMS Client SHALL respond
  2434           // with an M-Acknowledge.ind PDU to the MMS Proxy-Relay that supports
  2435           // the specific MMS Client. The M-Acknowledge.ind PDU confirms
  2436           // successful message retrieval to the MMS Proxy Relay.
  2437           let transaction = new AcknowledgeTransaction(mmsConnection,
  2438                                                        transactionId,
  2439                                                        reportAllowed);
  2440           transaction.run();
  2441         }).bind(this));
  2442       };
  2444       // Update the delivery status to pending in DB.
  2445       gMobileMessageDatabaseService
  2446         .setMessageDeliveryByMessageId(aMessageId,
  2447                                        null,
  2448                                        null,
  2449                                        DELIVERY_STATUS_PENDING,
  2450                                        null,
  2451                                        (function(rv) {
  2452           let success = Components.isSuccessCode(rv);
  2453           if (!success) {
  2454             if (DEBUG) debug("Could not change the delivery status, error code " + rv);
  2455             aRequest.notifyGetMessageFailed(
  2456               gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv));
  2457             return;
  2460           this.retrieveMessage(mmsConnection,
  2461                                url,
  2462                                responseNotify.bind(this),
  2463                                aDomMessage);
  2464         }).bind(this));
  2465     }).bind(this));
  2466   },
  2468   sendReadReport: function(messageID, toAddress, iccId) {
  2469     if (DEBUG) {
  2470       debug("messageID: " + messageID + " toAddress: " +
  2471             JSON.stringify(toAddress));
  2474     // Get MmsConnection based on the saved MMS message record's ICC ID,
  2475     // which could fail when the corresponding SIM card isn't installed.
  2476     let mmsConnection;
  2477     try {
  2478       mmsConnection = gMmsConnections.getConnByIccId(iccId);
  2479     } catch (e) {
  2480       if (DEBUG) debug("Failed to get connection by IccId. e = " + e);
  2481       return;
  2484     try {
  2485       let transaction =
  2486         new ReadRecTransaction(mmsConnection, messageID, toAddress);
  2487       transaction.run();
  2488     } catch (e) {
  2489       if (DEBUG) debug("sendReadReport fail. e = " + e);
  2491   },
  2493   // nsIWapPushApplication
  2495   receiveWapPush: function(array, length, offset, options) {
  2496     let data = {array: array, offset: offset};
  2497     let msg = MMS.PduHelper.parse(data, null);
  2498     if (!msg) {
  2499       return false;
  2501     if (DEBUG) debug("receiveWapPush: msg = " + JSON.stringify(msg));
  2503     switch (msg.type) {
  2504       case MMS.MMS_PDU_TYPE_NOTIFICATION_IND:
  2505         this.handleNotificationIndication(options.serviceId, msg);
  2506         break;
  2507       case MMS.MMS_PDU_TYPE_DELIVERY_IND:
  2508         this.handleDeliveryIndication(msg);
  2509         break;
  2510       case MMS.MMS_PDU_TYPE_READ_ORIG_IND:
  2511         this.handleReadOriginateIndication(msg);
  2512         break;
  2513       default:
  2514         if (DEBUG) debug("Unsupported X-MMS-Message-Type: " + msg.type);
  2515         break;
  2517   },
  2519   // nsIObserver
  2521   observe: function(aSubject, aTopic, aData) {
  2522     switch (aTopic) {
  2523       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
  2524         if (aData === kPrefDefaultServiceId) {
  2525           this.mmsDefaultServiceId = getDefaultServiceId();
  2527         break;
  2530 };
  2532 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MmsService]);

mercurial