dom/mobilemessage/src/gonk/MmsService.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/mobilemessage/src/gonk/MmsService.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2532 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 + * vim: sw=2 ts=2 sts=2 et filetype=javascript
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +"use strict";
    1.11 +
    1.12 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    1.13 +
    1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.15 +Cu.import("resource://gre/modules/Services.jsm");
    1.16 +
    1.17 +Cu.import("resource://gre/modules/NetUtil.jsm");
    1.18 +Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
    1.19 +
    1.20 +const RIL_MMSSERVICE_CONTRACTID = "@mozilla.org/mms/rilmmsservice;1";
    1.21 +const RIL_MMSSERVICE_CID = Components.ID("{217ddd76-75db-4210-955d-8806cd8d87f9}");
    1.22 +
    1.23 +let DEBUG = false;
    1.24 +function debug(s) {
    1.25 +  dump("-@- MmsService: " + s + "\n");
    1.26 +};
    1.27 +
    1.28 +// Read debug setting from pref.
    1.29 +try {
    1.30 +  let debugPref = Services.prefs.getBoolPref("mms.debugging.enabled");
    1.31 +  DEBUG = DEBUG || debugPref;
    1.32 +} catch (e) {}
    1.33 +
    1.34 +const kSmsSendingObserverTopic           = "sms-sending";
    1.35 +const kSmsSentObserverTopic              = "sms-sent";
    1.36 +const kSmsFailedObserverTopic            = "sms-failed";
    1.37 +const kSmsReceivedObserverTopic          = "sms-received";
    1.38 +const kSmsRetrievingObserverTopic        = "sms-retrieving";
    1.39 +const kSmsDeliverySuccessObserverTopic   = "sms-delivery-success";
    1.40 +const kSmsDeliveryErrorObserverTopic     = "sms-delivery-error";
    1.41 +const kSmsReadSuccessObserverTopic       = "sms-read-success";
    1.42 +const kSmsReadErrorObserverTopic         = "sms-read-error";
    1.43 +
    1.44 +const NS_XPCOM_SHUTDOWN_OBSERVER_ID      = "xpcom-shutdown";
    1.45 +const kNetworkConnStateChangedTopic      = "network-connection-state-changed";
    1.46 +const kMobileMessageDeletedObserverTopic = "mobile-message-deleted";
    1.47 +
    1.48 +const kPrefRilRadioDisabled              = "ril.radio.disabled";
    1.49 +
    1.50 +// HTTP status codes:
    1.51 +// @see http://tools.ietf.org/html/rfc2616#page-39
    1.52 +const HTTP_STATUS_OK = 200;
    1.53 +
    1.54 +// Non-standard HTTP status for internal use.
    1.55 +const _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS  =  0;
    1.56 +const _HTTP_STATUS_USER_CANCELLED              = -1;
    1.57 +const _HTTP_STATUS_RADIO_DISABLED              = -2;
    1.58 +const _HTTP_STATUS_NO_SIM_CARD                 = -3;
    1.59 +const _HTTP_STATUS_ACQUIRE_TIMEOUT             = -4;
    1.60 +
    1.61 +// Non-standard MMS status for internal use.
    1.62 +const _MMS_ERROR_MESSAGE_DELETED               = -1;
    1.63 +const _MMS_ERROR_RADIO_DISABLED                = -2;
    1.64 +const _MMS_ERROR_NO_SIM_CARD                   = -3;
    1.65 +const _MMS_ERROR_SIM_CARD_CHANGED              = -4;
    1.66 +const _MMS_ERROR_SHUTDOWN                      = -5;
    1.67 +const _MMS_ERROR_USER_CANCELLED_NO_REASON      = -6;
    1.68 +const _MMS_ERROR_SIM_NOT_MATCHED               = -7;
    1.69 +
    1.70 +const CONFIG_SEND_REPORT_NEVER       = 0;
    1.71 +const CONFIG_SEND_REPORT_DEFAULT_NO  = 1;
    1.72 +const CONFIG_SEND_REPORT_DEFAULT_YES = 2;
    1.73 +const CONFIG_SEND_REPORT_ALWAYS      = 3;
    1.74 +
    1.75 +const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
    1.76 +
    1.77 +const TIME_TO_BUFFER_MMS_REQUESTS    = 30000;
    1.78 +const PREF_TIME_TO_RELEASE_MMS_CONNECTION =
    1.79 +  Services.prefs.getIntPref("network.gonk.ms-release-mms-connection");
    1.80 +
    1.81 +const kPrefRetrievalMode       = 'dom.mms.retrieval_mode';
    1.82 +const RETRIEVAL_MODE_MANUAL    = "manual";
    1.83 +const RETRIEVAL_MODE_AUTOMATIC = "automatic";
    1.84 +const RETRIEVAL_MODE_AUTOMATIC_HOME = "automatic-home";
    1.85 +const RETRIEVAL_MODE_NEVER     = "never";
    1.86 +
    1.87 +//Internal const values.
    1.88 +const DELIVERY_RECEIVED       = "received";
    1.89 +const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
    1.90 +const DELIVERY_SENDING        = "sending";
    1.91 +const DELIVERY_SENT           = "sent";
    1.92 +const DELIVERY_ERROR          = "error";
    1.93 +
    1.94 +const DELIVERY_STATUS_SUCCESS        = "success";
    1.95 +const DELIVERY_STATUS_PENDING        = "pending";
    1.96 +const DELIVERY_STATUS_ERROR          = "error";
    1.97 +const DELIVERY_STATUS_REJECTED       = "rejected";
    1.98 +const DELIVERY_STATUS_MANUAL         = "manual";
    1.99 +const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
   1.100 +
   1.101 +const PREF_SEND_RETRY_COUNT =
   1.102 +  Services.prefs.getIntPref("dom.mms.sendRetryCount");
   1.103 +
   1.104 +const PREF_SEND_RETRY_INTERVAL =
   1.105 +  Services.prefs.getIntPref("dom.mms.sendRetryInterval");
   1.106 +
   1.107 +const PREF_RETRIEVAL_RETRY_COUNT =
   1.108 +  Services.prefs.getIntPref("dom.mms.retrievalRetryCount");
   1.109 +
   1.110 +const PREF_RETRIEVAL_RETRY_INTERVALS = (function() {
   1.111 +  let intervals =
   1.112 +    Services.prefs.getCharPref("dom.mms.retrievalRetryIntervals").split(",");
   1.113 +  for (let i = 0; i < PREF_RETRIEVAL_RETRY_COUNT; ++i) {
   1.114 +    intervals[i] = parseInt(intervals[i], 10);
   1.115 +    // If one of the intervals isn't valid (e.g., 0 or NaN),
   1.116 +    // assign a 10-minute interval to it as a default.
   1.117 +    if (!intervals[i]) {
   1.118 +      intervals[i] = 600000;
   1.119 +    }
   1.120 +  }
   1.121 +  intervals.length = PREF_RETRIEVAL_RETRY_COUNT;
   1.122 +  return intervals;
   1.123 +})();
   1.124 +
   1.125 +const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
   1.126 +const kPrefDefaultServiceId = "dom.mms.defaultServiceId";
   1.127 +
   1.128 +XPCOMUtils.defineLazyServiceGetter(this, "gpps",
   1.129 +                                   "@mozilla.org/network/protocol-proxy-service;1",
   1.130 +                                   "nsIProtocolProxyService");
   1.131 +
   1.132 +XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
   1.133 +                                   "@mozilla.org/uuid-generator;1",
   1.134 +                                   "nsIUUIDGenerator");
   1.135 +
   1.136 +XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageDatabaseService",
   1.137 +                                   "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1",
   1.138 +                                   "nsIRilMobileMessageDatabaseService");
   1.139 +
   1.140 +XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
   1.141 +                                   "@mozilla.org/mobilemessage/mobilemessageservice;1",
   1.142 +                                   "nsIMobileMessageService");
   1.143 +
   1.144 +XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
   1.145 +                                   "@mozilla.org/system-message-internal;1",
   1.146 +                                   "nsISystemMessagesInternal");
   1.147 +
   1.148 +XPCOMUtils.defineLazyServiceGetter(this, "gRil",
   1.149 +                                   "@mozilla.org/ril;1",
   1.150 +                                   "nsIRadioInterfaceLayer");
   1.151 +
   1.152 +XPCOMUtils.defineLazyGetter(this, "MMS", function() {
   1.153 +  let MMS = {};
   1.154 +  Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
   1.155 +  return MMS;
   1.156 +});
   1.157 +
   1.158 +// Internal Utilities
   1.159 +
   1.160 +/**
   1.161 + * Return default service Id for MMS.
   1.162 + */
   1.163 +function getDefaultServiceId() {
   1.164 +  let id = Services.prefs.getIntPref(kPrefDefaultServiceId);
   1.165 +  let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces);
   1.166 +
   1.167 +  if (id >= numRil || id < 0) {
   1.168 +    id = 0;
   1.169 +  }
   1.170 +
   1.171 +  return id;
   1.172 +}
   1.173 +
   1.174 +/**
   1.175 + * Return Radio disabled state.
   1.176 + */
   1.177 +function getRadioDisabledState() {
   1.178 +  let state;
   1.179 +  try {
   1.180 +    state = Services.prefs.getBoolPref(kPrefRilRadioDisabled);
   1.181 +  } catch (e) {
   1.182 +    if (DEBUG) debug("Getting preference 'ril.radio.disabled' fails.");
   1.183 +    state = false;
   1.184 +  }
   1.185 +
   1.186 +  return state;
   1.187 +}
   1.188 +
   1.189 +/**
   1.190 + * Helper Class to control MMS Data Connection.
   1.191 + */
   1.192 +function MmsConnection(aServiceId) {
   1.193 +  this.serviceId = aServiceId;
   1.194 +  this.radioInterface = gRil.getRadioInterface(aServiceId);
   1.195 +};
   1.196 +
   1.197 +MmsConnection.prototype = {
   1.198 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
   1.199 +
   1.200 +  /** MMS proxy settings. */
   1.201 +  mmsc:     "",
   1.202 +  mmsProxy: "",
   1.203 +  mmsPort:  -1,
   1.204 +
   1.205 +  setApnSetting: function(network) {
   1.206 +    this.mmsc = network.mmsc;
   1.207 +    this.mmsProxy = network.mmsProxy;
   1.208 +    this.mmsPort = network.mmsPort;
   1.209 +  },
   1.210 +
   1.211 +  get proxyInfo() {
   1.212 +    if (!this.mmsProxy) {
   1.213 +      if (DEBUG) debug("getProxyInfo: MMS proxy is not available.");
   1.214 +      return null;
   1.215 +    }
   1.216 +
   1.217 +    let port = this.mmsPort;
   1.218 +
   1.219 +    if (port <= 0) {
   1.220 +      port = 80;
   1.221 +      if (DEBUG) debug("getProxyInfo: port is not valid. Set to defult (80).");
   1.222 +    }
   1.223 +
   1.224 +    let proxyInfo =
   1.225 +      gpps.newProxyInfo("http", this.mmsProxy, port,
   1.226 +                        Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST,
   1.227 +                        -1, null);
   1.228 +    if (DEBUG) debug("getProxyInfo: " + JSON.stringify(proxyInfo));
   1.229 +
   1.230 +    return proxyInfo;
   1.231 +  },
   1.232 +
   1.233 +  connected: false,
   1.234 +
   1.235 +  //A queue to buffer the MMS HTTP requests when the MMS network
   1.236 +  //is not yet connected. The buffered requests will be cleared
   1.237 +  //if the MMS network fails to be connected within a timer.
   1.238 +  pendingCallbacks: [],
   1.239 +
   1.240 +  /** MMS network connection reference count. */
   1.241 +  refCount: 0,
   1.242 +
   1.243 +  connectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
   1.244 +
   1.245 +  disconnectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
   1.246 +
   1.247 +  /**
   1.248 +   * Callback when |connectTimer| is timeout or cancelled by shutdown.
   1.249 +   */
   1.250 +  flushPendingCallbacks: function(status) {
   1.251 +    if (DEBUG) debug("flushPendingCallbacks: " + this.pendingCallbacks.length
   1.252 +                     + " pending callbacks with status: " + status);
   1.253 +    while (this.pendingCallbacks.length) {
   1.254 +      let callback = this.pendingCallbacks.shift();
   1.255 +      let connected = (status == _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS);
   1.256 +      callback(connected, status);
   1.257 +    }
   1.258 +  },
   1.259 +
   1.260 +  /**
   1.261 +   * Callback when |disconnectTimer| is timeout or cancelled by shutdown.
   1.262 +   */
   1.263 +  onDisconnectTimerTimeout: function() {
   1.264 +    if (DEBUG) debug("onDisconnectTimerTimeout: deactivate the MMS data call.");
   1.265 +    if (this.connected) {
   1.266 +      this.radioInterface.deactivateDataCallByType("mms");
   1.267 +    }
   1.268 +  },
   1.269 +
   1.270 +  init: function() {
   1.271 +    Services.obs.addObserver(this, kNetworkConnStateChangedTopic,
   1.272 +                             false);
   1.273 +    Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
   1.274 +
   1.275 +    this.connected = this.radioInterface.getDataCallStateByType("mms") ==
   1.276 +      Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
   1.277 +    // If the MMS network is connected during the initialization, it means the
   1.278 +    // MMS network must share the same APN with the mobile network by default.
   1.279 +    // Under this case, |networkManager.active| should keep the mobile network,
   1.280 +    // which is supposed be an instance of |nsIRilNetworkInterface| for sure.
   1.281 +    if (this.connected) {
   1.282 +      let networkManager =
   1.283 +        Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
   1.284 +      let activeNetwork = networkManager.active;
   1.285 +
   1.286 +      let rilNetwork = activeNetwork.QueryInterface(Ci.nsIRilNetworkInterface);
   1.287 +      if (rilNetwork.serviceId != this.serviceId) {
   1.288 +        if (DEBUG) debug("Sevice ID between active/MMS network doesn't match.");
   1.289 +        return;
   1.290 +      }
   1.291 +
   1.292 +      // Set up the MMS APN setting based on the connected MMS network,
   1.293 +      // which is going to be used for the HTTP requests later.
   1.294 +      this.setApnSetting(rilNetwork);
   1.295 +    }
   1.296 +  },
   1.297 +
   1.298 +  /**
   1.299 +   * Return the roaming status of voice call.
   1.300 +   *
   1.301 +   * @return true if voice call is roaming.
   1.302 +   */
   1.303 +  isVoiceRoaming: function() {
   1.304 +    let isRoaming = this.radioInterface.rilContext.voice.roaming;
   1.305 +    if (DEBUG) debug("isVoiceRoaming = " + isRoaming);
   1.306 +    return isRoaming;
   1.307 +  },
   1.308 +
   1.309 +  /**
   1.310 +   * Get phone number from iccInfo.
   1.311 +   *
   1.312 +   * If the icc card is gsm card, the phone number is in msisdn.
   1.313 +   * @see nsIDOMMozGsmIccInfo
   1.314 +   *
   1.315 +   * Otherwise, the phone number is in mdn.
   1.316 +   * @see nsIDOMMozCdmaIccInfo
   1.317 +   */
   1.318 +  getPhoneNumber: function() {
   1.319 +    let iccInfo = this.radioInterface.rilContext.iccInfo;
   1.320 +
   1.321 +    if (!iccInfo) {
   1.322 +      return null;
   1.323 +    }
   1.324 +
   1.325 +    let number = (iccInfo instanceof Ci.nsIDOMMozGsmIccInfo)
   1.326 +               ? iccInfo.msisdn : iccInfo.mdn;
   1.327 +
   1.328 +    // Workaround an xpconnect issue with undefined string objects.
   1.329 +    // See bug 808220
   1.330 +    if (number === undefined || number === "undefined") {
   1.331 +      return null;
   1.332 +    }
   1.333 +
   1.334 +    return number;
   1.335 +  },
   1.336 +
   1.337 +  /**
   1.338 +  * A utility function to get the ICC ID of the SIM card (if installed).
   1.339 +  */
   1.340 +  getIccId: function() {
   1.341 +    let iccInfo = this.radioInterface.rilContext.iccInfo;
   1.342 +
   1.343 +    if (!iccInfo) {
   1.344 +      return null;
   1.345 +    }
   1.346 +
   1.347 +    let iccId = iccInfo.iccid;
   1.348 +
   1.349 +    // Workaround an xpconnect issue with undefined string objects.
   1.350 +    // See bug 808220
   1.351 +    if (iccId === undefined || iccId === "undefined") {
   1.352 +      return null;
   1.353 +    }
   1.354 +
   1.355 +    return iccId;
   1.356 +  },
   1.357 +
   1.358 +  /**
   1.359 +   * Acquire the MMS network connection.
   1.360 +   *
   1.361 +   * @param callback
   1.362 +   *        Callback function when either the connection setup is done,
   1.363 +   *        timeout, or failed. Parameters are:
   1.364 +   *        - A boolean value indicates whether the connection is ready.
   1.365 +   *        - Acquire connection status: _HTTP_STATUS_ACQUIRE_*.
   1.366 +   *
   1.367 +   * @return true if the callback for MMS network connection is done; false
   1.368 +   *         otherwise.
   1.369 +   */
   1.370 +  acquire: function(callback) {
   1.371 +    this.refCount++;
   1.372 +    this.connectTimer.cancel();
   1.373 +    this.disconnectTimer.cancel();
   1.374 +
   1.375 +    // If the MMS network is not yet connected, buffer the
   1.376 +    // MMS request and try to setup the MMS network first.
   1.377 +    if (!this.connected) {
   1.378 +      this.pendingCallbacks.push(callback);
   1.379 +
   1.380 +      let errorStatus;
   1.381 +      if (getRadioDisabledState()) {
   1.382 +        if (DEBUG) debug("Error! Radio is disabled when sending MMS.");
   1.383 +        errorStatus = _HTTP_STATUS_RADIO_DISABLED;
   1.384 +      } else if (this.radioInterface.rilContext.cardState != "ready") {
   1.385 +        if (DEBUG) debug("Error! SIM card is not ready when sending MMS.");
   1.386 +        errorStatus = _HTTP_STATUS_NO_SIM_CARD;
   1.387 +      }
   1.388 +      if (errorStatus != null) {
   1.389 +        this.flushPendingCallbacks(errorStatus);
   1.390 +        return true;
   1.391 +      }
   1.392 +
   1.393 +      if (DEBUG) debug("acquire: buffer the MMS request and setup the MMS data call.");
   1.394 +      this.radioInterface.setupDataCallByType("mms");
   1.395 +
   1.396 +      // Set a timer to clear the buffered MMS requests if the
   1.397 +      // MMS network fails to be connected within a time period.
   1.398 +      this.connectTimer.
   1.399 +        initWithCallback(this.flushPendingCallbacks.bind(this, _HTTP_STATUS_ACQUIRE_TIMEOUT),
   1.400 +                         TIME_TO_BUFFER_MMS_REQUESTS,
   1.401 +                         Ci.nsITimer.TYPE_ONE_SHOT);
   1.402 +      return false;
   1.403 +    }
   1.404 +
   1.405 +    callback(true, _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS);
   1.406 +    return true;
   1.407 +  },
   1.408 +
   1.409 +  /**
   1.410 +   * Release the MMS network connection.
   1.411 +   */
   1.412 +  release: function() {
   1.413 +    this.refCount--;
   1.414 +    if (this.refCount <= 0) {
   1.415 +      this.refCount = 0;
   1.416 +
   1.417 +      // The waiting is too small, just skip the timer creation.
   1.418 +      if (PREF_TIME_TO_RELEASE_MMS_CONNECTION < 1000) {
   1.419 +        this.onDisconnectTimerTimeout();
   1.420 +        return;
   1.421 +      }
   1.422 +
   1.423 +      // Set a timer to delay the release of MMS network connection,
   1.424 +      // since the MMS requests often come consecutively in a short time.
   1.425 +      this.disconnectTimer.
   1.426 +        initWithCallback(this.onDisconnectTimerTimeout.bind(this),
   1.427 +                         PREF_TIME_TO_RELEASE_MMS_CONNECTION,
   1.428 +                         Ci.nsITimer.TYPE_ONE_SHOT);
   1.429 +    }
   1.430 +  },
   1.431 +
   1.432 +  shutdown: function() {
   1.433 +    Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
   1.434 +    Services.obs.removeObserver(this, kNetworkConnStateChangedTopic);
   1.435 +
   1.436 +    this.connectTimer.cancel();
   1.437 +    this.flushPendingCallbacks(_HTTP_STATUS_RADIO_DISABLED);
   1.438 +    this.disconnectTimer.cancel();
   1.439 +    this.onDisconnectTimerTimeout();
   1.440 +  },
   1.441 +
   1.442 +  // nsIObserver
   1.443 +
   1.444 +  observe: function(subject, topic, data) {
   1.445 +    switch (topic) {
   1.446 +      case kNetworkConnStateChangedTopic: {
   1.447 +        // The network for MMS connection must be nsIRilNetworkInterface.
   1.448 +        if (!(subject instanceof Ci.nsIRilNetworkInterface)) {
   1.449 +          return;
   1.450 +        }
   1.451 +
   1.452 +        // Check if the network state change belongs to this service.
   1.453 +        let network = subject.QueryInterface(Ci.nsIRilNetworkInterface);
   1.454 +        if (network.serviceId != this.serviceId) {
   1.455 +          return;
   1.456 +        }
   1.457 +
   1.458 +        // We only need to capture the state change of MMS network. Using
   1.459 +        // |network.state| isn't reliable due to the possibilty of shared APN.
   1.460 +        let connected =
   1.461 +          this.radioInterface.getDataCallStateByType("mms") ==
   1.462 +            Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
   1.463 +
   1.464 +        // Return if the MMS network state doesn't change, where the network
   1.465 +        // state change can come from other non-MMS networks.
   1.466 +        if (connected == this.connected) {
   1.467 +          return;
   1.468 +        }
   1.469 +
   1.470 +        this.connected = connected;
   1.471 +        if (!this.connected) {
   1.472 +          return;
   1.473 +        }
   1.474 +
   1.475 +        // Set up the MMS APN setting based on the connected MMS network,
   1.476 +        // which is going to be used for the HTTP requests later.
   1.477 +        this.setApnSetting(network);
   1.478 +
   1.479 +        if (DEBUG) debug("Got the MMS network connected! Resend the buffered " +
   1.480 +                         "MMS requests: number: " + this.pendingCallbacks.length);
   1.481 +        this.connectTimer.cancel();
   1.482 +        this.flushPendingCallbacks(_HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS)
   1.483 +        break;
   1.484 +      }
   1.485 +      case NS_XPCOM_SHUTDOWN_OBSERVER_ID: {
   1.486 +        this.shutdown();
   1.487 +      }
   1.488 +    }
   1.489 +  }
   1.490 +};
   1.491 +
   1.492 +XPCOMUtils.defineLazyGetter(this, "gMmsConnections", function() {
   1.493 +  return {
   1.494 +    _connections: null,
   1.495 +    getConnByServiceId: function(id) {
   1.496 +      if (!this._connections) {
   1.497 +        this._connections = [];
   1.498 +      }
   1.499 +
   1.500 +      let conn = this._connections[id];
   1.501 +      if (conn) {
   1.502 +        return conn;
   1.503 +      }
   1.504 +
   1.505 +      conn = this._connections[id] = new MmsConnection(id);
   1.506 +      conn.init();
   1.507 +      return conn;
   1.508 +    },
   1.509 +    getConnByIccId: function(aIccId) {
   1.510 +      if (!aIccId) {
   1.511 +        // If the ICC ID isn't available, it means the MMS has been received
   1.512 +        // during the previous version that didn't take the DSDS scenario
   1.513 +        // into consideration. Tentatively, get connection from serviceId(0) by
   1.514 +        // default is better than nothing. Although it might use the wrong
   1.515 +        // SIM to download the desired MMS, eventually it would still fail to
   1.516 +        // download due to the wrong MMSC and proxy settings.
   1.517 +        return this.getConnByServiceId(0);
   1.518 +      }
   1.519 +
   1.520 +      let numCardAbsent = 0;
   1.521 +      let numRadioInterfaces = gRil.numRadioInterfaces;
   1.522 +      for (let clientId = 0; clientId < numRadioInterfaces; clientId++) {
   1.523 +        let mmsConnection = this.getConnByServiceId(clientId);
   1.524 +        let iccId = mmsConnection.getIccId();
   1.525 +        if (iccId === null) {
   1.526 +          numCardAbsent++;
   1.527 +          continue;
   1.528 +        }
   1.529 +
   1.530 +        if (iccId === aIccId) {
   1.531 +          return mmsConnection;
   1.532 +        }
   1.533 +      }
   1.534 +
   1.535 +      throw ((numCardAbsent === numRadioInterfaces)?
   1.536 +               _MMS_ERROR_NO_SIM_CARD: _MMS_ERROR_SIM_NOT_MATCHED);
   1.537 +    },
   1.538 +  };
   1.539 +});
   1.540 +
   1.541 +/**
   1.542 + * Implementation of nsIProtocolProxyFilter for MMS Proxy
   1.543 + */
   1.544 +function MmsProxyFilter(mmsConnection, url) {
   1.545 +  this.mmsConnection = mmsConnection;
   1.546 +  this.uri = Services.io.newURI(url, null, null);
   1.547 +}
   1.548 +MmsProxyFilter.prototype = {
   1.549 +
   1.550 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolProxyFilter]),
   1.551 +
   1.552 +  // nsIProtocolProxyFilter
   1.553 +
   1.554 +  applyFilter: function(proxyService, uri, proxyInfo) {
   1.555 +    if (!this.uri.equals(uri)) {
   1.556 +      if (DEBUG) debug("applyFilter: content uri = " + JSON.stringify(this.uri) +
   1.557 +                       " is not matched with uri = " + JSON.stringify(uri) + " .");
   1.558 +      return proxyInfo;
   1.559 +    }
   1.560 +
   1.561 +    // Fall-through, reutrn the MMS proxy info.
   1.562 +    let mmsProxyInfo = this.mmsConnection.proxyInfo;
   1.563 +
   1.564 +    if (DEBUG) {
   1.565 +      debug("applyFilter: MMSC/Content Location is matched with: " +
   1.566 +            JSON.stringify({ uri: JSON.stringify(this.uri),
   1.567 +                             mmsProxyInfo: mmsProxyInfo }));
   1.568 +    }
   1.569 +
   1.570 +    return mmsProxyInfo ? mmsProxyInfo : proxyInfo;
   1.571 +  }
   1.572 +};
   1.573 +
   1.574 +XPCOMUtils.defineLazyGetter(this, "gMmsTransactionHelper", function() {
   1.575 +  let helper = {
   1.576 +    /**
   1.577 +     * Send MMS request to MMSC.
   1.578 +     *
   1.579 +     * @param mmsConnection
   1.580 +     *        The MMS connection.
   1.581 +     * @param method
   1.582 +     *        "GET" or "POST".
   1.583 +     * @param url
   1.584 +     *        Target url string or null to be replaced by mmsc url.
   1.585 +     * @param istream
   1.586 +     *        An nsIInputStream instance as data source to be sent or null.
   1.587 +     * @param callback
   1.588 +     *        A callback function that takes two arguments: one for http
   1.589 +     *        status, the other for wrapped PDU data for further parsing.
   1.590 +     */
   1.591 +    sendRequest: function(mmsConnection, method, url, istream, callback) {
   1.592 +      // TODO: bug 810226 - Support GPRS bearer for MMS transmission and reception.
   1.593 +      let cancellable = {
   1.594 +        callback: callback,
   1.595 +
   1.596 +        isDone: false,
   1.597 +        isCancelled: false,
   1.598 +
   1.599 +        cancel: function() {
   1.600 +          if (this.isDone) {
   1.601 +            // It's too late to cancel.
   1.602 +            return;
   1.603 +          }
   1.604 +
   1.605 +          this.isCancelled = true;
   1.606 +          if (this.isAcquiringConn) {
   1.607 +            // We cannot cancel data connection setup here, so we invoke done()
   1.608 +            // here and handle |cancellable.isDone| in callback function of
   1.609 +            // |mmsConnection.acquire|.
   1.610 +            this.done(_HTTP_STATUS_USER_CANCELLED, null);
   1.611 +          } else if (this.xhr) {
   1.612 +            // Client has already sent the HTTP request. Try to abort it.
   1.613 +            this.xhr.abort();
   1.614 +          }
   1.615 +        },
   1.616 +
   1.617 +        done: function(httpStatus, data) {
   1.618 +          this.isDone = true;
   1.619 +          if (!this.callback) {
   1.620 +            return;
   1.621 +          }
   1.622 +
   1.623 +          if (this.isCancelled) {
   1.624 +            this.callback(_HTTP_STATUS_USER_CANCELLED, null);
   1.625 +          } else {
   1.626 +            this.callback(httpStatus, data);
   1.627 +          }
   1.628 +        }
   1.629 +      };
   1.630 +
   1.631 +      cancellable.isAcquiringConn =
   1.632 +        !mmsConnection.acquire((function(connected, errorCode) {
   1.633 +
   1.634 +        cancellable.isAcquiringConn = false;
   1.635 +
   1.636 +        if (!connected || cancellable.isCancelled) {
   1.637 +          mmsConnection.release();
   1.638 +
   1.639 +          if (!cancellable.isDone) {
   1.640 +            cancellable.done(cancellable.isCancelled ?
   1.641 +                             _HTTP_STATUS_USER_CANCELLED : errorCode, null);
   1.642 +          }
   1.643 +          return;
   1.644 +        }
   1.645 +
   1.646 +        // MMSC is available after an MMS connection is successfully acquired.
   1.647 +        if (!url) {
   1.648 +          url = mmsConnection.mmsc;
   1.649 +        }
   1.650 +
   1.651 +        if (DEBUG) debug("sendRequest: register proxy filter to " + url);
   1.652 +        let proxyFilter = new MmsProxyFilter(mmsConnection, url);
   1.653 +        gpps.registerFilter(proxyFilter, 0);
   1.654 +
   1.655 +        cancellable.xhr = this.sendHttpRequest(mmsConnection, method,
   1.656 +                                               url, istream, proxyFilter,
   1.657 +                                               cancellable.done.bind(cancellable));
   1.658 +      }).bind(this));
   1.659 +
   1.660 +      return cancellable;
   1.661 +    },
   1.662 +
   1.663 +    sendHttpRequest: function(mmsConnection, method, url, istream, proxyFilter,
   1.664 +                              callback) {
   1.665 +      let releaseMmsConnectionAndCallback = function(httpStatus, data) {
   1.666 +        gpps.unregisterFilter(proxyFilter);
   1.667 +        // Always release the MMS network connection before callback.
   1.668 +        mmsConnection.release();
   1.669 +        callback(httpStatus, data);
   1.670 +      };
   1.671 +
   1.672 +      try {
   1.673 +        let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
   1.674 +                  .createInstance(Ci.nsIXMLHttpRequest);
   1.675 +
   1.676 +        // Basic setups
   1.677 +        xhr.open(method, url, true);
   1.678 +        xhr.responseType = "arraybuffer";
   1.679 +        if (istream) {
   1.680 +          xhr.setRequestHeader("Content-Type",
   1.681 +                               "application/vnd.wap.mms-message");
   1.682 +          xhr.setRequestHeader("Content-Length", istream.available());
   1.683 +        }
   1.684 +
   1.685 +        // UAProf headers.
   1.686 +        let uaProfUrl, uaProfTagname = "x-wap-profile";
   1.687 +        try {
   1.688 +          uaProfUrl = Services.prefs.getCharPref('wap.UAProf.url');
   1.689 +          uaProfTagname = Services.prefs.getCharPref('wap.UAProf.tagname');
   1.690 +        } catch (e) {}
   1.691 +
   1.692 +        if (uaProfUrl) {
   1.693 +          xhr.setRequestHeader(uaProfTagname, uaProfUrl);
   1.694 +        }
   1.695 +
   1.696 +        // Setup event listeners
   1.697 +        xhr.onreadystatechange = function() {
   1.698 +          if (xhr.readyState != Ci.nsIXMLHttpRequest.DONE) {
   1.699 +            return;
   1.700 +          }
   1.701 +          let data = null;
   1.702 +          switch (xhr.status) {
   1.703 +            case HTTP_STATUS_OK: {
   1.704 +              if (DEBUG) debug("xhr success, response headers: "
   1.705 +                               + xhr.getAllResponseHeaders());
   1.706 +              let array = new Uint8Array(xhr.response);
   1.707 +              if (false) {
   1.708 +                for (let begin = 0; begin < array.length; begin += 20) {
   1.709 +                  let partial = array.subarray(begin, begin + 20);
   1.710 +                  if (DEBUG) debug("res: " + JSON.stringify(partial));
   1.711 +                }
   1.712 +              }
   1.713 +
   1.714 +              data = {array: array, offset: 0};
   1.715 +              break;
   1.716 +            }
   1.717 +
   1.718 +            default: {
   1.719 +              if (DEBUG) debug("xhr done, but status = " + xhr.status +
   1.720 +                               ", statusText = " + xhr.statusText);
   1.721 +              break;
   1.722 +            }
   1.723 +          }
   1.724 +          releaseMmsConnectionAndCallback(xhr.status, data);
   1.725 +        };
   1.726 +        // Send request
   1.727 +        xhr.send(istream);
   1.728 +        return xhr;
   1.729 +      } catch (e) {
   1.730 +        if (DEBUG) debug("xhr error, can't send: " + e.message);
   1.731 +        releaseMmsConnectionAndCallback(0, null);
   1.732 +        return null;
   1.733 +      }
   1.734 +    },
   1.735 +
   1.736 +    /**
   1.737 +     * Count number of recipients(to, cc, bcc fields).
   1.738 +     *
   1.739 +     * @param recipients
   1.740 +     *        The recipients in MMS message object.
   1.741 +     * @return the number of recipients
   1.742 +     * @see OMA-TS-MMS_CONF-V1_3-20110511-C section 10.2.5
   1.743 +     */
   1.744 +    countRecipients: function(recipients) {
   1.745 +      if (recipients && recipients.address) {
   1.746 +        return 1;
   1.747 +      }
   1.748 +      let totalRecipients = 0;
   1.749 +      if (!Array.isArray(recipients)) {
   1.750 +        return 0;
   1.751 +      }
   1.752 +      totalRecipients += recipients.length;
   1.753 +      for (let ix = 0; ix < recipients.length; ++ix) {
   1.754 +        if (recipients[ix].address.length > MMS.MMS_MAX_LENGTH_RECIPIENT) {
   1.755 +          throw new Error("MMS_MAX_LENGTH_RECIPIENT error");
   1.756 +        }
   1.757 +        if (recipients[ix].type === "email") {
   1.758 +          let found = recipients[ix].address.indexOf("<");
   1.759 +          let lenMailbox = recipients[ix].address.length - found;
   1.760 +          if(lenMailbox > MMS.MMS_MAX_LENGTH_MAILBOX_PORTION) {
   1.761 +            throw new Error("MMS_MAX_LENGTH_MAILBOX_PORTION error");
   1.762 +          }
   1.763 +        }
   1.764 +      }
   1.765 +      return totalRecipients;
   1.766 +    },
   1.767 +
   1.768 +    /**
   1.769 +     * Check maximum values of MMS parameters.
   1.770 +     *
   1.771 +     * @param msg
   1.772 +     *        The MMS message object.
   1.773 +     * @return true if the lengths are less than the maximum values of MMS
   1.774 +     *         parameters.
   1.775 +     * @see OMA-TS-MMS_CONF-V1_3-20110511-C section 10.2.5
   1.776 +     */
   1.777 +    checkMaxValuesParameters: function(msg) {
   1.778 +      let subject = msg.headers["subject"];
   1.779 +      if (subject && subject.length > MMS.MMS_MAX_LENGTH_SUBJECT) {
   1.780 +        return false;
   1.781 +      }
   1.782 +
   1.783 +      let totalRecipients = 0;
   1.784 +      try {
   1.785 +        totalRecipients += this.countRecipients(msg.headers["to"]);
   1.786 +        totalRecipients += this.countRecipients(msg.headers["cc"]);
   1.787 +        totalRecipients += this.countRecipients(msg.headers["bcc"]);
   1.788 +      } catch (ex) {
   1.789 +        if (DEBUG) debug("Exception caught : " + ex);
   1.790 +        return false;
   1.791 +      }
   1.792 +
   1.793 +      if (totalRecipients < 1 ||
   1.794 +          totalRecipients > MMS.MMS_MAX_TOTAL_RECIPIENTS) {
   1.795 +        return false;
   1.796 +      }
   1.797 +
   1.798 +      if (!Array.isArray(msg.parts)) {
   1.799 +        return true;
   1.800 +      }
   1.801 +      for (let i = 0; i < msg.parts.length; i++) {
   1.802 +        if (msg.parts[i].headers["content-type"] &&
   1.803 +          msg.parts[i].headers["content-type"].params) {
   1.804 +          let name = msg.parts[i].headers["content-type"].params["name"];
   1.805 +          if (name && name.length > MMS.MMS_MAX_LENGTH_NAME_CONTENT_TYPE) {
   1.806 +            return false;
   1.807 +          }
   1.808 +        }
   1.809 +      }
   1.810 +      return true;
   1.811 +    },
   1.812 +
   1.813 +    translateHttpStatusToMmsStatus: function(httpStatus, cancelledReason,
   1.814 +                                             defaultStatus) {
   1.815 +      switch(httpStatus) {
   1.816 +        case _HTTP_STATUS_USER_CANCELLED:
   1.817 +          return cancelledReason;
   1.818 +        case _HTTP_STATUS_RADIO_DISABLED:
   1.819 +          return _MMS_ERROR_RADIO_DISABLED;
   1.820 +        case _HTTP_STATUS_NO_SIM_CARD:
   1.821 +          return _MMS_ERROR_NO_SIM_CARD;
   1.822 +        case HTTP_STATUS_OK:
   1.823 +          return MMS.MMS_PDU_ERROR_OK;
   1.824 +        default:
   1.825 +          return defaultStatus;
   1.826 +      }
   1.827 +    }
   1.828 +  };
   1.829 +
   1.830 +  return helper;
   1.831 +});
   1.832 +
   1.833 +/**
   1.834 + * Send M-NotifyResp.ind back to MMSC.
   1.835 + *
   1.836 + * @param mmsConnection
   1.837 + *        The MMS connection.
   1.838 + * @param transactionId
   1.839 + *        X-Mms-Transaction-ID of the message.
   1.840 + * @param status
   1.841 + *        X-Mms-Status of the response.
   1.842 + * @param reportAllowed
   1.843 + *        X-Mms-Report-Allowed of the response.
   1.844 + *
   1.845 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.2
   1.846 + */
   1.847 +function NotifyResponseTransaction(mmsConnection, transactionId, status,
   1.848 +                                   reportAllowed) {
   1.849 +  this.mmsConnection = mmsConnection;
   1.850 +  let headers = {};
   1.851 +
   1.852 +  // Mandatory fields
   1.853 +  headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_NOTIFYRESP_IND;
   1.854 +  headers["x-mms-transaction-id"] = transactionId;
   1.855 +  headers["x-mms-mms-version"] = MMS.MMS_VERSION;
   1.856 +  headers["x-mms-status"] = status;
   1.857 +  // Optional fields
   1.858 +  headers["x-mms-report-allowed"] = reportAllowed;
   1.859 +
   1.860 +  this.istream = MMS.PduHelper.compose(null, {headers: headers});
   1.861 +}
   1.862 +NotifyResponseTransaction.prototype = {
   1.863 +  /**
   1.864 +   * @param callback [optional]
   1.865 +   *        A callback function that takes one argument -- the http status.
   1.866 +   */
   1.867 +  run: function(callback) {
   1.868 +    let requestCallback;
   1.869 +    if (callback) {
   1.870 +      requestCallback = function(httpStatus, data) {
   1.871 +        // `The MMS Client SHOULD ignore the associated HTTP POST response
   1.872 +        // from the MMS Proxy-Relay.` ~ OMA-TS-MMS_CTR-V1_3-20110913-A
   1.873 +        // section 8.2.2 "Notification".
   1.874 +        callback(httpStatus);
   1.875 +      };
   1.876 +    }
   1.877 +    gMmsTransactionHelper.sendRequest(this.mmsConnection,
   1.878 +                                      "POST",
   1.879 +                                      null,
   1.880 +                                      this.istream,
   1.881 +                                      requestCallback);
   1.882 +  }
   1.883 +};
   1.884 +
   1.885 +/**
   1.886 + * CancellableTransaction - base class inherited by [Send|Retrieve]Transaction.
   1.887 + * We can call |cancelRunning(reason)| to cancel the on-going transaction.
   1.888 + * @param cancellableId
   1.889 + *        An ID used to keep track of if an message is deleted from DB.
   1.890 + * @param serviceId
   1.891 + *        An ID used to keep track of if the primary SIM service is changed.
   1.892 + */
   1.893 +function CancellableTransaction(cancellableId, serviceId) {
   1.894 +  this.cancellableId = cancellableId;
   1.895 +  this.serviceId = serviceId;
   1.896 +  this.isCancelled = false;
   1.897 +}
   1.898 +CancellableTransaction.prototype = {
   1.899 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
   1.900 +
   1.901 +  // The timer for retrying sending or retrieving process.
   1.902 +  timer: null,
   1.903 +
   1.904 +  // Keep a reference to the callback when calling
   1.905 +  // |[Send|Retrieve]Transaction.run(callback)|.
   1.906 +  runCallback: null,
   1.907 +
   1.908 +  isObserversAdded: false,
   1.909 +
   1.910 +  cancelledReason: _MMS_ERROR_USER_CANCELLED_NO_REASON,
   1.911 +
   1.912 +  registerRunCallback: function(callback) {
   1.913 +    if (!this.isObserversAdded) {
   1.914 +      Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
   1.915 +      Services.obs.addObserver(this, kMobileMessageDeletedObserverTopic, false);
   1.916 +      Services.prefs.addObserver(kPrefRilRadioDisabled, this, false);
   1.917 +      Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
   1.918 +      this.isObserversAdded = true;
   1.919 +    }
   1.920 +
   1.921 +    this.runCallback = callback;
   1.922 +    this.isCancelled = false;
   1.923 +  },
   1.924 +
   1.925 +  removeObservers: function() {
   1.926 +    if (this.isObserversAdded) {
   1.927 +      Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
   1.928 +      Services.obs.removeObserver(this, kMobileMessageDeletedObserverTopic);
   1.929 +      Services.prefs.removeObserver(kPrefRilRadioDisabled, this);
   1.930 +      Services.prefs.removeObserver(kPrefDefaultServiceId, this);
   1.931 +      this.isObserversAdded = false;
   1.932 +    }
   1.933 +  },
   1.934 +
   1.935 +  runCallbackIfValid: function(mmsStatus, msg) {
   1.936 +    this.removeObservers();
   1.937 +
   1.938 +    if (this.runCallback) {
   1.939 +      this.runCallback(mmsStatus, msg);
   1.940 +      this.runCallback = null;
   1.941 +    }
   1.942 +  },
   1.943 +
   1.944 +  // Keep a reference to the cancellable when calling
   1.945 +  // |gMmsTransactionHelper.sendRequest(...)|.
   1.946 +  cancellable: null,
   1.947 +
   1.948 +  cancelRunning: function(reason) {
   1.949 +    this.isCancelled = true;
   1.950 +    this.cancelledReason = reason;
   1.951 +
   1.952 +    if (this.timer) {
   1.953 +      // The sending or retrieving process is waiting for the next retry.
   1.954 +      // What we only need to do is to cancel the timer.
   1.955 +      this.timer.cancel();
   1.956 +      this.timer = null;
   1.957 +      this.runCallbackIfValid(reason, null);
   1.958 +      return;
   1.959 +    }
   1.960 +
   1.961 +    if (this.cancellable) {
   1.962 +      // The sending or retrieving process is still running. We attempt to
   1.963 +      // abort the HTTP request.
   1.964 +      this.cancellable.cancel();
   1.965 +      this.cancellable = null;
   1.966 +    }
   1.967 +  },
   1.968 +
   1.969 +  // nsIObserver
   1.970 +
   1.971 +  observe: function(subject, topic, data) {
   1.972 +    switch (topic) {
   1.973 +      case NS_XPCOM_SHUTDOWN_OBSERVER_ID: {
   1.974 +        this.cancelRunning(_MMS_ERROR_SHUTDOWN);
   1.975 +        break;
   1.976 +      }
   1.977 +      case kMobileMessageDeletedObserverTopic: {
   1.978 +        data = JSON.parse(data);
   1.979 +        if (data.id != this.cancellableId) {
   1.980 +          return;
   1.981 +        }
   1.982 +
   1.983 +        this.cancelRunning(_MMS_ERROR_MESSAGE_DELETED);
   1.984 +        break;
   1.985 +      }
   1.986 +      case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: {
   1.987 +        if (data == kPrefRilRadioDisabled) {
   1.988 +          if (getRadioDisabledState()) {
   1.989 +            this.cancelRunning(_MMS_ERROR_RADIO_DISABLED);
   1.990 +          }
   1.991 +        } else if (data === kPrefDefaultServiceId &&
   1.992 +                   this.serviceId != getDefaultServiceId()) {
   1.993 +          this.cancelRunning(_MMS_ERROR_SIM_CARD_CHANGED);
   1.994 +        }
   1.995 +        break;
   1.996 +      }
   1.997 +    }
   1.998 +  }
   1.999 +};
  1.1000 +
  1.1001 +/**
  1.1002 + * Class for retrieving message from MMSC, which inherits CancellableTransaction.
  1.1003 + *
  1.1004 + * @param contentLocation
  1.1005 + *        X-Mms-Content-Location of the message.
  1.1006 + */
  1.1007 +function RetrieveTransaction(mmsConnection, cancellableId, contentLocation) {
  1.1008 +  this.mmsConnection = mmsConnection;
  1.1009 +
  1.1010 +  // Call |CancellableTransaction| constructor.
  1.1011 +  CancellableTransaction.call(this, cancellableId, mmsConnection.serviceId);
  1.1012 +
  1.1013 +  this.contentLocation = contentLocation;
  1.1014 +}
  1.1015 +RetrieveTransaction.prototype = Object.create(CancellableTransaction.prototype, {
  1.1016 +  /**
  1.1017 +   * @param callback [optional]
  1.1018 +   *        A callback function that takes two arguments: one for X-Mms-Status,
  1.1019 +   *        the other for the parsed M-Retrieve.conf message.
  1.1020 +   */
  1.1021 +  run: {
  1.1022 +    value: function(callback) {
  1.1023 +      this.registerRunCallback(callback);
  1.1024 +
  1.1025 +      this.retryCount = 0;
  1.1026 +      let retryCallback = (function(mmsStatus, msg) {
  1.1027 +        if (MMS.MMS_PDU_STATUS_DEFERRED == mmsStatus &&
  1.1028 +            this.retryCount < PREF_RETRIEVAL_RETRY_COUNT) {
  1.1029 +          let time = PREF_RETRIEVAL_RETRY_INTERVALS[this.retryCount];
  1.1030 +          if (DEBUG) debug("Fail to retrieve. Will retry after: " + time);
  1.1031 +
  1.1032 +          if (this.timer == null) {
  1.1033 +            this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1.1034 +          }
  1.1035 +
  1.1036 +          this.timer.initWithCallback(this.retrieve.bind(this, retryCallback),
  1.1037 +                                      time, Ci.nsITimer.TYPE_ONE_SHOT);
  1.1038 +          this.retryCount++;
  1.1039 +          return;
  1.1040 +        }
  1.1041 +        this.runCallbackIfValid(mmsStatus, msg);
  1.1042 +      }).bind(this);
  1.1043 +
  1.1044 +      this.retrieve(retryCallback);
  1.1045 +    },
  1.1046 +    enumerable: true,
  1.1047 +    configurable: true,
  1.1048 +    writable: true
  1.1049 +  },
  1.1050 +
  1.1051 +  /**
  1.1052 +   * @param callback
  1.1053 +   *        A callback function that takes two arguments: one for X-Mms-Status,
  1.1054 +   *        the other for the parsed M-Retrieve.conf message.
  1.1055 +   */
  1.1056 +  retrieve: {
  1.1057 +    value: function(callback) {
  1.1058 +      this.timer = null;
  1.1059 +
  1.1060 +      this.cancellable =
  1.1061 +        gMmsTransactionHelper.sendRequest(this.mmsConnection,
  1.1062 +                                          "GET", this.contentLocation, null,
  1.1063 +                                          (function(httpStatus, data) {
  1.1064 +        let mmsStatus = gMmsTransactionHelper
  1.1065 +                        .translateHttpStatusToMmsStatus(httpStatus,
  1.1066 +                                                        this.cancelledReason,
  1.1067 +                                                        MMS.MMS_PDU_STATUS_DEFERRED);
  1.1068 +        if (mmsStatus != MMS.MMS_PDU_ERROR_OK) {
  1.1069 +           callback(mmsStatus, null);
  1.1070 +           return;
  1.1071 +        }
  1.1072 +        if (!data) {
  1.1073 +          callback(MMS.MMS_PDU_STATUS_DEFERRED, null);
  1.1074 +          return;
  1.1075 +        }
  1.1076 +
  1.1077 +        let retrieved = MMS.PduHelper.parse(data, null);
  1.1078 +        if (!retrieved || (retrieved.type != MMS.MMS_PDU_TYPE_RETRIEVE_CONF)) {
  1.1079 +          callback(MMS.MMS_PDU_STATUS_UNRECOGNISED, null);
  1.1080 +          return;
  1.1081 +        }
  1.1082 +
  1.1083 +        // Fix default header field values.
  1.1084 +        if (retrieved.headers["x-mms-delivery-report"] == null) {
  1.1085 +          retrieved.headers["x-mms-delivery-report"] = false;
  1.1086 +        }
  1.1087 +
  1.1088 +        let retrieveStatus = retrieved.headers["x-mms-retrieve-status"];
  1.1089 +        if ((retrieveStatus != null) &&
  1.1090 +            (retrieveStatus != MMS.MMS_PDU_ERROR_OK)) {
  1.1091 +          callback(MMS.translatePduErrorToStatus(retrieveStatus), retrieved);
  1.1092 +          return;
  1.1093 +        }
  1.1094 +
  1.1095 +        callback(MMS.MMS_PDU_STATUS_RETRIEVED, retrieved);
  1.1096 +      }).bind(this));
  1.1097 +    },
  1.1098 +    enumerable: true,
  1.1099 +    configurable: true,
  1.1100 +    writable: true
  1.1101 +  }
  1.1102 +});
  1.1103 +
  1.1104 +/**
  1.1105 + * SendTransaction.
  1.1106 + *   Class for sending M-Send.req to MMSC, which inherits CancellableTransaction.
  1.1107 + *   @throws Error("Check max values parameters fail.")
  1.1108 + */
  1.1109 +function SendTransaction(mmsConnection, cancellableId, msg, requestDeliveryReport) {
  1.1110 +  this.mmsConnection = mmsConnection;
  1.1111 +
  1.1112 +  // Call |CancellableTransaction| constructor.
  1.1113 +  CancellableTransaction.call(this, cancellableId, mmsConnection.serviceId);
  1.1114 +
  1.1115 +  msg.headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_SEND_REQ;
  1.1116 +  if (!msg.headers["x-mms-transaction-id"]) {
  1.1117 +    // Create an unique transaction id
  1.1118 +    let tid = gUUIDGenerator.generateUUID().toString();
  1.1119 +    msg.headers["x-mms-transaction-id"] = tid;
  1.1120 +  }
  1.1121 +  msg.headers["x-mms-mms-version"] = MMS.MMS_VERSION;
  1.1122 +
  1.1123 +  // Let MMS Proxy Relay insert from address automatically for us
  1.1124 +  msg.headers["from"] = null;
  1.1125 +
  1.1126 +  msg.headers["date"] = new Date();
  1.1127 +  msg.headers["x-mms-message-class"] = "personal";
  1.1128 +  msg.headers["x-mms-expiry"] = 7 * 24 * 60 * 60;
  1.1129 +  msg.headers["x-mms-priority"] = 129;
  1.1130 +  try {
  1.1131 +    msg.headers["x-mms-read-report"] =
  1.1132 +      Services.prefs.getBoolPref("dom.mms.requestReadReport");
  1.1133 +  } catch (e) {
  1.1134 +    msg.headers["x-mms-read-report"] = true;
  1.1135 +  }
  1.1136 +  msg.headers["x-mms-delivery-report"] = requestDeliveryReport;
  1.1137 +
  1.1138 +  if (!gMmsTransactionHelper.checkMaxValuesParameters(msg)) {
  1.1139 +    //We should notify end user that the header format is wrong.
  1.1140 +    if (DEBUG) debug("Check max values parameters fail.");
  1.1141 +    throw new Error("Check max values parameters fail.");
  1.1142 +  }
  1.1143 +
  1.1144 +  if (msg.parts) {
  1.1145 +    let contentType = {
  1.1146 +      params: {
  1.1147 +        // `The type parameter must be specified and its value is the MIME
  1.1148 +        // media type of the "root" body part.` ~ RFC 2387 clause 3.1
  1.1149 +        type: msg.parts[0].headers["content-type"].media,
  1.1150 +      },
  1.1151 +    };
  1.1152 +
  1.1153 +    // `The Content-Type in M-Send.req and M-Retrieve.conf SHALL be
  1.1154 +    // application/vnd.wap.multipart.mixed when there is no presentation, and
  1.1155 +    // application/vnd.wap.multipart.related SHALL be used when there is SMIL
  1.1156 +    // presentation available.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1
  1.1157 +    if (contentType.params.type === "application/smil") {
  1.1158 +      contentType.media = "application/vnd.wap.multipart.related";
  1.1159 +
  1.1160 +      // `The start parameter, if given, is the content-ID of the compound
  1.1161 +      // object's "root".` ~ RFC 2387 clause 3.2
  1.1162 +      contentType.params.start = msg.parts[0].headers["content-id"];
  1.1163 +    } else {
  1.1164 +      contentType.media = "application/vnd.wap.multipart.mixed";
  1.1165 +    }
  1.1166 +
  1.1167 +    // Assign to Content-Type
  1.1168 +    msg.headers["content-type"] = contentType;
  1.1169 +  }
  1.1170 +
  1.1171 +  if (DEBUG) debug("msg: " + JSON.stringify(msg));
  1.1172 +
  1.1173 +  this.msg = msg;
  1.1174 +}
  1.1175 +SendTransaction.prototype = Object.create(CancellableTransaction.prototype, {
  1.1176 +  istreamComposed: {
  1.1177 +    value: false,
  1.1178 +    enumerable: true,
  1.1179 +    configurable: true,
  1.1180 +    writable: true
  1.1181 +  },
  1.1182 +
  1.1183 +  /**
  1.1184 +   * @param parts
  1.1185 +   *        'parts' property of a parsed MMS message.
  1.1186 +   * @param callback [optional]
  1.1187 +   *        A callback function that takes zero argument.
  1.1188 +   */
  1.1189 +  loadBlobs: {
  1.1190 +    value: function(parts, callback) {
  1.1191 +      let callbackIfValid = function callbackIfValid() {
  1.1192 +        if (DEBUG) debug("All parts loaded: " + JSON.stringify(parts));
  1.1193 +        if (callback) {
  1.1194 +          callback();
  1.1195 +        }
  1.1196 +      }
  1.1197 +
  1.1198 +      if (!parts || !parts.length) {
  1.1199 +        callbackIfValid();
  1.1200 +        return;
  1.1201 +      }
  1.1202 +
  1.1203 +      let numPartsToLoad = parts.length;
  1.1204 +      for each (let part in parts) {
  1.1205 +        if (!(part.content instanceof Ci.nsIDOMBlob)) {
  1.1206 +          numPartsToLoad--;
  1.1207 +          if (!numPartsToLoad) {
  1.1208 +            callbackIfValid();
  1.1209 +            return;
  1.1210 +          }
  1.1211 +          continue;
  1.1212 +        }
  1.1213 +        let fileReader = Cc["@mozilla.org/files/filereader;1"]
  1.1214 +                         .createInstance(Ci.nsIDOMFileReader);
  1.1215 +        fileReader.addEventListener("loadend",
  1.1216 +          (function onloadend(part, event) {
  1.1217 +          let arrayBuffer = event.target.result;
  1.1218 +          part.content = new Uint8Array(arrayBuffer);
  1.1219 +          numPartsToLoad--;
  1.1220 +          if (!numPartsToLoad) {
  1.1221 +            callbackIfValid();
  1.1222 +          }
  1.1223 +        }).bind(null, part));
  1.1224 +        fileReader.readAsArrayBuffer(part.content);
  1.1225 +      };
  1.1226 +    },
  1.1227 +    enumerable: true,
  1.1228 +    configurable: true,
  1.1229 +    writable: true
  1.1230 +  },
  1.1231 +
  1.1232 +  /**
  1.1233 +   * @param callback [optional]
  1.1234 +   *        A callback function that takes two arguments: one for
  1.1235 +   *        X-Mms-Response-Status, the other for the parsed M-Send.conf message.
  1.1236 +   */
  1.1237 +  run: {
  1.1238 +    value: function(callback) {
  1.1239 +      this.registerRunCallback(callback);
  1.1240 +
  1.1241 +      if (!this.istreamComposed) {
  1.1242 +        this.loadBlobs(this.msg.parts, (function() {
  1.1243 +          this.istream = MMS.PduHelper.compose(null, this.msg);
  1.1244 +          this.istreamSize = this.istream.available();
  1.1245 +          this.istreamComposed = true;
  1.1246 +          if (this.isCancelled) {
  1.1247 +            this.runCallbackIfValid(_MMS_ERROR_MESSAGE_DELETED, null);
  1.1248 +          } else {
  1.1249 +            this.run(callback);
  1.1250 +          }
  1.1251 +        }).bind(this));
  1.1252 +        return;
  1.1253 +      }
  1.1254 +
  1.1255 +      if (!this.istream) {
  1.1256 +        this.runCallbackIfValid(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
  1.1257 +        return;
  1.1258 +      }
  1.1259 +
  1.1260 +      this.retryCount = 0;
  1.1261 +      let retryCallback = (function(mmsStatus, msg) {
  1.1262 +        if ((MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE == mmsStatus ||
  1.1263 +              MMS.MMS_PDU_ERROR_PERMANENT_FAILURE == mmsStatus) &&
  1.1264 +            this.retryCount < PREF_SEND_RETRY_COUNT) {
  1.1265 +          if (DEBUG) {
  1.1266 +            debug("Fail to send. Will retry after: " + PREF_SEND_RETRY_INTERVAL);
  1.1267 +          }
  1.1268 +
  1.1269 +          if (this.timer == null) {
  1.1270 +            this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1.1271 +          }
  1.1272 +
  1.1273 +          this.retryCount++;
  1.1274 +
  1.1275 +          // the input stream may be read in the previous failure request so
  1.1276 +          // we have to re-compose it.
  1.1277 +          if (this.istreamSize == null ||
  1.1278 +              this.istreamSize != this.istream.available()) {
  1.1279 +            this.istream = MMS.PduHelper.compose(null, this.msg);
  1.1280 +          }
  1.1281 +
  1.1282 +          this.timer.initWithCallback(this.send.bind(this, retryCallback),
  1.1283 +                                      PREF_SEND_RETRY_INTERVAL,
  1.1284 +                                      Ci.nsITimer.TYPE_ONE_SHOT);
  1.1285 +          return;
  1.1286 +        }
  1.1287 +
  1.1288 +        this.runCallbackIfValid(mmsStatus, msg);
  1.1289 +      }).bind(this);
  1.1290 +
  1.1291 +      // This is the entry point to start sending.
  1.1292 +      this.send(retryCallback);
  1.1293 +    },
  1.1294 +    enumerable: true,
  1.1295 +    configurable: true,
  1.1296 +    writable: true
  1.1297 +  },
  1.1298 +
  1.1299 +  /**
  1.1300 +   * @param callback
  1.1301 +   *        A callback function that takes two arguments: one for
  1.1302 +   *        X-Mms-Response-Status, the other for the parsed M-Send.conf message.
  1.1303 +   */
  1.1304 +  send: {
  1.1305 +    value: function(callback) {
  1.1306 +      this.timer = null;
  1.1307 +
  1.1308 +      this.cancellable =
  1.1309 +        gMmsTransactionHelper.sendRequest(this.mmsConnection,
  1.1310 +                                          "POST",
  1.1311 +                                          null,
  1.1312 +                                          this.istream,
  1.1313 +                                          (function(httpStatus, data) {
  1.1314 +        let mmsStatus = gMmsTransactionHelper.
  1.1315 +                          translateHttpStatusToMmsStatus(
  1.1316 +                            httpStatus,
  1.1317 +                            this.cancelledReason,
  1.1318 +                            MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE);
  1.1319 +        if (httpStatus != HTTP_STATUS_OK) {
  1.1320 +          callback(mmsStatus, null);
  1.1321 +          return;
  1.1322 +        }
  1.1323 +
  1.1324 +        if (!data) {
  1.1325 +          callback(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
  1.1326 +          return;
  1.1327 +        }
  1.1328 +
  1.1329 +        let response = MMS.PduHelper.parse(data, null);
  1.1330 +        if (!response || (response.type != MMS.MMS_PDU_TYPE_SEND_CONF)) {
  1.1331 +          callback(MMS.MMS_PDU_RESPONSE_ERROR_UNSUPPORTED_MESSAGE, null);
  1.1332 +          return;
  1.1333 +        }
  1.1334 +
  1.1335 +        let responseStatus = response.headers["x-mms-response-status"];
  1.1336 +        callback(responseStatus, response);
  1.1337 +      }).bind(this));
  1.1338 +    },
  1.1339 +    enumerable: true,
  1.1340 +    configurable: true,
  1.1341 +    writable: true
  1.1342 +  }
  1.1343 +});
  1.1344 +
  1.1345 +/**
  1.1346 + * Send M-acknowledge.ind back to MMSC.
  1.1347 + *
  1.1348 + * @param mmsConnection
  1.1349 + *        The MMS connection.
  1.1350 + * @param transactionId
  1.1351 + *        X-Mms-Transaction-ID of the message.
  1.1352 + * @param reportAllowed
  1.1353 + *        X-Mms-Report-Allowed of the response.
  1.1354 + *
  1.1355 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.4
  1.1356 + */
  1.1357 +function AcknowledgeTransaction(mmsConnection, transactionId, reportAllowed) {
  1.1358 +  this.mmsConnection = mmsConnection;
  1.1359 +  let headers = {};
  1.1360 +
  1.1361 +  // Mandatory fields
  1.1362 +  headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_ACKNOWLEDGE_IND;
  1.1363 +  headers["x-mms-transaction-id"] = transactionId;
  1.1364 +  headers["x-mms-mms-version"] = MMS.MMS_VERSION;
  1.1365 +  // Optional fields
  1.1366 +  headers["x-mms-report-allowed"] = reportAllowed;
  1.1367 +
  1.1368 +  this.istream = MMS.PduHelper.compose(null, {headers: headers});
  1.1369 +}
  1.1370 +AcknowledgeTransaction.prototype = {
  1.1371 +  /**
  1.1372 +   * @param callback [optional]
  1.1373 +   *        A callback function that takes one argument -- the http status.
  1.1374 +   */
  1.1375 +  run: function(callback) {
  1.1376 +    let requestCallback;
  1.1377 +    if (callback) {
  1.1378 +      requestCallback = function(httpStatus, data) {
  1.1379 +        // `The MMS Client SHOULD ignore the associated HTTP POST response
  1.1380 +        // from the MMS Proxy-Relay.` ~ OMA-TS-MMS_CTR-V1_3-20110913-A
  1.1381 +        // section 8.2.3 "Retrieving an MM".
  1.1382 +        callback(httpStatus);
  1.1383 +      };
  1.1384 +    }
  1.1385 +    gMmsTransactionHelper.sendRequest(this.mmsConnection,
  1.1386 +                                      "POST",
  1.1387 +                                      null,
  1.1388 +                                      this.istream,
  1.1389 +                                      requestCallback);
  1.1390 +  }
  1.1391 +};
  1.1392 +
  1.1393 +/**
  1.1394 + * Return M-Read-Rec.ind back to MMSC
  1.1395 + *
  1.1396 + * @param messageID
  1.1397 + *        Message-ID of the message.
  1.1398 + * @param toAddress
  1.1399 + *        The address of the recipient of the Read Report, i.e. the originator
  1.1400 + *        of the original multimedia message.
  1.1401 + *
  1.1402 + * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.7.2
  1.1403 + */
  1.1404 +function ReadRecTransaction(mmsConnection, messageID, toAddress) {
  1.1405 +  this.mmsConnection = mmsConnection;
  1.1406 +  let headers = {};
  1.1407 +
  1.1408 +  // Mandatory fields
  1.1409 +  headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_READ_REC_IND;
  1.1410 +  headers["x-mms-mms-version"] = MMS.MMS_VERSION;
  1.1411 +  headers["message-id"] = messageID;
  1.1412 +  let type = MMS.Address.resolveType(toAddress);
  1.1413 +  let to = {address: toAddress,
  1.1414 +            type: type}
  1.1415 +  headers["to"] = to;
  1.1416 +  headers["from"] = null;
  1.1417 +  headers["x-mms-read-status"] = MMS.MMS_PDU_READ_STATUS_READ;
  1.1418 +
  1.1419 +  this.istream = MMS.PduHelper.compose(null, {headers: headers});
  1.1420 +  if (!this.istream) {
  1.1421 +    throw Cr.NS_ERROR_FAILURE;
  1.1422 +  }
  1.1423 +}
  1.1424 +ReadRecTransaction.prototype = {
  1.1425 +  run: function() {
  1.1426 +    gMmsTransactionHelper.sendRequest(this.mmsConnection,
  1.1427 +                                      "POST",
  1.1428 +                                      null,
  1.1429 +                                      this.istream,
  1.1430 +                                      null);
  1.1431 +  }
  1.1432 +};
  1.1433 +
  1.1434 +/**
  1.1435 + * MmsService
  1.1436 + */
  1.1437 +function MmsService() {
  1.1438 +  if (DEBUG) {
  1.1439 +    let macro = (MMS.MMS_VERSION >> 4) & 0x0f;
  1.1440 +    let minor = MMS.MMS_VERSION & 0x0f;
  1.1441 +    debug("Running protocol version: " + macro + "." + minor);
  1.1442 +  }
  1.1443 +
  1.1444 +  Services.prefs.addObserver(kPrefDefaultServiceId, this, false);
  1.1445 +  this.mmsDefaultServiceId = getDefaultServiceId();
  1.1446 +
  1.1447 +  // TODO: bug 810084 - support application identifier
  1.1448 +}
  1.1449 +MmsService.prototype = {
  1.1450 +
  1.1451 +  classID:   RIL_MMSSERVICE_CID,
  1.1452 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMmsService,
  1.1453 +                                         Ci.nsIWapPushApplication,
  1.1454 +                                         Ci.nsIObserver]),
  1.1455 +  /*
  1.1456 +   * Whether or not should we enable X-Mms-Report-Allowed in M-NotifyResp.ind
  1.1457 +   * and M-Acknowledge.ind PDU.
  1.1458 +   */
  1.1459 +  confSendDeliveryReport: CONFIG_SEND_REPORT_DEFAULT_YES,
  1.1460 +
  1.1461 +  /**
  1.1462 +   * Calculate Whether or not should we enable X-Mms-Report-Allowed.
  1.1463 +   *
  1.1464 +   * @param config
  1.1465 +   *        Current configure value.
  1.1466 +   * @param wish
  1.1467 +   *        Sender wish. Could be undefined, false, or true.
  1.1468 +   */
  1.1469 +  getReportAllowed: function(config, wish) {
  1.1470 +    if ((config == CONFIG_SEND_REPORT_DEFAULT_NO)
  1.1471 +        || (config == CONFIG_SEND_REPORT_DEFAULT_YES)) {
  1.1472 +      if (wish != null) {
  1.1473 +        config += (wish ? 1 : -1);
  1.1474 +      }
  1.1475 +    }
  1.1476 +    return config >= CONFIG_SEND_REPORT_DEFAULT_YES;
  1.1477 +  },
  1.1478 +
  1.1479 +  /**
  1.1480 +   * Convert intermediate message to indexedDB savable object.
  1.1481 +   *
  1.1482 +   * @param mmsConnection
  1.1483 +   *        The MMS connection.
  1.1484 +   * @param retrievalMode
  1.1485 +   *        Retrieval mode for MMS receiving setting.
  1.1486 +   * @param intermediate
  1.1487 +   *        Intermediate MMS message parsed from PDU.
  1.1488 +   */
  1.1489 +  convertIntermediateToSavable: function(mmsConnection, intermediate,
  1.1490 +                                         retrievalMode) {
  1.1491 +    intermediate.type = "mms";
  1.1492 +    intermediate.delivery = DELIVERY_NOT_DOWNLOADED;
  1.1493 +
  1.1494 +    let deliveryStatus;
  1.1495 +    switch (retrievalMode) {
  1.1496 +      case RETRIEVAL_MODE_MANUAL:
  1.1497 +        deliveryStatus = DELIVERY_STATUS_MANUAL;
  1.1498 +        break;
  1.1499 +      case RETRIEVAL_MODE_NEVER:
  1.1500 +        deliveryStatus = DELIVERY_STATUS_REJECTED;
  1.1501 +        break;
  1.1502 +      case RETRIEVAL_MODE_AUTOMATIC:
  1.1503 +        deliveryStatus = DELIVERY_STATUS_PENDING;
  1.1504 +        break;
  1.1505 +      case RETRIEVAL_MODE_AUTOMATIC_HOME:
  1.1506 +        if (mmsConnection.isVoiceRoaming()) {
  1.1507 +          deliveryStatus = DELIVERY_STATUS_MANUAL;
  1.1508 +        } else {
  1.1509 +          deliveryStatus = DELIVERY_STATUS_PENDING;
  1.1510 +        }
  1.1511 +        break;
  1.1512 +      default:
  1.1513 +        deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
  1.1514 +        break;
  1.1515 +    }
  1.1516 +    // |intermediate.deliveryStatus| will be deleted after being stored in db.
  1.1517 +    intermediate.deliveryStatus = deliveryStatus;
  1.1518 +
  1.1519 +    intermediate.timestamp = Date.now();
  1.1520 +    intermediate.receivers = [];
  1.1521 +    intermediate.phoneNumber = mmsConnection.getPhoneNumber();
  1.1522 +    intermediate.iccId = mmsConnection.getIccId();
  1.1523 +    return intermediate;
  1.1524 +  },
  1.1525 +
  1.1526 +  /**
  1.1527 +   * Merge the retrieval confirmation into the savable message.
  1.1528 +   *
  1.1529 +   * @param mmsConnection
  1.1530 +   *        The MMS connection.
  1.1531 +   * @param intermediate
  1.1532 +   *        Intermediate MMS message parsed from PDU, which carries
  1.1533 +   *        the retrieval confirmation.
  1.1534 +   * @param savable
  1.1535 +   *        The indexedDB savable MMS message, which is going to be
  1.1536 +   *        merged with the extra retrieval confirmation.
  1.1537 +   */
  1.1538 +  mergeRetrievalConfirmation: function(mmsConnection, intermediate, savable) {
  1.1539 +    // Prepare timestamp/sentTimestamp.
  1.1540 +    savable.timestamp = Date.now();
  1.1541 +    savable.sentTimestamp = intermediate.headers["date"].getTime();
  1.1542 +
  1.1543 +    savable.receivers = [];
  1.1544 +    // We don't have Bcc in recevied MMS message.
  1.1545 +    for each (let type in ["cc", "to"]) {
  1.1546 +      if (intermediate.headers[type]) {
  1.1547 +        if (intermediate.headers[type] instanceof Array) {
  1.1548 +          for (let index in intermediate.headers[type]) {
  1.1549 +            savable.receivers.push(intermediate.headers[type][index].address);
  1.1550 +          }
  1.1551 +        } else {
  1.1552 +          savable.receivers.push(intermediate.headers[type].address);
  1.1553 +        }
  1.1554 +      }
  1.1555 +    }
  1.1556 +
  1.1557 +    savable.delivery = DELIVERY_RECEIVED;
  1.1558 +    // |savable.deliveryStatus| will be deleted after being stored in db.
  1.1559 +    savable.deliveryStatus = DELIVERY_STATUS_SUCCESS;
  1.1560 +    for (let field in intermediate.headers) {
  1.1561 +      savable.headers[field] = intermediate.headers[field];
  1.1562 +    }
  1.1563 +    if (intermediate.parts) {
  1.1564 +      savable.parts = intermediate.parts;
  1.1565 +    }
  1.1566 +    if (intermediate.content) {
  1.1567 +      savable.content = intermediate.content;
  1.1568 +    }
  1.1569 +    return savable;
  1.1570 +  },
  1.1571 +
  1.1572 +  /**
  1.1573 +   * @param aMmsConnection
  1.1574 +   *        The MMS connection.
  1.1575 +   * @param aContentLocation
  1.1576 +   *        X-Mms-Content-Location of the message.
  1.1577 +   * @param aCallback [optional]
  1.1578 +   *        A callback function that takes two arguments: one for X-Mms-Status,
  1.1579 +   *        the other parsed MMS message.
  1.1580 +   * @param aDomMessage
  1.1581 +   *        The nsIDOMMozMmsMessage object.
  1.1582 +   */
  1.1583 +  retrieveMessage: function(aMmsConnection, aContentLocation, aCallback,
  1.1584 +                            aDomMessage) {
  1.1585 +    // Notifying observers an MMS message is retrieving.
  1.1586 +    Services.obs.notifyObservers(aDomMessage, kSmsRetrievingObserverTopic, null);
  1.1587 +
  1.1588 +    let transaction = new RetrieveTransaction(aMmsConnection,
  1.1589 +                                              aDomMessage.id,
  1.1590 +                                              aContentLocation);
  1.1591 +    transaction.run(aCallback);
  1.1592 +  },
  1.1593 +
  1.1594 +  /**
  1.1595 +   * A helper to broadcast the system message to launch registered apps
  1.1596 +   * like Costcontrol, Notification and Message app... etc.
  1.1597 +   *
  1.1598 +   * @param aName
  1.1599 +   *        The system message name.
  1.1600 +   * @param aDomMessage
  1.1601 +   *        The nsIDOMMozMmsMessage object.
  1.1602 +   */
  1.1603 +  broadcastMmsSystemMessage: function(aName, aDomMessage) {
  1.1604 +    if (DEBUG) debug("Broadcasting the MMS system message: " + aName);
  1.1605 +
  1.1606 +    // Sadly we cannot directly broadcast the aDomMessage object
  1.1607 +    // because the system message mechamism will rewrap the object
  1.1608 +    // based on the content window, which needs to know the properties.
  1.1609 +    gSystemMessenger.broadcastMessage(aName, {
  1.1610 +      iccId:               aDomMessage.iccId,
  1.1611 +      type:                aDomMessage.type,
  1.1612 +      id:                  aDomMessage.id,
  1.1613 +      threadId:            aDomMessage.threadId,
  1.1614 +      delivery:            aDomMessage.delivery,
  1.1615 +      deliveryInfo:        aDomMessage.deliveryInfo,
  1.1616 +      sender:              aDomMessage.sender,
  1.1617 +      receivers:           aDomMessage.receivers,
  1.1618 +      timestamp:           aDomMessage.timestamp,
  1.1619 +      sentTimestamp:       aDomMessage.sentTimestamp,
  1.1620 +      read:                aDomMessage.read,
  1.1621 +      subject:             aDomMessage.subject,
  1.1622 +      smil:                aDomMessage.smil,
  1.1623 +      attachments:         aDomMessage.attachments,
  1.1624 +      expiryDate:          aDomMessage.expiryDate,
  1.1625 +      readReportRequested: aDomMessage.readReportRequested
  1.1626 +    });
  1.1627 +  },
  1.1628 +
  1.1629 +  /**
  1.1630 +   * A helper function to broadcast system message and notify observers that
  1.1631 +   * an MMS is sent.
  1.1632 +   *
  1.1633 +   * @params aDomMessage
  1.1634 +   *         The nsIDOMMozMmsMessage object.
  1.1635 +   */
  1.1636 +  broadcastSentMessageEvent: function(aDomMessage) {
  1.1637 +    // Broadcasting a 'sms-sent' system message to open apps.
  1.1638 +    this.broadcastMmsSystemMessage(kSmsSentObserverTopic, aDomMessage);
  1.1639 +
  1.1640 +    // Notifying observers an MMS message is sent.
  1.1641 +    Services.obs.notifyObservers(aDomMessage, kSmsSentObserverTopic, null);
  1.1642 +  },
  1.1643 +
  1.1644 +  /**
  1.1645 +   * A helper function to broadcast system message and notify observers that
  1.1646 +   * an MMS is received.
  1.1647 +   *
  1.1648 +   * @params aDomMessage
  1.1649 +   *         The nsIDOMMozMmsMessage object.
  1.1650 +   */
  1.1651 +  broadcastReceivedMessageEvent :function broadcastReceivedMessageEvent(aDomMessage) {
  1.1652 +    // Broadcasting a 'sms-received' system message to open apps.
  1.1653 +    this.broadcastMmsSystemMessage(kSmsReceivedObserverTopic, aDomMessage);
  1.1654 +
  1.1655 +    // Notifying observers an MMS message is received.
  1.1656 +    Services.obs.notifyObservers(aDomMessage, kSmsReceivedObserverTopic, null);
  1.1657 +  },
  1.1658 +
  1.1659 +  /**
  1.1660 +   * Callback for retrieveMessage.
  1.1661 +   */
  1.1662 +  retrieveMessageCallback: function(mmsConnection, wish, savableMessage,
  1.1663 +                                    mmsStatus, retrievedMessage) {
  1.1664 +    if (DEBUG) debug("retrievedMessage = " + JSON.stringify(retrievedMessage));
  1.1665 +
  1.1666 +    let transactionId = savableMessage.headers["x-mms-transaction-id"];
  1.1667 +
  1.1668 +    // The absence of the field does not indicate any default
  1.1669 +    // value. So we go check the same field in the retrieved
  1.1670 +    // message instead.
  1.1671 +    if (wish == null && retrievedMessage) {
  1.1672 +      wish = retrievedMessage.headers["x-mms-delivery-report"];
  1.1673 +    }
  1.1674 +
  1.1675 +    let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
  1.1676 +                                              wish);
  1.1677 +    // If the mmsStatus isn't MMS_PDU_STATUS_RETRIEVED after retrieving,
  1.1678 +    // something must be wrong with MMSC, so stop updating the DB record.
  1.1679 +    // We could send a message to content to notify the user the MMS
  1.1680 +    // retrieving failed. The end user has to retrieve the MMS again.
  1.1681 +    if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) {
  1.1682 +      if (mmsStatus != _MMS_ERROR_RADIO_DISABLED &&
  1.1683 +          mmsStatus != _MMS_ERROR_NO_SIM_CARD &&
  1.1684 +          mmsStatus != _MMS_ERROR_SIM_CARD_CHANGED) {
  1.1685 +        let transaction = new NotifyResponseTransaction(mmsConnection,
  1.1686 +                                                        transactionId,
  1.1687 +                                                        mmsStatus,
  1.1688 +                                                        reportAllowed);
  1.1689 +        transaction.run();
  1.1690 +      }
  1.1691 +      // Retrieved fail after retry, so we update the delivery status in DB and
  1.1692 +      // notify this domMessage that error happen.
  1.1693 +      gMobileMessageDatabaseService
  1.1694 +        .setMessageDeliveryByMessageId(savableMessage.id,
  1.1695 +                                       null,
  1.1696 +                                       null,
  1.1697 +                                       DELIVERY_STATUS_ERROR,
  1.1698 +                                       null,
  1.1699 +                                       (function(rv, domMessage) {
  1.1700 +        this.broadcastReceivedMessageEvent(domMessage);
  1.1701 +      }).bind(this));
  1.1702 +      return;
  1.1703 +    }
  1.1704 +
  1.1705 +    savableMessage = this.mergeRetrievalConfirmation(mmsConnection,
  1.1706 +                                                     retrievedMessage,
  1.1707 +                                                     savableMessage);
  1.1708 +    gMobileMessageDatabaseService.saveReceivedMessage(savableMessage,
  1.1709 +        (function(rv, domMessage) {
  1.1710 +      let success = Components.isSuccessCode(rv);
  1.1711 +
  1.1712 +      // Cite 6.2.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A:
  1.1713 +      // The M-NotifyResp.ind response PDU SHALL provide a message retrieval
  1.1714 +      // status code. The status ‘retrieved’ SHALL be used only if the MMS
  1.1715 +      // Client has successfully retrieved the MM prior to sending the
  1.1716 +      // NotifyResp.ind response PDU.
  1.1717 +      let transaction =
  1.1718 +        new NotifyResponseTransaction(mmsConnection,
  1.1719 +                                      transactionId,
  1.1720 +                                      success ? MMS.MMS_PDU_STATUS_RETRIEVED
  1.1721 +                                              : MMS.MMS_PDU_STATUS_DEFERRED,
  1.1722 +                                      reportAllowed);
  1.1723 +      transaction.run();
  1.1724 +
  1.1725 +      if (!success) {
  1.1726 +        // At this point we could send a message to content to notify the user
  1.1727 +        // that storing an incoming MMS failed, most likely due to a full disk.
  1.1728 +        // The end user has to retrieve the MMS again.
  1.1729 +        if (DEBUG) debug("Could not store MMS , error code " + rv);
  1.1730 +        return;
  1.1731 +      }
  1.1732 +
  1.1733 +      this.broadcastReceivedMessageEvent(domMessage);
  1.1734 +    }).bind(this));
  1.1735 +  },
  1.1736 +
  1.1737 +  /**
  1.1738 +   * Callback for saveReceivedMessage.
  1.1739 +   */
  1.1740 +  saveReceivedMessageCallback: function(mmsConnection, retrievalMode,
  1.1741 +                                        savableMessage, rv, domMessage) {
  1.1742 +    let success = Components.isSuccessCode(rv);
  1.1743 +    if (!success) {
  1.1744 +      // At this point we could send a message to content to notify the
  1.1745 +      // user that storing an incoming MMS notification indication failed,
  1.1746 +      // ost likely due to a full disk.
  1.1747 +      if (DEBUG) debug("Could not store MMS " + JSON.stringify(savableMessage) +
  1.1748 +            ", error code " + rv);
  1.1749 +      // Because MMSC will resend the notification indication once we don't
  1.1750 +      // response the notification. Hope the end user will clean some space
  1.1751 +      // for the resent notification indication.
  1.1752 +      return;
  1.1753 +    }
  1.1754 +
  1.1755 +    // For X-Mms-Report-Allowed and X-Mms-Transaction-Id
  1.1756 +    let wish = savableMessage.headers["x-mms-delivery-report"];
  1.1757 +    let transactionId = savableMessage.headers["x-mms-transaction-id"];
  1.1758 +
  1.1759 +    this.broadcastReceivedMessageEvent(domMessage);
  1.1760 +
  1.1761 +    // To avoid costing money, we only send notify response when it's under
  1.1762 +    // the "automatic" retrieval mode or it's not in the roaming environment.
  1.1763 +    if (retrievalMode !== RETRIEVAL_MODE_AUTOMATIC &&
  1.1764 +        mmsConnection.isVoiceRoaming()) {
  1.1765 +      return;
  1.1766 +    }
  1.1767 +
  1.1768 +    if (RETRIEVAL_MODE_MANUAL === retrievalMode ||
  1.1769 +        RETRIEVAL_MODE_NEVER === retrievalMode) {
  1.1770 +      let mmsStatus = RETRIEVAL_MODE_NEVER === retrievalMode
  1.1771 +                    ? MMS.MMS_PDU_STATUS_REJECTED
  1.1772 +                    : MMS.MMS_PDU_STATUS_DEFERRED;
  1.1773 +
  1.1774 +      // For X-Mms-Report-Allowed
  1.1775 +      let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
  1.1776 +                                                wish);
  1.1777 +
  1.1778 +      let transaction = new NotifyResponseTransaction(mmsConnection,
  1.1779 +                                                      transactionId,
  1.1780 +                                                      mmsStatus,
  1.1781 +                                                      reportAllowed);
  1.1782 +      transaction.run();
  1.1783 +      return;
  1.1784 +    }
  1.1785 +
  1.1786 +    let url = savableMessage.headers["x-mms-content-location"].uri;
  1.1787 +
  1.1788 +    // For RETRIEVAL_MODE_AUTOMATIC or RETRIEVAL_MODE_AUTOMATIC_HOME but not
  1.1789 +    // roaming, proceed to retrieve MMS.
  1.1790 +    this.retrieveMessage(mmsConnection,
  1.1791 +                         url,
  1.1792 +                         this.retrieveMessageCallback.bind(this,
  1.1793 +                                                           mmsConnection,
  1.1794 +                                                           wish,
  1.1795 +                                                           savableMessage),
  1.1796 +                         domMessage);
  1.1797 +  },
  1.1798 +
  1.1799 +  /**
  1.1800 +   * Handle incoming M-Notification.ind PDU.
  1.1801 +   *
  1.1802 +   * @param serviceId
  1.1803 +   *        The ID of the service for receiving the PDU data.
  1.1804 +   * @param notification
  1.1805 +   *        The parsed MMS message object.
  1.1806 +   */
  1.1807 +  handleNotificationIndication: function(serviceId, notification) {
  1.1808 +    let transactionId = notification.headers["x-mms-transaction-id"];
  1.1809 +    gMobileMessageDatabaseService.getMessageRecordByTransactionId(transactionId,
  1.1810 +        (function(aRv, aMessageRecord) {
  1.1811 +      if (Components.isSuccessCode(aRv) && aMessageRecord) {
  1.1812 +        if (DEBUG) debug("We already got the NotificationIndication with transactionId = "
  1.1813 +                         + transactionId + " before.");
  1.1814 +        return;
  1.1815 +      }
  1.1816 +
  1.1817 +      let retrievalMode = RETRIEVAL_MODE_MANUAL;
  1.1818 +      try {
  1.1819 +        retrievalMode = Services.prefs.getCharPref(kPrefRetrievalMode);
  1.1820 +      } catch (e) {}
  1.1821 +
  1.1822 +      // Under the "automatic"/"automatic-home" retrieval mode, we switch to
  1.1823 +      // the "manual" retrieval mode to download MMS for non-active SIM.
  1.1824 +      if ((retrievalMode == RETRIEVAL_MODE_AUTOMATIC ||
  1.1825 +           retrievalMode == RETRIEVAL_MODE_AUTOMATIC_HOME) &&
  1.1826 +          serviceId != this.mmsDefaultServiceId) {
  1.1827 +        if (DEBUG) {
  1.1828 +          debug("Switch to 'manual' mode to download MMS for non-active SIM: " +
  1.1829 +                "serviceId = " + serviceId + " doesn't equal to " +
  1.1830 +                "mmsDefaultServiceId = " + this.mmsDefaultServiceId);
  1.1831 +        }
  1.1832 +
  1.1833 +        retrievalMode = RETRIEVAL_MODE_MANUAL;
  1.1834 +      }
  1.1835 +
  1.1836 +      let mmsConnection = gMmsConnections.getConnByServiceId(serviceId);
  1.1837 +
  1.1838 +      let savableMessage = this.convertIntermediateToSavable(mmsConnection,
  1.1839 +                                                             notification,
  1.1840 +                                                             retrievalMode);
  1.1841 +
  1.1842 +      gMobileMessageDatabaseService
  1.1843 +        .saveReceivedMessage(savableMessage,
  1.1844 +                             this.saveReceivedMessageCallback
  1.1845 +                                 .bind(this,
  1.1846 +                                       mmsConnection,
  1.1847 +                                       retrievalMode,
  1.1848 +                                       savableMessage));
  1.1849 +    }).bind(this));
  1.1850 +  },
  1.1851 +
  1.1852 +  /**
  1.1853 +   * Handle incoming M-Delivery.ind PDU.
  1.1854 +   *
  1.1855 +   * @param aMsg
  1.1856 +   *        The MMS message object.
  1.1857 +   */
  1.1858 +  handleDeliveryIndication: function(aMsg) {
  1.1859 +    let headers = aMsg.headers;
  1.1860 +    let envelopeId = headers["message-id"];
  1.1861 +    let address = headers.to.address;
  1.1862 +    let mmsStatus = headers["x-mms-status"];
  1.1863 +    if (DEBUG) {
  1.1864 +      debug("Start updating the delivery status for envelopeId: " + envelopeId +
  1.1865 +            " address: " + address + " mmsStatus: " + mmsStatus);
  1.1866 +    }
  1.1867 +
  1.1868 +    // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.3 "X-Mms-Status",
  1.1869 +    // in the M-Delivery.ind the X-Mms-Status could be MMS.MMS_PDU_STATUS_{
  1.1870 +    // EXPIRED, RETRIEVED, REJECTED, DEFERRED, UNRECOGNISED, INDETERMINATE,
  1.1871 +    // FORWARDED, UNREACHABLE }.
  1.1872 +    let deliveryStatus;
  1.1873 +    switch (mmsStatus) {
  1.1874 +      case MMS.MMS_PDU_STATUS_RETRIEVED:
  1.1875 +        deliveryStatus = DELIVERY_STATUS_SUCCESS;
  1.1876 +        break;
  1.1877 +      case MMS.MMS_PDU_STATUS_EXPIRED:
  1.1878 +      case MMS.MMS_PDU_STATUS_REJECTED:
  1.1879 +      case MMS.MMS_PDU_STATUS_UNRECOGNISED:
  1.1880 +      case MMS.MMS_PDU_STATUS_UNREACHABLE:
  1.1881 +        deliveryStatus = DELIVERY_STATUS_REJECTED;
  1.1882 +        break;
  1.1883 +      case MMS.MMS_PDU_STATUS_DEFERRED:
  1.1884 +        deliveryStatus = DELIVERY_STATUS_PENDING;
  1.1885 +        break;
  1.1886 +      case MMS.MMS_PDU_STATUS_INDETERMINATE:
  1.1887 +        deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
  1.1888 +        break;
  1.1889 +      default:
  1.1890 +        if (DEBUG) debug("Cannot handle this MMS status. Returning.");
  1.1891 +        return;
  1.1892 +    }
  1.1893 +
  1.1894 +    if (DEBUG) debug("Updating the delivery status to: " + deliveryStatus);
  1.1895 +    gMobileMessageDatabaseService
  1.1896 +      .setMessageDeliveryStatusByEnvelopeId(envelopeId, address, deliveryStatus,
  1.1897 +                                            (function(aRv, aDomMessage) {
  1.1898 +      if (DEBUG) debug("Marking the delivery status is done.");
  1.1899 +      // TODO bug 832140 handle !Components.isSuccessCode(aRv)
  1.1900 +
  1.1901 +      let topic;
  1.1902 +      if (mmsStatus === MMS.MMS_PDU_STATUS_RETRIEVED) {
  1.1903 +        topic = kSmsDeliverySuccessObserverTopic;
  1.1904 +
  1.1905 +        // Broadcasting a 'sms-delivery-success' system message to open apps.
  1.1906 +        this.broadcastMmsSystemMessage(topic, aDomMessage);
  1.1907 +      } else if (mmsStatus === MMS.MMS_PDU_STATUS_REJECTED) {
  1.1908 +        topic = kSmsDeliveryErrorObserverTopic;
  1.1909 +      } else {
  1.1910 +        if (DEBUG) debug("Needn't fire event for this MMS status. Returning.");
  1.1911 +        return;
  1.1912 +      }
  1.1913 +
  1.1914 +      // Notifying observers the delivery status is updated.
  1.1915 +      Services.obs.notifyObservers(aDomMessage, topic, null);
  1.1916 +    }).bind(this));
  1.1917 +  },
  1.1918 +
  1.1919 +  /**
  1.1920 +   * Handle incoming M-Read-Orig.ind PDU.
  1.1921 +   *
  1.1922 +   * @param aIndication
  1.1923 +   *        The MMS message object.
  1.1924 +   */
  1.1925 +  handleReadOriginateIndication: function(aIndication) {
  1.1926 +
  1.1927 +    let headers = aIndication.headers;
  1.1928 +    let envelopeId = headers["message-id"];
  1.1929 +    let address = headers.from.address;
  1.1930 +    let mmsReadStatus = headers["x-mms-read-status"];
  1.1931 +    if (DEBUG) {
  1.1932 +      debug("Start updating the read status for envelopeId: " + envelopeId +
  1.1933 +            ", address: " + address + ", mmsReadStatus: " + mmsReadStatus);
  1.1934 +    }
  1.1935 +
  1.1936 +    // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.4 "X-Mms-Read-Status",
  1.1937 +    // in M-Read-Rec-Orig.ind the X-Mms-Read-Status could be
  1.1938 +    // MMS.MMS_READ_STATUS_{ READ, DELETED_WITHOUT_BEING_READ }.
  1.1939 +    let readStatus = mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ
  1.1940 +                   ? MMS.DOM_READ_STATUS_SUCCESS
  1.1941 +                   : MMS.DOM_READ_STATUS_ERROR;
  1.1942 +    if (DEBUG) debug("Updating the read status to: " + readStatus);
  1.1943 +
  1.1944 +    gMobileMessageDatabaseService
  1.1945 +      .setMessageReadStatusByEnvelopeId(envelopeId, address, readStatus,
  1.1946 +                                        (function(aRv, aDomMessage) {
  1.1947 +      if (!Components.isSuccessCode(aRv)) {
  1.1948 +        // Notifying observers the read status is error.
  1.1949 +        Services.obs.notifyObservers(aDomMessage, kSmsReadSuccessObserverTopic, null);
  1.1950 +        return;
  1.1951 +      }
  1.1952 +
  1.1953 +      if (DEBUG) debug("Marking the read status is done.");
  1.1954 +      let topic;
  1.1955 +      if (mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ) {
  1.1956 +        topic = kSmsReadSuccessObserverTopic;
  1.1957 +
  1.1958 +        // Broadcasting a 'sms-read-success' system message to open apps.
  1.1959 +        this.broadcastMmsSystemMessage(topic, aDomMessage);
  1.1960 +      } else {
  1.1961 +        topic = kSmsReadErrorObserverTopic;
  1.1962 +      }
  1.1963 +
  1.1964 +      // Notifying observers the read status is updated.
  1.1965 +      Services.obs.notifyObservers(aDomMessage, topic, null);
  1.1966 +    }).bind(this));
  1.1967 +  },
  1.1968 +
  1.1969 +  /**
  1.1970 +   * A utility function to convert the MmsParameters dictionary object
  1.1971 +   * to a database-savable message.
  1.1972 +   *
  1.1973 +   * @param aMmsConnection
  1.1974 +   *        The MMS connection.
  1.1975 +   * @param aParams
  1.1976 +   *        The MmsParameters dictionay object.
  1.1977 +   * @param aMessage (output)
  1.1978 +   *        The database-savable message.
  1.1979 +   * Return the error code by veryfying if the |aParams| is valid or not.
  1.1980 +   *
  1.1981 +   * Notes:
  1.1982 +   *
  1.1983 +   * OMA-TS-MMS-CONF-V1_3-20110913-A section 10.2.2 "Message Content Encoding":
  1.1984 +   *
  1.1985 +   * A name for multipart object SHALL be encoded using name-parameter for Content-Type
  1.1986 +   * header in WSP multipart headers. In decoding, name-parameter of Content-Type SHALL
  1.1987 +   * be used if available. If name-parameter of Content-Type is not available, filename
  1.1988 +   * parameter of Content-Disposition header SHALL be used if available. If neither
  1.1989 +   * name-parameter of Content-Type header nor filename parameter of Content-Disposition
  1.1990 +   * header is available, Content-Location header SHALL be used if available.
  1.1991 +   */
  1.1992 +  createSavableFromParams: function(aMmsConnection, aParams, aMessage) {
  1.1993 +    if (DEBUG) debug("createSavableFromParams: aParams: " + JSON.stringify(aParams));
  1.1994 +
  1.1995 +    let isAddrValid = true;
  1.1996 +    let smil = aParams.smil;
  1.1997 +
  1.1998 +    // |aMessage.headers|
  1.1999 +    let headers = aMessage["headers"] = {};
  1.2000 +    let receivers = aParams.receivers;
  1.2001 +    if (receivers.length != 0) {
  1.2002 +      let headersTo = headers["to"] = [];
  1.2003 +      for (let i = 0; i < receivers.length; i++) {
  1.2004 +        let receiver = receivers[i];
  1.2005 +        let type = MMS.Address.resolveType(receiver);
  1.2006 +        let address;
  1.2007 +        if (type == "PLMN") {
  1.2008 +          address = PhoneNumberUtils.normalize(receiver, false);
  1.2009 +          if (!PhoneNumberUtils.isPlainPhoneNumber(address)) {
  1.2010 +            isAddrValid = false;
  1.2011 +          }
  1.2012 +          if (DEBUG) debug("createSavableFromParams: normalize phone number " +
  1.2013 +                           "from " + receiver + " to " + address);
  1.2014 +        } else {
  1.2015 +          address = receiver;
  1.2016 +          isAddrValid = false;
  1.2017 +          if (DEBUG) debug("Error! Address is invalid to send MMS: " + address);
  1.2018 +        }
  1.2019 +        headersTo.push({"address": address, "type": type});
  1.2020 +      }
  1.2021 +    }
  1.2022 +    if (aParams.subject) {
  1.2023 +      headers["subject"] = aParams.subject;
  1.2024 +    }
  1.2025 +
  1.2026 +    // |aMessage.parts|
  1.2027 +    let attachments = aParams.attachments;
  1.2028 +    if (attachments.length != 0 || smil) {
  1.2029 +      let parts = aMessage["parts"] = [];
  1.2030 +
  1.2031 +      // Set the SMIL part if needed.
  1.2032 +      if (smil) {
  1.2033 +        let part = {
  1.2034 +          "headers": {
  1.2035 +            "content-type": {
  1.2036 +              "media": "application/smil",
  1.2037 +              "params": {
  1.2038 +                "name": "smil.xml",
  1.2039 +                "charset": {
  1.2040 +                  "charset": "utf-8"
  1.2041 +                }
  1.2042 +              }
  1.2043 +            },
  1.2044 +            "content-location": "smil.xml",
  1.2045 +            "content-id": "<smil>"
  1.2046 +          },
  1.2047 +          "content": smil
  1.2048 +        };
  1.2049 +        parts.push(part);
  1.2050 +      }
  1.2051 +
  1.2052 +      // Set other parts for attachments if needed.
  1.2053 +      for (let i = 0; i < attachments.length; i++) {
  1.2054 +        let attachment = attachments[i];
  1.2055 +        let content = attachment.content;
  1.2056 +        let location = attachment.location;
  1.2057 +
  1.2058 +        let params = {
  1.2059 +          "name": location
  1.2060 +        };
  1.2061 +
  1.2062 +        if (content.type && content.type.indexOf("text/") == 0) {
  1.2063 +          params.charset = {
  1.2064 +            "charset": "utf-8"
  1.2065 +          };
  1.2066 +        }
  1.2067 +
  1.2068 +        let part = {
  1.2069 +          "headers": {
  1.2070 +            "content-type": {
  1.2071 +              "media": content.type,
  1.2072 +              "params": params
  1.2073 +            },
  1.2074 +            "content-location": location,
  1.2075 +            "content-id": attachment.id
  1.2076 +          },
  1.2077 +          "content": content
  1.2078 +        };
  1.2079 +        parts.push(part);
  1.2080 +      }
  1.2081 +    }
  1.2082 +
  1.2083 +    // The following attributes are needed for saving message into DB.
  1.2084 +    aMessage["type"] = "mms";
  1.2085 +    aMessage["timestamp"] = Date.now();
  1.2086 +    aMessage["receivers"] = receivers;
  1.2087 +    aMessage["sender"] = aMmsConnection.getPhoneNumber();
  1.2088 +    aMessage["iccId"] = aMmsConnection.getIccId();
  1.2089 +    try {
  1.2090 +      aMessage["deliveryStatusRequested"] =
  1.2091 +        Services.prefs.getBoolPref("dom.mms.requestStatusReport");
  1.2092 +    } catch (e) {
  1.2093 +      aMessage["deliveryStatusRequested"] = false;
  1.2094 +    }
  1.2095 +
  1.2096 +    if (DEBUG) debug("createSavableFromParams: aMessage: " +
  1.2097 +                     JSON.stringify(aMessage));
  1.2098 +
  1.2099 +    return isAddrValid ? Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR
  1.2100 +                       : Ci.nsIMobileMessageCallback.INVALID_ADDRESS_ERROR;
  1.2101 +  },
  1.2102 +
  1.2103 +  // nsIMmsService
  1.2104 +
  1.2105 +  mmsDefaultServiceId: 0,
  1.2106 +
  1.2107 +  send: function(aServiceId, aParams, aRequest) {
  1.2108 +    if (DEBUG) debug("send: aParams: " + JSON.stringify(aParams));
  1.2109 +
  1.2110 +    // Note that the following sanity checks for |aParams| should be consistent
  1.2111 +    // with the checks in SmsIPCService.GetSendMmsMessageRequestFromParams.
  1.2112 +
  1.2113 +    // Check if |aParams| is valid.
  1.2114 +    if (aParams == null || typeof aParams != "object") {
  1.2115 +      if (DEBUG) debug("Error! 'aParams' should be a non-null object.");
  1.2116 +      throw Cr.NS_ERROR_INVALID_ARG;
  1.2117 +      return;
  1.2118 +    }
  1.2119 +
  1.2120 +    // Check if |receivers| is valid.
  1.2121 +    if (!Array.isArray(aParams.receivers)) {
  1.2122 +      if (DEBUG) debug("Error! 'receivers' should be an array.");
  1.2123 +      throw Cr.NS_ERROR_INVALID_ARG;
  1.2124 +      return;
  1.2125 +    }
  1.2126 +
  1.2127 +    // Check if |subject| is valid.
  1.2128 +    if (aParams.subject != null && typeof aParams.subject != "string") {
  1.2129 +      if (DEBUG) debug("Error! 'subject' should be a string if passed.");
  1.2130 +      throw Cr.NS_ERROR_INVALID_ARG;
  1.2131 +      return;
  1.2132 +    }
  1.2133 +
  1.2134 +    // Check if |smil| is valid.
  1.2135 +    if (aParams.smil != null && typeof aParams.smil != "string") {
  1.2136 +      if (DEBUG) debug("Error! 'smil' should be a string if passed.");
  1.2137 +      throw Cr.NS_ERROR_INVALID_ARG;
  1.2138 +      return;
  1.2139 +    }
  1.2140 +
  1.2141 +    // Check if |attachments| is valid.
  1.2142 +    if (!Array.isArray(aParams.attachments)) {
  1.2143 +      if (DEBUG) debug("Error! 'attachments' should be an array.");
  1.2144 +      throw Cr.NS_ERROR_INVALID_ARG;
  1.2145 +      return;
  1.2146 +    }
  1.2147 +
  1.2148 +    let self = this;
  1.2149 +
  1.2150 +    let sendTransactionCb = function sendTransactionCb(aDomMessage,
  1.2151 +                                                       aErrorCode,
  1.2152 +                                                       aEnvelopeId) {
  1.2153 +      if (DEBUG) {
  1.2154 +        debug("The returned status of sending transaction: " +
  1.2155 +              "aErrorCode: " + aErrorCode + " aEnvelopeId: " + aEnvelopeId);
  1.2156 +      }
  1.2157 +
  1.2158 +      // If the messsage has been deleted (because the sending process is
  1.2159 +      // cancelled), we don't need to reset the its delievery state/status.
  1.2160 +      if (aErrorCode == Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR) {
  1.2161 +        aRequest.notifySendMessageFailed(aErrorCode);
  1.2162 +        Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
  1.2163 +        return;
  1.2164 +      }
  1.2165 +
  1.2166 +      let isSentSuccess = (aErrorCode == Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR);
  1.2167 +      gMobileMessageDatabaseService
  1.2168 +        .setMessageDeliveryByMessageId(aDomMessage.id,
  1.2169 +                                       null,
  1.2170 +                                       isSentSuccess ? DELIVERY_SENT : DELIVERY_ERROR,
  1.2171 +                                       isSentSuccess ? null : DELIVERY_STATUS_ERROR,
  1.2172 +                                       aEnvelopeId,
  1.2173 +                                       function notifySetDeliveryResult(aRv, aDomMessage) {
  1.2174 +        if (DEBUG) debug("Marking the delivery state/staus is done. Notify sent or failed.");
  1.2175 +        // TODO bug 832140 handle !Components.isSuccessCode(aRv)
  1.2176 +        if (!isSentSuccess) {
  1.2177 +          if (DEBUG) debug("Sending MMS failed.");
  1.2178 +          aRequest.notifySendMessageFailed(aErrorCode);
  1.2179 +          Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
  1.2180 +          return;
  1.2181 +        }
  1.2182 +
  1.2183 +        if (DEBUG) debug("Sending MMS succeeded.");
  1.2184 +
  1.2185 +        // Notifying observers the MMS message is sent.
  1.2186 +        self.broadcastSentMessageEvent(aDomMessage);
  1.2187 +
  1.2188 +        // Return the request after sending the MMS message successfully.
  1.2189 +        aRequest.notifyMessageSent(aDomMessage);
  1.2190 +      });
  1.2191 +    };
  1.2192 +
  1.2193 +    let mmsConnection = gMmsConnections.getConnByServiceId(aServiceId);
  1.2194 +
  1.2195 +    let savableMessage = {};
  1.2196 +    let errorCode = this.createSavableFromParams(mmsConnection, aParams,
  1.2197 +                                                 savableMessage);
  1.2198 +    gMobileMessageDatabaseService
  1.2199 +      .saveSendingMessage(savableMessage,
  1.2200 +                          function notifySendingResult(aRv, aDomMessage) {
  1.2201 +      if (!Components.isSuccessCode(aRv)) {
  1.2202 +        if (DEBUG) debug("Error! Fail to save sending message! rv = " + aRv);
  1.2203 +        aRequest.notifySendMessageFailed(
  1.2204 +          gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(aRv));
  1.2205 +        Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null);
  1.2206 +        return;
  1.2207 +      }
  1.2208 +
  1.2209 +      if (DEBUG) debug("Saving sending message is done. Start to send.");
  1.2210 +
  1.2211 +      Services.obs.notifyObservers(aDomMessage, kSmsSendingObserverTopic, null);
  1.2212 +
  1.2213 +      if (errorCode !== Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR) {
  1.2214 +        if (DEBUG) debug("Error! The params for sending MMS are invalid.");
  1.2215 +        sendTransactionCb(aDomMessage, errorCode, null);
  1.2216 +        return;
  1.2217 +      }
  1.2218 +
  1.2219 +      // Check radio state in prior to default service Id.
  1.2220 +      if (getRadioDisabledState()) {
  1.2221 +        if (DEBUG) debug("Error! Radio is disabled when sending MMS.");
  1.2222 +        sendTransactionCb(aDomMessage,
  1.2223 +                          Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR,
  1.2224 +                          null);
  1.2225 +        return;
  1.2226 +      }
  1.2227 +
  1.2228 +      // To support DSDS, we have to stop users sending MMS when the selected
  1.2229 +      // SIM is not active, thus avoiding the data disconnection of the current
  1.2230 +      // SIM. Users have to manually swith the default SIM before sending.
  1.2231 +      if (mmsConnection.serviceId != self.mmsDefaultServiceId) {
  1.2232 +        if (DEBUG) debug("RIL service is not active to send MMS.");
  1.2233 +        sendTransactionCb(aDomMessage,
  1.2234 +                          Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR,
  1.2235 +                          null);
  1.2236 +        return;
  1.2237 +      }
  1.2238 +
  1.2239 +      // This is the entry point starting to send MMS.
  1.2240 +      let sendTransaction;
  1.2241 +      try {
  1.2242 +        sendTransaction =
  1.2243 +          new SendTransaction(mmsConnection, aDomMessage.id, savableMessage,
  1.2244 +                              savableMessage["deliveryStatusRequested"]);
  1.2245 +      } catch (e) {
  1.2246 +        if (DEBUG) debug("Exception: fail to create a SendTransaction instance.");
  1.2247 +        sendTransactionCb(aDomMessage,
  1.2248 +                          Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null);
  1.2249 +        return;
  1.2250 +      }
  1.2251 +      sendTransaction.run(function callback(aMmsStatus, aMsg) {
  1.2252 +        if (DEBUG) debug("The sending status of sendTransaction.run(): " + aMmsStatus);
  1.2253 +        let errorCode;
  1.2254 +        if (aMmsStatus == _MMS_ERROR_MESSAGE_DELETED) {
  1.2255 +          errorCode = Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR;
  1.2256 +        } else if (aMmsStatus == _MMS_ERROR_RADIO_DISABLED) {
  1.2257 +          errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR;
  1.2258 +        } else if (aMmsStatus == _MMS_ERROR_NO_SIM_CARD) {
  1.2259 +          errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
  1.2260 +        } else if (aMmsStatus == _MMS_ERROR_SIM_CARD_CHANGED) {
  1.2261 +          errorCode = Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR;
  1.2262 +        } else if (aMmsStatus != MMS.MMS_PDU_ERROR_OK) {
  1.2263 +          errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR;
  1.2264 +        } else {
  1.2265 +          errorCode = Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR;
  1.2266 +        }
  1.2267 +        let envelopeId =
  1.2268 +          aMsg && aMsg.headers && aMsg.headers["message-id"] || null;
  1.2269 +        sendTransactionCb(aDomMessage, errorCode, envelopeId);
  1.2270 +      });
  1.2271 +    });
  1.2272 +  },
  1.2273 +
  1.2274 +  retrieve: function(aMessageId, aRequest) {
  1.2275 +    if (DEBUG) debug("Retrieving message with ID " + aMessageId);
  1.2276 +    gMobileMessageDatabaseService.getMessageRecordById(aMessageId,
  1.2277 +        (function notifyResult(aRv, aMessageRecord, aDomMessage) {
  1.2278 +      if (!Components.isSuccessCode(aRv)) {
  1.2279 +        if (DEBUG) debug("Function getMessageRecordById() return error: " + aRv);
  1.2280 +        aRequest.notifyGetMessageFailed(
  1.2281 +          gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(aRv));
  1.2282 +        return;
  1.2283 +      }
  1.2284 +      if ("mms" != aMessageRecord.type) {
  1.2285 +        if (DEBUG) debug("Type of message record is not 'mms'.");
  1.2286 +        aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  1.2287 +        return;
  1.2288 +      }
  1.2289 +      if (!aMessageRecord.headers) {
  1.2290 +        if (DEBUG) debug("Must need the MMS' headers to proceed the retrieve.");
  1.2291 +        aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  1.2292 +        return;
  1.2293 +      }
  1.2294 +      if (!aMessageRecord.headers["x-mms-content-location"]) {
  1.2295 +        if (DEBUG) debug("Can't find mms content url in database.");
  1.2296 +        aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  1.2297 +        return;
  1.2298 +      }
  1.2299 +      if (DELIVERY_NOT_DOWNLOADED != aMessageRecord.delivery) {
  1.2300 +        if (DEBUG) debug("Delivery of message record is not 'not-downloaded'.");
  1.2301 +        aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  1.2302 +        return;
  1.2303 +      }
  1.2304 +      let deliveryStatus = aMessageRecord.deliveryInfo[0].deliveryStatus;
  1.2305 +      if (DELIVERY_STATUS_PENDING == deliveryStatus) {
  1.2306 +        if (DEBUG) debug("Delivery status of message record is 'pending'.");
  1.2307 +        aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
  1.2308 +        return;
  1.2309 +      }
  1.2310 +
  1.2311 +      // Cite 6.2 "Multimedia Message Notification" in OMA-TS-MMS_ENC-V1_3-20110913-A:
  1.2312 +      // The field has only one format, relative. The recipient client calculates
  1.2313 +      // this length of time relative to the time it receives the notification.
  1.2314 +      if (aMessageRecord.headers["x-mms-expiry"] != undefined) {
  1.2315 +        let expiryDate = aMessageRecord.timestamp +
  1.2316 +                         aMessageRecord.headers["x-mms-expiry"] * 1000;
  1.2317 +        if (expiryDate < Date.now()) {
  1.2318 +          if (DEBUG) debug("The message to be retrieved is expired.");
  1.2319 +          aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR);
  1.2320 +          return;
  1.2321 +        }
  1.2322 +      }
  1.2323 +
  1.2324 +      // IccInfo in RadioInterface is not available when radio is off and
  1.2325 +      // NO_SIM_CARD_ERROR will be replied instead of RADIO_DISABLED_ERROR.
  1.2326 +      // Hence, for manual retrieving, instead of checking radio state later
  1.2327 +      // in MmsConnection.acquire(), We have to check radio state in prior to
  1.2328 +      // iccId to return the error correctly.
  1.2329 +      if (getRadioDisabledState()) {
  1.2330 +        if (DEBUG) debug("Error! Radio is disabled when retrieving MMS.");
  1.2331 +        aRequest.notifyGetMessageFailed(
  1.2332 +          Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR);
  1.2333 +        return;
  1.2334 +      }
  1.2335 +
  1.2336 +      // Get MmsConnection based on the saved MMS message record's ICC ID,
  1.2337 +      // which could fail when the corresponding SIM card isn't installed.
  1.2338 +      let mmsConnection;
  1.2339 +      try {
  1.2340 +        mmsConnection = gMmsConnections.getConnByIccId(aMessageRecord.iccId);
  1.2341 +      } catch (e) {
  1.2342 +        if (DEBUG) debug("Failed to get connection by IccId. e= " + e);
  1.2343 +        let error = (e === _MMS_ERROR_SIM_NOT_MATCHED) ?
  1.2344 +                      Ci.nsIMobileMessageCallback.SIM_NOT_MATCHED_ERROR :
  1.2345 +                      Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
  1.2346 +        aRequest.notifyGetMessageFailed(error);
  1.2347 +        return;
  1.2348 +      }
  1.2349 +
  1.2350 +      // To support DSDS, we have to stop users retrieving MMS when the needed
  1.2351 +      // SIM is not active, thus avoiding the data disconnection of the current
  1.2352 +      // SIM. Users have to manually swith the default SIM before retrieving.
  1.2353 +      if (mmsConnection.serviceId != this.mmsDefaultServiceId) {
  1.2354 +        if (DEBUG) debug("RIL service is not active to retrieve MMS.");
  1.2355 +        aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR);
  1.2356 +        return;
  1.2357 +      }
  1.2358 +
  1.2359 +      let url =  aMessageRecord.headers["x-mms-content-location"].uri;
  1.2360 +      // For X-Mms-Report-Allowed
  1.2361 +      let wish = aMessageRecord.headers["x-mms-delivery-report"];
  1.2362 +      let responseNotify = function responseNotify(mmsStatus, retrievedMsg) {
  1.2363 +        // If the messsage has been deleted (because the retrieving process is
  1.2364 +        // cancelled), we don't need to reset the its delievery state/status.
  1.2365 +        if (mmsStatus == _MMS_ERROR_MESSAGE_DELETED) {
  1.2366 +          aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR);
  1.2367 +          return;
  1.2368 +        }
  1.2369 +
  1.2370 +        // If the mmsStatus is still MMS_PDU_STATUS_DEFERRED after retry,
  1.2371 +        // we should not store it into database and update its delivery
  1.2372 +        // status to 'error'.
  1.2373 +        if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) {
  1.2374 +          if (DEBUG) debug("RetrieveMessage fail after retry.");
  1.2375 +          let errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR;
  1.2376 +          if (mmsStatus == _MMS_ERROR_RADIO_DISABLED) {
  1.2377 +            errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR;
  1.2378 +          } else if (mmsStatus == _MMS_ERROR_NO_SIM_CARD) {
  1.2379 +            errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
  1.2380 +          } else if (mmsStatus == _MMS_ERROR_SIM_CARD_CHANGED) {
  1.2381 +            errorCode = Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR;
  1.2382 +          }
  1.2383 +          gMobileMessageDatabaseService
  1.2384 +            .setMessageDeliveryByMessageId(aMessageId,
  1.2385 +                                           null,
  1.2386 +                                           null,
  1.2387 +                                           DELIVERY_STATUS_ERROR,
  1.2388 +                                           null,
  1.2389 +                                           function() {
  1.2390 +            aRequest.notifyGetMessageFailed(errorCode);
  1.2391 +          });
  1.2392 +          return;
  1.2393 +        }
  1.2394 +        // In OMA-TS-MMS_ENC-V1_3, Table 5 in page 25. This header field
  1.2395 +        // (x-mms-transaction-id) SHALL be present when the MMS Proxy relay
  1.2396 +        // seeks an acknowledgement for the MM delivered though M-Retrieve.conf
  1.2397 +        // PDU during deferred retrieval. This transaction ID is used by the MMS
  1.2398 +        // Client and MMS Proxy-Relay to provide linkage between the originated
  1.2399 +        // M-Retrieve.conf and the response M-Acknowledge.ind PDUs.
  1.2400 +        let transactionId = retrievedMsg.headers["x-mms-transaction-id"];
  1.2401 +
  1.2402 +        // The absence of the field does not indicate any default
  1.2403 +        // value. So we go checking the same field in retrieved
  1.2404 +        // message instead.
  1.2405 +        if (wish == null && retrievedMsg) {
  1.2406 +          wish = retrievedMsg.headers["x-mms-delivery-report"];
  1.2407 +        }
  1.2408 +        let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
  1.2409 +                                                  wish);
  1.2410 +
  1.2411 +        if (DEBUG) debug("retrievedMsg = " + JSON.stringify(retrievedMsg));
  1.2412 +        aMessageRecord = this.mergeRetrievalConfirmation(mmsConnection,
  1.2413 +                                                         retrievedMsg,
  1.2414 +                                                         aMessageRecord);
  1.2415 +
  1.2416 +        gMobileMessageDatabaseService.saveReceivedMessage(aMessageRecord,
  1.2417 +                                                          (function(rv, domMessage) {
  1.2418 +          let success = Components.isSuccessCode(rv);
  1.2419 +          if (!success) {
  1.2420 +            // At this point we could send a message to content to
  1.2421 +            // notify the user that storing an incoming MMS failed, most
  1.2422 +            // likely due to a full disk.
  1.2423 +            if (DEBUG) debug("Could not store MMS, error code " + rv);
  1.2424 +            aRequest.notifyGetMessageFailed(
  1.2425 +              gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv));
  1.2426 +            return;
  1.2427 +          }
  1.2428 +
  1.2429 +          // Notifying observers a new MMS message is retrieved.
  1.2430 +          this.broadcastReceivedMessageEvent(domMessage);
  1.2431 +
  1.2432 +          // Return the request after retrieving the MMS message successfully.
  1.2433 +          aRequest.notifyMessageGot(domMessage);
  1.2434 +
  1.2435 +          // Cite 6.3.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A:
  1.2436 +          // If an acknowledgement is requested, the MMS Client SHALL respond
  1.2437 +          // with an M-Acknowledge.ind PDU to the MMS Proxy-Relay that supports
  1.2438 +          // the specific MMS Client. The M-Acknowledge.ind PDU confirms
  1.2439 +          // successful message retrieval to the MMS Proxy Relay.
  1.2440 +          let transaction = new AcknowledgeTransaction(mmsConnection,
  1.2441 +                                                       transactionId,
  1.2442 +                                                       reportAllowed);
  1.2443 +          transaction.run();
  1.2444 +        }).bind(this));
  1.2445 +      };
  1.2446 +
  1.2447 +      // Update the delivery status to pending in DB.
  1.2448 +      gMobileMessageDatabaseService
  1.2449 +        .setMessageDeliveryByMessageId(aMessageId,
  1.2450 +                                       null,
  1.2451 +                                       null,
  1.2452 +                                       DELIVERY_STATUS_PENDING,
  1.2453 +                                       null,
  1.2454 +                                       (function(rv) {
  1.2455 +          let success = Components.isSuccessCode(rv);
  1.2456 +          if (!success) {
  1.2457 +            if (DEBUG) debug("Could not change the delivery status, error code " + rv);
  1.2458 +            aRequest.notifyGetMessageFailed(
  1.2459 +              gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv));
  1.2460 +            return;
  1.2461 +          }
  1.2462 +
  1.2463 +          this.retrieveMessage(mmsConnection,
  1.2464 +                               url,
  1.2465 +                               responseNotify.bind(this),
  1.2466 +                               aDomMessage);
  1.2467 +        }).bind(this));
  1.2468 +    }).bind(this));
  1.2469 +  },
  1.2470 +
  1.2471 +  sendReadReport: function(messageID, toAddress, iccId) {
  1.2472 +    if (DEBUG) {
  1.2473 +      debug("messageID: " + messageID + " toAddress: " +
  1.2474 +            JSON.stringify(toAddress));
  1.2475 +    }
  1.2476 +
  1.2477 +    // Get MmsConnection based on the saved MMS message record's ICC ID,
  1.2478 +    // which could fail when the corresponding SIM card isn't installed.
  1.2479 +    let mmsConnection;
  1.2480 +    try {
  1.2481 +      mmsConnection = gMmsConnections.getConnByIccId(iccId);
  1.2482 +    } catch (e) {
  1.2483 +      if (DEBUG) debug("Failed to get connection by IccId. e = " + e);
  1.2484 +      return;
  1.2485 +    }
  1.2486 +
  1.2487 +    try {
  1.2488 +      let transaction =
  1.2489 +        new ReadRecTransaction(mmsConnection, messageID, toAddress);
  1.2490 +      transaction.run();
  1.2491 +    } catch (e) {
  1.2492 +      if (DEBUG) debug("sendReadReport fail. e = " + e);
  1.2493 +    }
  1.2494 +  },
  1.2495 +
  1.2496 +  // nsIWapPushApplication
  1.2497 +
  1.2498 +  receiveWapPush: function(array, length, offset, options) {
  1.2499 +    let data = {array: array, offset: offset};
  1.2500 +    let msg = MMS.PduHelper.parse(data, null);
  1.2501 +    if (!msg) {
  1.2502 +      return false;
  1.2503 +    }
  1.2504 +    if (DEBUG) debug("receiveWapPush: msg = " + JSON.stringify(msg));
  1.2505 +
  1.2506 +    switch (msg.type) {
  1.2507 +      case MMS.MMS_PDU_TYPE_NOTIFICATION_IND:
  1.2508 +        this.handleNotificationIndication(options.serviceId, msg);
  1.2509 +        break;
  1.2510 +      case MMS.MMS_PDU_TYPE_DELIVERY_IND:
  1.2511 +        this.handleDeliveryIndication(msg);
  1.2512 +        break;
  1.2513 +      case MMS.MMS_PDU_TYPE_READ_ORIG_IND:
  1.2514 +        this.handleReadOriginateIndication(msg);
  1.2515 +        break;
  1.2516 +      default:
  1.2517 +        if (DEBUG) debug("Unsupported X-MMS-Message-Type: " + msg.type);
  1.2518 +        break;
  1.2519 +    }
  1.2520 +  },
  1.2521 +
  1.2522 +  // nsIObserver
  1.2523 +
  1.2524 +  observe: function(aSubject, aTopic, aData) {
  1.2525 +    switch (aTopic) {
  1.2526 +      case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
  1.2527 +        if (aData === kPrefDefaultServiceId) {
  1.2528 +          this.mmsDefaultServiceId = getDefaultServiceId();
  1.2529 +        }
  1.2530 +        break;
  1.2531 +    }
  1.2532 +  }
  1.2533 +};
  1.2534 +
  1.2535 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MmsService]);

mercurial