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]);