michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 sts=2 et filetype=javascript michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: Cu.import("resource://gre/modules/NetUtil.jsm"); michael@0: Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); michael@0: michael@0: const RIL_MMSSERVICE_CONTRACTID = "@mozilla.org/mms/rilmmsservice;1"; michael@0: const RIL_MMSSERVICE_CID = Components.ID("{217ddd76-75db-4210-955d-8806cd8d87f9}"); michael@0: michael@0: let DEBUG = false; michael@0: function debug(s) { michael@0: dump("-@- MmsService: " + s + "\n"); michael@0: }; michael@0: michael@0: // Read debug setting from pref. michael@0: try { michael@0: let debugPref = Services.prefs.getBoolPref("mms.debugging.enabled"); michael@0: DEBUG = DEBUG || debugPref; michael@0: } catch (e) {} michael@0: michael@0: const kSmsSendingObserverTopic = "sms-sending"; michael@0: const kSmsSentObserverTopic = "sms-sent"; michael@0: const kSmsFailedObserverTopic = "sms-failed"; michael@0: const kSmsReceivedObserverTopic = "sms-received"; michael@0: const kSmsRetrievingObserverTopic = "sms-retrieving"; michael@0: const kSmsDeliverySuccessObserverTopic = "sms-delivery-success"; michael@0: const kSmsDeliveryErrorObserverTopic = "sms-delivery-error"; michael@0: const kSmsReadSuccessObserverTopic = "sms-read-success"; michael@0: const kSmsReadErrorObserverTopic = "sms-read-error"; michael@0: michael@0: const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown"; michael@0: const kNetworkConnStateChangedTopic = "network-connection-state-changed"; michael@0: const kMobileMessageDeletedObserverTopic = "mobile-message-deleted"; michael@0: michael@0: const kPrefRilRadioDisabled = "ril.radio.disabled"; michael@0: michael@0: // HTTP status codes: michael@0: // @see http://tools.ietf.org/html/rfc2616#page-39 michael@0: const HTTP_STATUS_OK = 200; michael@0: michael@0: // Non-standard HTTP status for internal use. michael@0: const _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS = 0; michael@0: const _HTTP_STATUS_USER_CANCELLED = -1; michael@0: const _HTTP_STATUS_RADIO_DISABLED = -2; michael@0: const _HTTP_STATUS_NO_SIM_CARD = -3; michael@0: const _HTTP_STATUS_ACQUIRE_TIMEOUT = -4; michael@0: michael@0: // Non-standard MMS status for internal use. michael@0: const _MMS_ERROR_MESSAGE_DELETED = -1; michael@0: const _MMS_ERROR_RADIO_DISABLED = -2; michael@0: const _MMS_ERROR_NO_SIM_CARD = -3; michael@0: const _MMS_ERROR_SIM_CARD_CHANGED = -4; michael@0: const _MMS_ERROR_SHUTDOWN = -5; michael@0: const _MMS_ERROR_USER_CANCELLED_NO_REASON = -6; michael@0: const _MMS_ERROR_SIM_NOT_MATCHED = -7; michael@0: michael@0: const CONFIG_SEND_REPORT_NEVER = 0; michael@0: const CONFIG_SEND_REPORT_DEFAULT_NO = 1; michael@0: const CONFIG_SEND_REPORT_DEFAULT_YES = 2; michael@0: const CONFIG_SEND_REPORT_ALWAYS = 3; michael@0: michael@0: const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; michael@0: michael@0: const TIME_TO_BUFFER_MMS_REQUESTS = 30000; michael@0: const PREF_TIME_TO_RELEASE_MMS_CONNECTION = michael@0: Services.prefs.getIntPref("network.gonk.ms-release-mms-connection"); michael@0: michael@0: const kPrefRetrievalMode = 'dom.mms.retrieval_mode'; michael@0: const RETRIEVAL_MODE_MANUAL = "manual"; michael@0: const RETRIEVAL_MODE_AUTOMATIC = "automatic"; michael@0: const RETRIEVAL_MODE_AUTOMATIC_HOME = "automatic-home"; michael@0: const RETRIEVAL_MODE_NEVER = "never"; michael@0: michael@0: //Internal const values. michael@0: const DELIVERY_RECEIVED = "received"; michael@0: const DELIVERY_NOT_DOWNLOADED = "not-downloaded"; michael@0: const DELIVERY_SENDING = "sending"; michael@0: const DELIVERY_SENT = "sent"; michael@0: const DELIVERY_ERROR = "error"; michael@0: michael@0: const DELIVERY_STATUS_SUCCESS = "success"; michael@0: const DELIVERY_STATUS_PENDING = "pending"; michael@0: const DELIVERY_STATUS_ERROR = "error"; michael@0: const DELIVERY_STATUS_REJECTED = "rejected"; michael@0: const DELIVERY_STATUS_MANUAL = "manual"; michael@0: const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable"; michael@0: michael@0: const PREF_SEND_RETRY_COUNT = michael@0: Services.prefs.getIntPref("dom.mms.sendRetryCount"); michael@0: michael@0: const PREF_SEND_RETRY_INTERVAL = michael@0: Services.prefs.getIntPref("dom.mms.sendRetryInterval"); michael@0: michael@0: const PREF_RETRIEVAL_RETRY_COUNT = michael@0: Services.prefs.getIntPref("dom.mms.retrievalRetryCount"); michael@0: michael@0: const PREF_RETRIEVAL_RETRY_INTERVALS = (function() { michael@0: let intervals = michael@0: Services.prefs.getCharPref("dom.mms.retrievalRetryIntervals").split(","); michael@0: for (let i = 0; i < PREF_RETRIEVAL_RETRY_COUNT; ++i) { michael@0: intervals[i] = parseInt(intervals[i], 10); michael@0: // If one of the intervals isn't valid (e.g., 0 or NaN), michael@0: // assign a 10-minute interval to it as a default. michael@0: if (!intervals[i]) { michael@0: intervals[i] = 600000; michael@0: } michael@0: } michael@0: intervals.length = PREF_RETRIEVAL_RETRY_COUNT; michael@0: return intervals; michael@0: })(); michael@0: michael@0: const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces"; michael@0: const kPrefDefaultServiceId = "dom.mms.defaultServiceId"; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gpps", michael@0: "@mozilla.org/network/protocol-proxy-service;1", michael@0: "nsIProtocolProxyService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", michael@0: "@mozilla.org/uuid-generator;1", michael@0: "nsIUUIDGenerator"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageDatabaseService", michael@0: "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1", michael@0: "nsIRilMobileMessageDatabaseService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService", michael@0: "@mozilla.org/mobilemessage/mobilemessageservice;1", michael@0: "nsIMobileMessageService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger", michael@0: "@mozilla.org/system-message-internal;1", michael@0: "nsISystemMessagesInternal"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gRil", michael@0: "@mozilla.org/ril;1", michael@0: "nsIRadioInterfaceLayer"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "MMS", function() { michael@0: let MMS = {}; michael@0: Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS); michael@0: return MMS; michael@0: }); michael@0: michael@0: // Internal Utilities michael@0: michael@0: /** michael@0: * Return default service Id for MMS. michael@0: */ michael@0: function getDefaultServiceId() { michael@0: let id = Services.prefs.getIntPref(kPrefDefaultServiceId); michael@0: let numRil = Services.prefs.getIntPref(kPrefRilNumRadioInterfaces); michael@0: michael@0: if (id >= numRil || id < 0) { michael@0: id = 0; michael@0: } michael@0: michael@0: return id; michael@0: } michael@0: michael@0: /** michael@0: * Return Radio disabled state. michael@0: */ michael@0: function getRadioDisabledState() { michael@0: let state; michael@0: try { michael@0: state = Services.prefs.getBoolPref(kPrefRilRadioDisabled); michael@0: } catch (e) { michael@0: if (DEBUG) debug("Getting preference 'ril.radio.disabled' fails."); michael@0: state = false; michael@0: } michael@0: michael@0: return state; michael@0: } michael@0: michael@0: /** michael@0: * Helper Class to control MMS Data Connection. michael@0: */ michael@0: function MmsConnection(aServiceId) { michael@0: this.serviceId = aServiceId; michael@0: this.radioInterface = gRil.getRadioInterface(aServiceId); michael@0: }; michael@0: michael@0: MmsConnection.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), michael@0: michael@0: /** MMS proxy settings. */ michael@0: mmsc: "", michael@0: mmsProxy: "", michael@0: mmsPort: -1, michael@0: michael@0: setApnSetting: function(network) { michael@0: this.mmsc = network.mmsc; michael@0: this.mmsProxy = network.mmsProxy; michael@0: this.mmsPort = network.mmsPort; michael@0: }, michael@0: michael@0: get proxyInfo() { michael@0: if (!this.mmsProxy) { michael@0: if (DEBUG) debug("getProxyInfo: MMS proxy is not available."); michael@0: return null; michael@0: } michael@0: michael@0: let port = this.mmsPort; michael@0: michael@0: if (port <= 0) { michael@0: port = 80; michael@0: if (DEBUG) debug("getProxyInfo: port is not valid. Set to defult (80)."); michael@0: } michael@0: michael@0: let proxyInfo = michael@0: gpps.newProxyInfo("http", this.mmsProxy, port, michael@0: Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST, michael@0: -1, null); michael@0: if (DEBUG) debug("getProxyInfo: " + JSON.stringify(proxyInfo)); michael@0: michael@0: return proxyInfo; michael@0: }, michael@0: michael@0: connected: false, michael@0: michael@0: //A queue to buffer the MMS HTTP requests when the MMS network michael@0: //is not yet connected. The buffered requests will be cleared michael@0: //if the MMS network fails to be connected within a timer. michael@0: pendingCallbacks: [], michael@0: michael@0: /** MMS network connection reference count. */ michael@0: refCount: 0, michael@0: michael@0: connectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), michael@0: michael@0: disconnectTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), michael@0: michael@0: /** michael@0: * Callback when |connectTimer| is timeout or cancelled by shutdown. michael@0: */ michael@0: flushPendingCallbacks: function(status) { michael@0: if (DEBUG) debug("flushPendingCallbacks: " + this.pendingCallbacks.length michael@0: + " pending callbacks with status: " + status); michael@0: while (this.pendingCallbacks.length) { michael@0: let callback = this.pendingCallbacks.shift(); michael@0: let connected = (status == _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS); michael@0: callback(connected, status); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Callback when |disconnectTimer| is timeout or cancelled by shutdown. michael@0: */ michael@0: onDisconnectTimerTimeout: function() { michael@0: if (DEBUG) debug("onDisconnectTimerTimeout: deactivate the MMS data call."); michael@0: if (this.connected) { michael@0: this.radioInterface.deactivateDataCallByType("mms"); michael@0: } michael@0: }, michael@0: michael@0: init: function() { michael@0: Services.obs.addObserver(this, kNetworkConnStateChangedTopic, michael@0: false); michael@0: Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: michael@0: this.connected = this.radioInterface.getDataCallStateByType("mms") == michael@0: Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED; michael@0: // If the MMS network is connected during the initialization, it means the michael@0: // MMS network must share the same APN with the mobile network by default. michael@0: // Under this case, |networkManager.active| should keep the mobile network, michael@0: // which is supposed be an instance of |nsIRilNetworkInterface| for sure. michael@0: if (this.connected) { michael@0: let networkManager = michael@0: Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); michael@0: let activeNetwork = networkManager.active; michael@0: michael@0: let rilNetwork = activeNetwork.QueryInterface(Ci.nsIRilNetworkInterface); michael@0: if (rilNetwork.serviceId != this.serviceId) { michael@0: if (DEBUG) debug("Sevice ID between active/MMS network doesn't match."); michael@0: return; michael@0: } michael@0: michael@0: // Set up the MMS APN setting based on the connected MMS network, michael@0: // which is going to be used for the HTTP requests later. michael@0: this.setApnSetting(rilNetwork); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Return the roaming status of voice call. michael@0: * michael@0: * @return true if voice call is roaming. michael@0: */ michael@0: isVoiceRoaming: function() { michael@0: let isRoaming = this.radioInterface.rilContext.voice.roaming; michael@0: if (DEBUG) debug("isVoiceRoaming = " + isRoaming); michael@0: return isRoaming; michael@0: }, michael@0: michael@0: /** michael@0: * Get phone number from iccInfo. michael@0: * michael@0: * If the icc card is gsm card, the phone number is in msisdn. michael@0: * @see nsIDOMMozGsmIccInfo michael@0: * michael@0: * Otherwise, the phone number is in mdn. michael@0: * @see nsIDOMMozCdmaIccInfo michael@0: */ michael@0: getPhoneNumber: function() { michael@0: let iccInfo = this.radioInterface.rilContext.iccInfo; michael@0: michael@0: if (!iccInfo) { michael@0: return null; michael@0: } michael@0: michael@0: let number = (iccInfo instanceof Ci.nsIDOMMozGsmIccInfo) michael@0: ? iccInfo.msisdn : iccInfo.mdn; michael@0: michael@0: // Workaround an xpconnect issue with undefined string objects. michael@0: // See bug 808220 michael@0: if (number === undefined || number === "undefined") { michael@0: return null; michael@0: } michael@0: michael@0: return number; michael@0: }, michael@0: michael@0: /** michael@0: * A utility function to get the ICC ID of the SIM card (if installed). michael@0: */ michael@0: getIccId: function() { michael@0: let iccInfo = this.radioInterface.rilContext.iccInfo; michael@0: michael@0: if (!iccInfo) { michael@0: return null; michael@0: } michael@0: michael@0: let iccId = iccInfo.iccid; michael@0: michael@0: // Workaround an xpconnect issue with undefined string objects. michael@0: // See bug 808220 michael@0: if (iccId === undefined || iccId === "undefined") { michael@0: return null; michael@0: } michael@0: michael@0: return iccId; michael@0: }, michael@0: michael@0: /** michael@0: * Acquire the MMS network connection. michael@0: * michael@0: * @param callback michael@0: * Callback function when either the connection setup is done, michael@0: * timeout, or failed. Parameters are: michael@0: * - A boolean value indicates whether the connection is ready. michael@0: * - Acquire connection status: _HTTP_STATUS_ACQUIRE_*. michael@0: * michael@0: * @return true if the callback for MMS network connection is done; false michael@0: * otherwise. michael@0: */ michael@0: acquire: function(callback) { michael@0: this.refCount++; michael@0: this.connectTimer.cancel(); michael@0: this.disconnectTimer.cancel(); michael@0: michael@0: // If the MMS network is not yet connected, buffer the michael@0: // MMS request and try to setup the MMS network first. michael@0: if (!this.connected) { michael@0: this.pendingCallbacks.push(callback); michael@0: michael@0: let errorStatus; michael@0: if (getRadioDisabledState()) { michael@0: if (DEBUG) debug("Error! Radio is disabled when sending MMS."); michael@0: errorStatus = _HTTP_STATUS_RADIO_DISABLED; michael@0: } else if (this.radioInterface.rilContext.cardState != "ready") { michael@0: if (DEBUG) debug("Error! SIM card is not ready when sending MMS."); michael@0: errorStatus = _HTTP_STATUS_NO_SIM_CARD; michael@0: } michael@0: if (errorStatus != null) { michael@0: this.flushPendingCallbacks(errorStatus); michael@0: return true; michael@0: } michael@0: michael@0: if (DEBUG) debug("acquire: buffer the MMS request and setup the MMS data call."); michael@0: this.radioInterface.setupDataCallByType("mms"); michael@0: michael@0: // Set a timer to clear the buffered MMS requests if the michael@0: // MMS network fails to be connected within a time period. michael@0: this.connectTimer. michael@0: initWithCallback(this.flushPendingCallbacks.bind(this, _HTTP_STATUS_ACQUIRE_TIMEOUT), michael@0: TIME_TO_BUFFER_MMS_REQUESTS, michael@0: Ci.nsITimer.TYPE_ONE_SHOT); michael@0: return false; michael@0: } michael@0: michael@0: callback(true, _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS); michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Release the MMS network connection. michael@0: */ michael@0: release: function() { michael@0: this.refCount--; michael@0: if (this.refCount <= 0) { michael@0: this.refCount = 0; michael@0: michael@0: // The waiting is too small, just skip the timer creation. michael@0: if (PREF_TIME_TO_RELEASE_MMS_CONNECTION < 1000) { michael@0: this.onDisconnectTimerTimeout(); michael@0: return; michael@0: } michael@0: michael@0: // Set a timer to delay the release of MMS network connection, michael@0: // since the MMS requests often come consecutively in a short time. michael@0: this.disconnectTimer. michael@0: initWithCallback(this.onDisconnectTimerTimeout.bind(this), michael@0: PREF_TIME_TO_RELEASE_MMS_CONNECTION, michael@0: Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: }, michael@0: michael@0: shutdown: function() { michael@0: Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: Services.obs.removeObserver(this, kNetworkConnStateChangedTopic); michael@0: michael@0: this.connectTimer.cancel(); michael@0: this.flushPendingCallbacks(_HTTP_STATUS_RADIO_DISABLED); michael@0: this.disconnectTimer.cancel(); michael@0: this.onDisconnectTimerTimeout(); michael@0: }, michael@0: michael@0: // nsIObserver michael@0: michael@0: observe: function(subject, topic, data) { michael@0: switch (topic) { michael@0: case kNetworkConnStateChangedTopic: { michael@0: // The network for MMS connection must be nsIRilNetworkInterface. michael@0: if (!(subject instanceof Ci.nsIRilNetworkInterface)) { michael@0: return; michael@0: } michael@0: michael@0: // Check if the network state change belongs to this service. michael@0: let network = subject.QueryInterface(Ci.nsIRilNetworkInterface); michael@0: if (network.serviceId != this.serviceId) { michael@0: return; michael@0: } michael@0: michael@0: // We only need to capture the state change of MMS network. Using michael@0: // |network.state| isn't reliable due to the possibilty of shared APN. michael@0: let connected = michael@0: this.radioInterface.getDataCallStateByType("mms") == michael@0: Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED; michael@0: michael@0: // Return if the MMS network state doesn't change, where the network michael@0: // state change can come from other non-MMS networks. michael@0: if (connected == this.connected) { michael@0: return; michael@0: } michael@0: michael@0: this.connected = connected; michael@0: if (!this.connected) { michael@0: return; michael@0: } michael@0: michael@0: // Set up the MMS APN setting based on the connected MMS network, michael@0: // which is going to be used for the HTTP requests later. michael@0: this.setApnSetting(network); michael@0: michael@0: if (DEBUG) debug("Got the MMS network connected! Resend the buffered " + michael@0: "MMS requests: number: " + this.pendingCallbacks.length); michael@0: this.connectTimer.cancel(); michael@0: this.flushPendingCallbacks(_HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS) michael@0: break; michael@0: } michael@0: case NS_XPCOM_SHUTDOWN_OBSERVER_ID: { michael@0: this.shutdown(); michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gMmsConnections", function() { michael@0: return { michael@0: _connections: null, michael@0: getConnByServiceId: function(id) { michael@0: if (!this._connections) { michael@0: this._connections = []; michael@0: } michael@0: michael@0: let conn = this._connections[id]; michael@0: if (conn) { michael@0: return conn; michael@0: } michael@0: michael@0: conn = this._connections[id] = new MmsConnection(id); michael@0: conn.init(); michael@0: return conn; michael@0: }, michael@0: getConnByIccId: function(aIccId) { michael@0: if (!aIccId) { michael@0: // If the ICC ID isn't available, it means the MMS has been received michael@0: // during the previous version that didn't take the DSDS scenario michael@0: // into consideration. Tentatively, get connection from serviceId(0) by michael@0: // default is better than nothing. Although it might use the wrong michael@0: // SIM to download the desired MMS, eventually it would still fail to michael@0: // download due to the wrong MMSC and proxy settings. michael@0: return this.getConnByServiceId(0); michael@0: } michael@0: michael@0: let numCardAbsent = 0; michael@0: let numRadioInterfaces = gRil.numRadioInterfaces; michael@0: for (let clientId = 0; clientId < numRadioInterfaces; clientId++) { michael@0: let mmsConnection = this.getConnByServiceId(clientId); michael@0: let iccId = mmsConnection.getIccId(); michael@0: if (iccId === null) { michael@0: numCardAbsent++; michael@0: continue; michael@0: } michael@0: michael@0: if (iccId === aIccId) { michael@0: return mmsConnection; michael@0: } michael@0: } michael@0: michael@0: throw ((numCardAbsent === numRadioInterfaces)? michael@0: _MMS_ERROR_NO_SIM_CARD: _MMS_ERROR_SIM_NOT_MATCHED); michael@0: }, michael@0: }; michael@0: }); michael@0: michael@0: /** michael@0: * Implementation of nsIProtocolProxyFilter for MMS Proxy michael@0: */ michael@0: function MmsProxyFilter(mmsConnection, url) { michael@0: this.mmsConnection = mmsConnection; michael@0: this.uri = Services.io.newURI(url, null, null); michael@0: } michael@0: MmsProxyFilter.prototype = { michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolProxyFilter]), michael@0: michael@0: // nsIProtocolProxyFilter michael@0: michael@0: applyFilter: function(proxyService, uri, proxyInfo) { michael@0: if (!this.uri.equals(uri)) { michael@0: if (DEBUG) debug("applyFilter: content uri = " + JSON.stringify(this.uri) + michael@0: " is not matched with uri = " + JSON.stringify(uri) + " ."); michael@0: return proxyInfo; michael@0: } michael@0: michael@0: // Fall-through, reutrn the MMS proxy info. michael@0: let mmsProxyInfo = this.mmsConnection.proxyInfo; michael@0: michael@0: if (DEBUG) { michael@0: debug("applyFilter: MMSC/Content Location is matched with: " + michael@0: JSON.stringify({ uri: JSON.stringify(this.uri), michael@0: mmsProxyInfo: mmsProxyInfo })); michael@0: } michael@0: michael@0: return mmsProxyInfo ? mmsProxyInfo : proxyInfo; michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gMmsTransactionHelper", function() { michael@0: let helper = { michael@0: /** michael@0: * Send MMS request to MMSC. michael@0: * michael@0: * @param mmsConnection michael@0: * The MMS connection. michael@0: * @param method michael@0: * "GET" or "POST". michael@0: * @param url michael@0: * Target url string or null to be replaced by mmsc url. michael@0: * @param istream michael@0: * An nsIInputStream instance as data source to be sent or null. michael@0: * @param callback michael@0: * A callback function that takes two arguments: one for http michael@0: * status, the other for wrapped PDU data for further parsing. michael@0: */ michael@0: sendRequest: function(mmsConnection, method, url, istream, callback) { michael@0: // TODO: bug 810226 - Support GPRS bearer for MMS transmission and reception. michael@0: let cancellable = { michael@0: callback: callback, michael@0: michael@0: isDone: false, michael@0: isCancelled: false, michael@0: michael@0: cancel: function() { michael@0: if (this.isDone) { michael@0: // It's too late to cancel. michael@0: return; michael@0: } michael@0: michael@0: this.isCancelled = true; michael@0: if (this.isAcquiringConn) { michael@0: // We cannot cancel data connection setup here, so we invoke done() michael@0: // here and handle |cancellable.isDone| in callback function of michael@0: // |mmsConnection.acquire|. michael@0: this.done(_HTTP_STATUS_USER_CANCELLED, null); michael@0: } else if (this.xhr) { michael@0: // Client has already sent the HTTP request. Try to abort it. michael@0: this.xhr.abort(); michael@0: } michael@0: }, michael@0: michael@0: done: function(httpStatus, data) { michael@0: this.isDone = true; michael@0: if (!this.callback) { michael@0: return; michael@0: } michael@0: michael@0: if (this.isCancelled) { michael@0: this.callback(_HTTP_STATUS_USER_CANCELLED, null); michael@0: } else { michael@0: this.callback(httpStatus, data); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: cancellable.isAcquiringConn = michael@0: !mmsConnection.acquire((function(connected, errorCode) { michael@0: michael@0: cancellable.isAcquiringConn = false; michael@0: michael@0: if (!connected || cancellable.isCancelled) { michael@0: mmsConnection.release(); michael@0: michael@0: if (!cancellable.isDone) { michael@0: cancellable.done(cancellable.isCancelled ? michael@0: _HTTP_STATUS_USER_CANCELLED : errorCode, null); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // MMSC is available after an MMS connection is successfully acquired. michael@0: if (!url) { michael@0: url = mmsConnection.mmsc; michael@0: } michael@0: michael@0: if (DEBUG) debug("sendRequest: register proxy filter to " + url); michael@0: let proxyFilter = new MmsProxyFilter(mmsConnection, url); michael@0: gpps.registerFilter(proxyFilter, 0); michael@0: michael@0: cancellable.xhr = this.sendHttpRequest(mmsConnection, method, michael@0: url, istream, proxyFilter, michael@0: cancellable.done.bind(cancellable)); michael@0: }).bind(this)); michael@0: michael@0: return cancellable; michael@0: }, michael@0: michael@0: sendHttpRequest: function(mmsConnection, method, url, istream, proxyFilter, michael@0: callback) { michael@0: let releaseMmsConnectionAndCallback = function(httpStatus, data) { michael@0: gpps.unregisterFilter(proxyFilter); michael@0: // Always release the MMS network connection before callback. michael@0: mmsConnection.release(); michael@0: callback(httpStatus, data); michael@0: }; michael@0: michael@0: try { michael@0: let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] michael@0: .createInstance(Ci.nsIXMLHttpRequest); michael@0: michael@0: // Basic setups michael@0: xhr.open(method, url, true); michael@0: xhr.responseType = "arraybuffer"; michael@0: if (istream) { michael@0: xhr.setRequestHeader("Content-Type", michael@0: "application/vnd.wap.mms-message"); michael@0: xhr.setRequestHeader("Content-Length", istream.available()); michael@0: } michael@0: michael@0: // UAProf headers. michael@0: let uaProfUrl, uaProfTagname = "x-wap-profile"; michael@0: try { michael@0: uaProfUrl = Services.prefs.getCharPref('wap.UAProf.url'); michael@0: uaProfTagname = Services.prefs.getCharPref('wap.UAProf.tagname'); michael@0: } catch (e) {} michael@0: michael@0: if (uaProfUrl) { michael@0: xhr.setRequestHeader(uaProfTagname, uaProfUrl); michael@0: } michael@0: michael@0: // Setup event listeners michael@0: xhr.onreadystatechange = function() { michael@0: if (xhr.readyState != Ci.nsIXMLHttpRequest.DONE) { michael@0: return; michael@0: } michael@0: let data = null; michael@0: switch (xhr.status) { michael@0: case HTTP_STATUS_OK: { michael@0: if (DEBUG) debug("xhr success, response headers: " michael@0: + xhr.getAllResponseHeaders()); michael@0: let array = new Uint8Array(xhr.response); michael@0: if (false) { michael@0: for (let begin = 0; begin < array.length; begin += 20) { michael@0: let partial = array.subarray(begin, begin + 20); michael@0: if (DEBUG) debug("res: " + JSON.stringify(partial)); michael@0: } michael@0: } michael@0: michael@0: data = {array: array, offset: 0}; michael@0: break; michael@0: } michael@0: michael@0: default: { michael@0: if (DEBUG) debug("xhr done, but status = " + xhr.status + michael@0: ", statusText = " + xhr.statusText); michael@0: break; michael@0: } michael@0: } michael@0: releaseMmsConnectionAndCallback(xhr.status, data); michael@0: }; michael@0: // Send request michael@0: xhr.send(istream); michael@0: return xhr; michael@0: } catch (e) { michael@0: if (DEBUG) debug("xhr error, can't send: " + e.message); michael@0: releaseMmsConnectionAndCallback(0, null); michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Count number of recipients(to, cc, bcc fields). michael@0: * michael@0: * @param recipients michael@0: * The recipients in MMS message object. michael@0: * @return the number of recipients michael@0: * @see OMA-TS-MMS_CONF-V1_3-20110511-C section 10.2.5 michael@0: */ michael@0: countRecipients: function(recipients) { michael@0: if (recipients && recipients.address) { michael@0: return 1; michael@0: } michael@0: let totalRecipients = 0; michael@0: if (!Array.isArray(recipients)) { michael@0: return 0; michael@0: } michael@0: totalRecipients += recipients.length; michael@0: for (let ix = 0; ix < recipients.length; ++ix) { michael@0: if (recipients[ix].address.length > MMS.MMS_MAX_LENGTH_RECIPIENT) { michael@0: throw new Error("MMS_MAX_LENGTH_RECIPIENT error"); michael@0: } michael@0: if (recipients[ix].type === "email") { michael@0: let found = recipients[ix].address.indexOf("<"); michael@0: let lenMailbox = recipients[ix].address.length - found; michael@0: if(lenMailbox > MMS.MMS_MAX_LENGTH_MAILBOX_PORTION) { michael@0: throw new Error("MMS_MAX_LENGTH_MAILBOX_PORTION error"); michael@0: } michael@0: } michael@0: } michael@0: return totalRecipients; michael@0: }, michael@0: michael@0: /** michael@0: * Check maximum values of MMS parameters. michael@0: * michael@0: * @param msg michael@0: * The MMS message object. michael@0: * @return true if the lengths are less than the maximum values of MMS michael@0: * parameters. michael@0: * @see OMA-TS-MMS_CONF-V1_3-20110511-C section 10.2.5 michael@0: */ michael@0: checkMaxValuesParameters: function(msg) { michael@0: let subject = msg.headers["subject"]; michael@0: if (subject && subject.length > MMS.MMS_MAX_LENGTH_SUBJECT) { michael@0: return false; michael@0: } michael@0: michael@0: let totalRecipients = 0; michael@0: try { michael@0: totalRecipients += this.countRecipients(msg.headers["to"]); michael@0: totalRecipients += this.countRecipients(msg.headers["cc"]); michael@0: totalRecipients += this.countRecipients(msg.headers["bcc"]); michael@0: } catch (ex) { michael@0: if (DEBUG) debug("Exception caught : " + ex); michael@0: return false; michael@0: } michael@0: michael@0: if (totalRecipients < 1 || michael@0: totalRecipients > MMS.MMS_MAX_TOTAL_RECIPIENTS) { michael@0: return false; michael@0: } michael@0: michael@0: if (!Array.isArray(msg.parts)) { michael@0: return true; michael@0: } michael@0: for (let i = 0; i < msg.parts.length; i++) { michael@0: if (msg.parts[i].headers["content-type"] && michael@0: msg.parts[i].headers["content-type"].params) { michael@0: let name = msg.parts[i].headers["content-type"].params["name"]; michael@0: if (name && name.length > MMS.MMS_MAX_LENGTH_NAME_CONTENT_TYPE) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: translateHttpStatusToMmsStatus: function(httpStatus, cancelledReason, michael@0: defaultStatus) { michael@0: switch(httpStatus) { michael@0: case _HTTP_STATUS_USER_CANCELLED: michael@0: return cancelledReason; michael@0: case _HTTP_STATUS_RADIO_DISABLED: michael@0: return _MMS_ERROR_RADIO_DISABLED; michael@0: case _HTTP_STATUS_NO_SIM_CARD: michael@0: return _MMS_ERROR_NO_SIM_CARD; michael@0: case HTTP_STATUS_OK: michael@0: return MMS.MMS_PDU_ERROR_OK; michael@0: default: michael@0: return defaultStatus; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: return helper; michael@0: }); michael@0: michael@0: /** michael@0: * Send M-NotifyResp.ind back to MMSC. michael@0: * michael@0: * @param mmsConnection michael@0: * The MMS connection. michael@0: * @param transactionId michael@0: * X-Mms-Transaction-ID of the message. michael@0: * @param status michael@0: * X-Mms-Status of the response. michael@0: * @param reportAllowed michael@0: * X-Mms-Report-Allowed of the response. michael@0: * michael@0: * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.2 michael@0: */ michael@0: function NotifyResponseTransaction(mmsConnection, transactionId, status, michael@0: reportAllowed) { michael@0: this.mmsConnection = mmsConnection; michael@0: let headers = {}; michael@0: michael@0: // Mandatory fields michael@0: headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_NOTIFYRESP_IND; michael@0: headers["x-mms-transaction-id"] = transactionId; michael@0: headers["x-mms-mms-version"] = MMS.MMS_VERSION; michael@0: headers["x-mms-status"] = status; michael@0: // Optional fields michael@0: headers["x-mms-report-allowed"] = reportAllowed; michael@0: michael@0: this.istream = MMS.PduHelper.compose(null, {headers: headers}); michael@0: } michael@0: NotifyResponseTransaction.prototype = { michael@0: /** michael@0: * @param callback [optional] michael@0: * A callback function that takes one argument -- the http status. michael@0: */ michael@0: run: function(callback) { michael@0: let requestCallback; michael@0: if (callback) { michael@0: requestCallback = function(httpStatus, data) { michael@0: // `The MMS Client SHOULD ignore the associated HTTP POST response michael@0: // from the MMS Proxy-Relay.` ~ OMA-TS-MMS_CTR-V1_3-20110913-A michael@0: // section 8.2.2 "Notification". michael@0: callback(httpStatus); michael@0: }; michael@0: } michael@0: gMmsTransactionHelper.sendRequest(this.mmsConnection, michael@0: "POST", michael@0: null, michael@0: this.istream, michael@0: requestCallback); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * CancellableTransaction - base class inherited by [Send|Retrieve]Transaction. michael@0: * We can call |cancelRunning(reason)| to cancel the on-going transaction. michael@0: * @param cancellableId michael@0: * An ID used to keep track of if an message is deleted from DB. michael@0: * @param serviceId michael@0: * An ID used to keep track of if the primary SIM service is changed. michael@0: */ michael@0: function CancellableTransaction(cancellableId, serviceId) { michael@0: this.cancellableId = cancellableId; michael@0: this.serviceId = serviceId; michael@0: this.isCancelled = false; michael@0: } michael@0: CancellableTransaction.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), michael@0: michael@0: // The timer for retrying sending or retrieving process. michael@0: timer: null, michael@0: michael@0: // Keep a reference to the callback when calling michael@0: // |[Send|Retrieve]Transaction.run(callback)|. michael@0: runCallback: null, michael@0: michael@0: isObserversAdded: false, michael@0: michael@0: cancelledReason: _MMS_ERROR_USER_CANCELLED_NO_REASON, michael@0: michael@0: registerRunCallback: function(callback) { michael@0: if (!this.isObserversAdded) { michael@0: Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: Services.obs.addObserver(this, kMobileMessageDeletedObserverTopic, false); michael@0: Services.prefs.addObserver(kPrefRilRadioDisabled, this, false); michael@0: Services.prefs.addObserver(kPrefDefaultServiceId, this, false); michael@0: this.isObserversAdded = true; michael@0: } michael@0: michael@0: this.runCallback = callback; michael@0: this.isCancelled = false; michael@0: }, michael@0: michael@0: removeObservers: function() { michael@0: if (this.isObserversAdded) { michael@0: Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: Services.obs.removeObserver(this, kMobileMessageDeletedObserverTopic); michael@0: Services.prefs.removeObserver(kPrefRilRadioDisabled, this); michael@0: Services.prefs.removeObserver(kPrefDefaultServiceId, this); michael@0: this.isObserversAdded = false; michael@0: } michael@0: }, michael@0: michael@0: runCallbackIfValid: function(mmsStatus, msg) { michael@0: this.removeObservers(); michael@0: michael@0: if (this.runCallback) { michael@0: this.runCallback(mmsStatus, msg); michael@0: this.runCallback = null; michael@0: } michael@0: }, michael@0: michael@0: // Keep a reference to the cancellable when calling michael@0: // |gMmsTransactionHelper.sendRequest(...)|. michael@0: cancellable: null, michael@0: michael@0: cancelRunning: function(reason) { michael@0: this.isCancelled = true; michael@0: this.cancelledReason = reason; michael@0: michael@0: if (this.timer) { michael@0: // The sending or retrieving process is waiting for the next retry. michael@0: // What we only need to do is to cancel the timer. michael@0: this.timer.cancel(); michael@0: this.timer = null; michael@0: this.runCallbackIfValid(reason, null); michael@0: return; michael@0: } michael@0: michael@0: if (this.cancellable) { michael@0: // The sending or retrieving process is still running. We attempt to michael@0: // abort the HTTP request. michael@0: this.cancellable.cancel(); michael@0: this.cancellable = null; michael@0: } michael@0: }, michael@0: michael@0: // nsIObserver michael@0: michael@0: observe: function(subject, topic, data) { michael@0: switch (topic) { michael@0: case NS_XPCOM_SHUTDOWN_OBSERVER_ID: { michael@0: this.cancelRunning(_MMS_ERROR_SHUTDOWN); michael@0: break; michael@0: } michael@0: case kMobileMessageDeletedObserverTopic: { michael@0: data = JSON.parse(data); michael@0: if (data.id != this.cancellableId) { michael@0: return; michael@0: } michael@0: michael@0: this.cancelRunning(_MMS_ERROR_MESSAGE_DELETED); michael@0: break; michael@0: } michael@0: case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: { michael@0: if (data == kPrefRilRadioDisabled) { michael@0: if (getRadioDisabledState()) { michael@0: this.cancelRunning(_MMS_ERROR_RADIO_DISABLED); michael@0: } michael@0: } else if (data === kPrefDefaultServiceId && michael@0: this.serviceId != getDefaultServiceId()) { michael@0: this.cancelRunning(_MMS_ERROR_SIM_CARD_CHANGED); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Class for retrieving message from MMSC, which inherits CancellableTransaction. michael@0: * michael@0: * @param contentLocation michael@0: * X-Mms-Content-Location of the message. michael@0: */ michael@0: function RetrieveTransaction(mmsConnection, cancellableId, contentLocation) { michael@0: this.mmsConnection = mmsConnection; michael@0: michael@0: // Call |CancellableTransaction| constructor. michael@0: CancellableTransaction.call(this, cancellableId, mmsConnection.serviceId); michael@0: michael@0: this.contentLocation = contentLocation; michael@0: } michael@0: RetrieveTransaction.prototype = Object.create(CancellableTransaction.prototype, { michael@0: /** michael@0: * @param callback [optional] michael@0: * A callback function that takes two arguments: one for X-Mms-Status, michael@0: * the other for the parsed M-Retrieve.conf message. michael@0: */ michael@0: run: { michael@0: value: function(callback) { michael@0: this.registerRunCallback(callback); michael@0: michael@0: this.retryCount = 0; michael@0: let retryCallback = (function(mmsStatus, msg) { michael@0: if (MMS.MMS_PDU_STATUS_DEFERRED == mmsStatus && michael@0: this.retryCount < PREF_RETRIEVAL_RETRY_COUNT) { michael@0: let time = PREF_RETRIEVAL_RETRY_INTERVALS[this.retryCount]; michael@0: if (DEBUG) debug("Fail to retrieve. Will retry after: " + time); michael@0: michael@0: if (this.timer == null) { michael@0: this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: } michael@0: michael@0: this.timer.initWithCallback(this.retrieve.bind(this, retryCallback), michael@0: time, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: this.retryCount++; michael@0: return; michael@0: } michael@0: this.runCallbackIfValid(mmsStatus, msg); michael@0: }).bind(this); michael@0: michael@0: this.retrieve(retryCallback); michael@0: }, michael@0: enumerable: true, michael@0: configurable: true, michael@0: writable: true michael@0: }, michael@0: michael@0: /** michael@0: * @param callback michael@0: * A callback function that takes two arguments: one for X-Mms-Status, michael@0: * the other for the parsed M-Retrieve.conf message. michael@0: */ michael@0: retrieve: { michael@0: value: function(callback) { michael@0: this.timer = null; michael@0: michael@0: this.cancellable = michael@0: gMmsTransactionHelper.sendRequest(this.mmsConnection, michael@0: "GET", this.contentLocation, null, michael@0: (function(httpStatus, data) { michael@0: let mmsStatus = gMmsTransactionHelper michael@0: .translateHttpStatusToMmsStatus(httpStatus, michael@0: this.cancelledReason, michael@0: MMS.MMS_PDU_STATUS_DEFERRED); michael@0: if (mmsStatus != MMS.MMS_PDU_ERROR_OK) { michael@0: callback(mmsStatus, null); michael@0: return; michael@0: } michael@0: if (!data) { michael@0: callback(MMS.MMS_PDU_STATUS_DEFERRED, null); michael@0: return; michael@0: } michael@0: michael@0: let retrieved = MMS.PduHelper.parse(data, null); michael@0: if (!retrieved || (retrieved.type != MMS.MMS_PDU_TYPE_RETRIEVE_CONF)) { michael@0: callback(MMS.MMS_PDU_STATUS_UNRECOGNISED, null); michael@0: return; michael@0: } michael@0: michael@0: // Fix default header field values. michael@0: if (retrieved.headers["x-mms-delivery-report"] == null) { michael@0: retrieved.headers["x-mms-delivery-report"] = false; michael@0: } michael@0: michael@0: let retrieveStatus = retrieved.headers["x-mms-retrieve-status"]; michael@0: if ((retrieveStatus != null) && michael@0: (retrieveStatus != MMS.MMS_PDU_ERROR_OK)) { michael@0: callback(MMS.translatePduErrorToStatus(retrieveStatus), retrieved); michael@0: return; michael@0: } michael@0: michael@0: callback(MMS.MMS_PDU_STATUS_RETRIEVED, retrieved); michael@0: }).bind(this)); michael@0: }, michael@0: enumerable: true, michael@0: configurable: true, michael@0: writable: true michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * SendTransaction. michael@0: * Class for sending M-Send.req to MMSC, which inherits CancellableTransaction. michael@0: * @throws Error("Check max values parameters fail.") michael@0: */ michael@0: function SendTransaction(mmsConnection, cancellableId, msg, requestDeliveryReport) { michael@0: this.mmsConnection = mmsConnection; michael@0: michael@0: // Call |CancellableTransaction| constructor. michael@0: CancellableTransaction.call(this, cancellableId, mmsConnection.serviceId); michael@0: michael@0: msg.headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_SEND_REQ; michael@0: if (!msg.headers["x-mms-transaction-id"]) { michael@0: // Create an unique transaction id michael@0: let tid = gUUIDGenerator.generateUUID().toString(); michael@0: msg.headers["x-mms-transaction-id"] = tid; michael@0: } michael@0: msg.headers["x-mms-mms-version"] = MMS.MMS_VERSION; michael@0: michael@0: // Let MMS Proxy Relay insert from address automatically for us michael@0: msg.headers["from"] = null; michael@0: michael@0: msg.headers["date"] = new Date(); michael@0: msg.headers["x-mms-message-class"] = "personal"; michael@0: msg.headers["x-mms-expiry"] = 7 * 24 * 60 * 60; michael@0: msg.headers["x-mms-priority"] = 129; michael@0: try { michael@0: msg.headers["x-mms-read-report"] = michael@0: Services.prefs.getBoolPref("dom.mms.requestReadReport"); michael@0: } catch (e) { michael@0: msg.headers["x-mms-read-report"] = true; michael@0: } michael@0: msg.headers["x-mms-delivery-report"] = requestDeliveryReport; michael@0: michael@0: if (!gMmsTransactionHelper.checkMaxValuesParameters(msg)) { michael@0: //We should notify end user that the header format is wrong. michael@0: if (DEBUG) debug("Check max values parameters fail."); michael@0: throw new Error("Check max values parameters fail."); michael@0: } michael@0: michael@0: if (msg.parts) { michael@0: let contentType = { michael@0: params: { michael@0: // `The type parameter must be specified and its value is the MIME michael@0: // media type of the "root" body part.` ~ RFC 2387 clause 3.1 michael@0: type: msg.parts[0].headers["content-type"].media, michael@0: }, michael@0: }; michael@0: michael@0: // `The Content-Type in M-Send.req and M-Retrieve.conf SHALL be michael@0: // application/vnd.wap.multipart.mixed when there is no presentation, and michael@0: // application/vnd.wap.multipart.related SHALL be used when there is SMIL michael@0: // presentation available.` ~ OMA-TS-MMS_CONF-V1_3-20110913-A clause 10.2.1 michael@0: if (contentType.params.type === "application/smil") { michael@0: contentType.media = "application/vnd.wap.multipart.related"; michael@0: michael@0: // `The start parameter, if given, is the content-ID of the compound michael@0: // object's "root".` ~ RFC 2387 clause 3.2 michael@0: contentType.params.start = msg.parts[0].headers["content-id"]; michael@0: } else { michael@0: contentType.media = "application/vnd.wap.multipart.mixed"; michael@0: } michael@0: michael@0: // Assign to Content-Type michael@0: msg.headers["content-type"] = contentType; michael@0: } michael@0: michael@0: if (DEBUG) debug("msg: " + JSON.stringify(msg)); michael@0: michael@0: this.msg = msg; michael@0: } michael@0: SendTransaction.prototype = Object.create(CancellableTransaction.prototype, { michael@0: istreamComposed: { michael@0: value: false, michael@0: enumerable: true, michael@0: configurable: true, michael@0: writable: true michael@0: }, michael@0: michael@0: /** michael@0: * @param parts michael@0: * 'parts' property of a parsed MMS message. michael@0: * @param callback [optional] michael@0: * A callback function that takes zero argument. michael@0: */ michael@0: loadBlobs: { michael@0: value: function(parts, callback) { michael@0: let callbackIfValid = function callbackIfValid() { michael@0: if (DEBUG) debug("All parts loaded: " + JSON.stringify(parts)); michael@0: if (callback) { michael@0: callback(); michael@0: } michael@0: } michael@0: michael@0: if (!parts || !parts.length) { michael@0: callbackIfValid(); michael@0: return; michael@0: } michael@0: michael@0: let numPartsToLoad = parts.length; michael@0: for each (let part in parts) { michael@0: if (!(part.content instanceof Ci.nsIDOMBlob)) { michael@0: numPartsToLoad--; michael@0: if (!numPartsToLoad) { michael@0: callbackIfValid(); michael@0: return; michael@0: } michael@0: continue; michael@0: } michael@0: let fileReader = Cc["@mozilla.org/files/filereader;1"] michael@0: .createInstance(Ci.nsIDOMFileReader); michael@0: fileReader.addEventListener("loadend", michael@0: (function onloadend(part, event) { michael@0: let arrayBuffer = event.target.result; michael@0: part.content = new Uint8Array(arrayBuffer); michael@0: numPartsToLoad--; michael@0: if (!numPartsToLoad) { michael@0: callbackIfValid(); michael@0: } michael@0: }).bind(null, part)); michael@0: fileReader.readAsArrayBuffer(part.content); michael@0: }; michael@0: }, michael@0: enumerable: true, michael@0: configurable: true, michael@0: writable: true michael@0: }, michael@0: michael@0: /** michael@0: * @param callback [optional] michael@0: * A callback function that takes two arguments: one for michael@0: * X-Mms-Response-Status, the other for the parsed M-Send.conf message. michael@0: */ michael@0: run: { michael@0: value: function(callback) { michael@0: this.registerRunCallback(callback); michael@0: michael@0: if (!this.istreamComposed) { michael@0: this.loadBlobs(this.msg.parts, (function() { michael@0: this.istream = MMS.PduHelper.compose(null, this.msg); michael@0: this.istreamSize = this.istream.available(); michael@0: this.istreamComposed = true; michael@0: if (this.isCancelled) { michael@0: this.runCallbackIfValid(_MMS_ERROR_MESSAGE_DELETED, null); michael@0: } else { michael@0: this.run(callback); michael@0: } michael@0: }).bind(this)); michael@0: return; michael@0: } michael@0: michael@0: if (!this.istream) { michael@0: this.runCallbackIfValid(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null); michael@0: return; michael@0: } michael@0: michael@0: this.retryCount = 0; michael@0: let retryCallback = (function(mmsStatus, msg) { michael@0: if ((MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE == mmsStatus || michael@0: MMS.MMS_PDU_ERROR_PERMANENT_FAILURE == mmsStatus) && michael@0: this.retryCount < PREF_SEND_RETRY_COUNT) { michael@0: if (DEBUG) { michael@0: debug("Fail to send. Will retry after: " + PREF_SEND_RETRY_INTERVAL); michael@0: } michael@0: michael@0: if (this.timer == null) { michael@0: this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: } michael@0: michael@0: this.retryCount++; michael@0: michael@0: // the input stream may be read in the previous failure request so michael@0: // we have to re-compose it. michael@0: if (this.istreamSize == null || michael@0: this.istreamSize != this.istream.available()) { michael@0: this.istream = MMS.PduHelper.compose(null, this.msg); michael@0: } michael@0: michael@0: this.timer.initWithCallback(this.send.bind(this, retryCallback), michael@0: PREF_SEND_RETRY_INTERVAL, michael@0: Ci.nsITimer.TYPE_ONE_SHOT); michael@0: return; michael@0: } michael@0: michael@0: this.runCallbackIfValid(mmsStatus, msg); michael@0: }).bind(this); michael@0: michael@0: // This is the entry point to start sending. michael@0: this.send(retryCallback); michael@0: }, michael@0: enumerable: true, michael@0: configurable: true, michael@0: writable: true michael@0: }, michael@0: michael@0: /** michael@0: * @param callback michael@0: * A callback function that takes two arguments: one for michael@0: * X-Mms-Response-Status, the other for the parsed M-Send.conf message. michael@0: */ michael@0: send: { michael@0: value: function(callback) { michael@0: this.timer = null; michael@0: michael@0: this.cancellable = michael@0: gMmsTransactionHelper.sendRequest(this.mmsConnection, michael@0: "POST", michael@0: null, michael@0: this.istream, michael@0: (function(httpStatus, data) { michael@0: let mmsStatus = gMmsTransactionHelper. michael@0: translateHttpStatusToMmsStatus( michael@0: httpStatus, michael@0: this.cancelledReason, michael@0: MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE); michael@0: if (httpStatus != HTTP_STATUS_OK) { michael@0: callback(mmsStatus, null); michael@0: return; michael@0: } michael@0: michael@0: if (!data) { michael@0: callback(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null); michael@0: return; michael@0: } michael@0: michael@0: let response = MMS.PduHelper.parse(data, null); michael@0: if (!response || (response.type != MMS.MMS_PDU_TYPE_SEND_CONF)) { michael@0: callback(MMS.MMS_PDU_RESPONSE_ERROR_UNSUPPORTED_MESSAGE, null); michael@0: return; michael@0: } michael@0: michael@0: let responseStatus = response.headers["x-mms-response-status"]; michael@0: callback(responseStatus, response); michael@0: }).bind(this)); michael@0: }, michael@0: enumerable: true, michael@0: configurable: true, michael@0: writable: true michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Send M-acknowledge.ind back to MMSC. michael@0: * michael@0: * @param mmsConnection michael@0: * The MMS connection. michael@0: * @param transactionId michael@0: * X-Mms-Transaction-ID of the message. michael@0: * @param reportAllowed michael@0: * X-Mms-Report-Allowed of the response. michael@0: * michael@0: * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.4 michael@0: */ michael@0: function AcknowledgeTransaction(mmsConnection, transactionId, reportAllowed) { michael@0: this.mmsConnection = mmsConnection; michael@0: let headers = {}; michael@0: michael@0: // Mandatory fields michael@0: headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_ACKNOWLEDGE_IND; michael@0: headers["x-mms-transaction-id"] = transactionId; michael@0: headers["x-mms-mms-version"] = MMS.MMS_VERSION; michael@0: // Optional fields michael@0: headers["x-mms-report-allowed"] = reportAllowed; michael@0: michael@0: this.istream = MMS.PduHelper.compose(null, {headers: headers}); michael@0: } michael@0: AcknowledgeTransaction.prototype = { michael@0: /** michael@0: * @param callback [optional] michael@0: * A callback function that takes one argument -- the http status. michael@0: */ michael@0: run: function(callback) { michael@0: let requestCallback; michael@0: if (callback) { michael@0: requestCallback = function(httpStatus, data) { michael@0: // `The MMS Client SHOULD ignore the associated HTTP POST response michael@0: // from the MMS Proxy-Relay.` ~ OMA-TS-MMS_CTR-V1_3-20110913-A michael@0: // section 8.2.3 "Retrieving an MM". michael@0: callback(httpStatus); michael@0: }; michael@0: } michael@0: gMmsTransactionHelper.sendRequest(this.mmsConnection, michael@0: "POST", michael@0: null, michael@0: this.istream, michael@0: requestCallback); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Return M-Read-Rec.ind back to MMSC michael@0: * michael@0: * @param messageID michael@0: * Message-ID of the message. michael@0: * @param toAddress michael@0: * The address of the recipient of the Read Report, i.e. the originator michael@0: * of the original multimedia message. michael@0: * michael@0: * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.7.2 michael@0: */ michael@0: function ReadRecTransaction(mmsConnection, messageID, toAddress) { michael@0: this.mmsConnection = mmsConnection; michael@0: let headers = {}; michael@0: michael@0: // Mandatory fields michael@0: headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_READ_REC_IND; michael@0: headers["x-mms-mms-version"] = MMS.MMS_VERSION; michael@0: headers["message-id"] = messageID; michael@0: let type = MMS.Address.resolveType(toAddress); michael@0: let to = {address: toAddress, michael@0: type: type} michael@0: headers["to"] = to; michael@0: headers["from"] = null; michael@0: headers["x-mms-read-status"] = MMS.MMS_PDU_READ_STATUS_READ; michael@0: michael@0: this.istream = MMS.PduHelper.compose(null, {headers: headers}); michael@0: if (!this.istream) { michael@0: throw Cr.NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: ReadRecTransaction.prototype = { michael@0: run: function() { michael@0: gMmsTransactionHelper.sendRequest(this.mmsConnection, michael@0: "POST", michael@0: null, michael@0: this.istream, michael@0: null); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * MmsService michael@0: */ michael@0: function MmsService() { michael@0: if (DEBUG) { michael@0: let macro = (MMS.MMS_VERSION >> 4) & 0x0f; michael@0: let minor = MMS.MMS_VERSION & 0x0f; michael@0: debug("Running protocol version: " + macro + "." + minor); michael@0: } michael@0: michael@0: Services.prefs.addObserver(kPrefDefaultServiceId, this, false); michael@0: this.mmsDefaultServiceId = getDefaultServiceId(); michael@0: michael@0: // TODO: bug 810084 - support application identifier michael@0: } michael@0: MmsService.prototype = { michael@0: michael@0: classID: RIL_MMSSERVICE_CID, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIMmsService, michael@0: Ci.nsIWapPushApplication, michael@0: Ci.nsIObserver]), michael@0: /* michael@0: * Whether or not should we enable X-Mms-Report-Allowed in M-NotifyResp.ind michael@0: * and M-Acknowledge.ind PDU. michael@0: */ michael@0: confSendDeliveryReport: CONFIG_SEND_REPORT_DEFAULT_YES, michael@0: michael@0: /** michael@0: * Calculate Whether or not should we enable X-Mms-Report-Allowed. michael@0: * michael@0: * @param config michael@0: * Current configure value. michael@0: * @param wish michael@0: * Sender wish. Could be undefined, false, or true. michael@0: */ michael@0: getReportAllowed: function(config, wish) { michael@0: if ((config == CONFIG_SEND_REPORT_DEFAULT_NO) michael@0: || (config == CONFIG_SEND_REPORT_DEFAULT_YES)) { michael@0: if (wish != null) { michael@0: config += (wish ? 1 : -1); michael@0: } michael@0: } michael@0: return config >= CONFIG_SEND_REPORT_DEFAULT_YES; michael@0: }, michael@0: michael@0: /** michael@0: * Convert intermediate message to indexedDB savable object. michael@0: * michael@0: * @param mmsConnection michael@0: * The MMS connection. michael@0: * @param retrievalMode michael@0: * Retrieval mode for MMS receiving setting. michael@0: * @param intermediate michael@0: * Intermediate MMS message parsed from PDU. michael@0: */ michael@0: convertIntermediateToSavable: function(mmsConnection, intermediate, michael@0: retrievalMode) { michael@0: intermediate.type = "mms"; michael@0: intermediate.delivery = DELIVERY_NOT_DOWNLOADED; michael@0: michael@0: let deliveryStatus; michael@0: switch (retrievalMode) { michael@0: case RETRIEVAL_MODE_MANUAL: michael@0: deliveryStatus = DELIVERY_STATUS_MANUAL; michael@0: break; michael@0: case RETRIEVAL_MODE_NEVER: michael@0: deliveryStatus = DELIVERY_STATUS_REJECTED; michael@0: break; michael@0: case RETRIEVAL_MODE_AUTOMATIC: michael@0: deliveryStatus = DELIVERY_STATUS_PENDING; michael@0: break; michael@0: case RETRIEVAL_MODE_AUTOMATIC_HOME: michael@0: if (mmsConnection.isVoiceRoaming()) { michael@0: deliveryStatus = DELIVERY_STATUS_MANUAL; michael@0: } else { michael@0: deliveryStatus = DELIVERY_STATUS_PENDING; michael@0: } michael@0: break; michael@0: default: michael@0: deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE; michael@0: break; michael@0: } michael@0: // |intermediate.deliveryStatus| will be deleted after being stored in db. michael@0: intermediate.deliveryStatus = deliveryStatus; michael@0: michael@0: intermediate.timestamp = Date.now(); michael@0: intermediate.receivers = []; michael@0: intermediate.phoneNumber = mmsConnection.getPhoneNumber(); michael@0: intermediate.iccId = mmsConnection.getIccId(); michael@0: return intermediate; michael@0: }, michael@0: michael@0: /** michael@0: * Merge the retrieval confirmation into the savable message. michael@0: * michael@0: * @param mmsConnection michael@0: * The MMS connection. michael@0: * @param intermediate michael@0: * Intermediate MMS message parsed from PDU, which carries michael@0: * the retrieval confirmation. michael@0: * @param savable michael@0: * The indexedDB savable MMS message, which is going to be michael@0: * merged with the extra retrieval confirmation. michael@0: */ michael@0: mergeRetrievalConfirmation: function(mmsConnection, intermediate, savable) { michael@0: // Prepare timestamp/sentTimestamp. michael@0: savable.timestamp = Date.now(); michael@0: savable.sentTimestamp = intermediate.headers["date"].getTime(); michael@0: michael@0: savable.receivers = []; michael@0: // We don't have Bcc in recevied MMS message. michael@0: for each (let type in ["cc", "to"]) { michael@0: if (intermediate.headers[type]) { michael@0: if (intermediate.headers[type] instanceof Array) { michael@0: for (let index in intermediate.headers[type]) { michael@0: savable.receivers.push(intermediate.headers[type][index].address); michael@0: } michael@0: } else { michael@0: savable.receivers.push(intermediate.headers[type].address); michael@0: } michael@0: } michael@0: } michael@0: michael@0: savable.delivery = DELIVERY_RECEIVED; michael@0: // |savable.deliveryStatus| will be deleted after being stored in db. michael@0: savable.deliveryStatus = DELIVERY_STATUS_SUCCESS; michael@0: for (let field in intermediate.headers) { michael@0: savable.headers[field] = intermediate.headers[field]; michael@0: } michael@0: if (intermediate.parts) { michael@0: savable.parts = intermediate.parts; michael@0: } michael@0: if (intermediate.content) { michael@0: savable.content = intermediate.content; michael@0: } michael@0: return savable; michael@0: }, michael@0: michael@0: /** michael@0: * @param aMmsConnection michael@0: * The MMS connection. michael@0: * @param aContentLocation michael@0: * X-Mms-Content-Location of the message. michael@0: * @param aCallback [optional] michael@0: * A callback function that takes two arguments: one for X-Mms-Status, michael@0: * the other parsed MMS message. michael@0: * @param aDomMessage michael@0: * The nsIDOMMozMmsMessage object. michael@0: */ michael@0: retrieveMessage: function(aMmsConnection, aContentLocation, aCallback, michael@0: aDomMessage) { michael@0: // Notifying observers an MMS message is retrieving. michael@0: Services.obs.notifyObservers(aDomMessage, kSmsRetrievingObserverTopic, null); michael@0: michael@0: let transaction = new RetrieveTransaction(aMmsConnection, michael@0: aDomMessage.id, michael@0: aContentLocation); michael@0: transaction.run(aCallback); michael@0: }, michael@0: michael@0: /** michael@0: * A helper to broadcast the system message to launch registered apps michael@0: * like Costcontrol, Notification and Message app... etc. michael@0: * michael@0: * @param aName michael@0: * The system message name. michael@0: * @param aDomMessage michael@0: * The nsIDOMMozMmsMessage object. michael@0: */ michael@0: broadcastMmsSystemMessage: function(aName, aDomMessage) { michael@0: if (DEBUG) debug("Broadcasting the MMS system message: " + aName); michael@0: michael@0: // Sadly we cannot directly broadcast the aDomMessage object michael@0: // because the system message mechamism will rewrap the object michael@0: // based on the content window, which needs to know the properties. michael@0: gSystemMessenger.broadcastMessage(aName, { michael@0: iccId: aDomMessage.iccId, michael@0: type: aDomMessage.type, michael@0: id: aDomMessage.id, michael@0: threadId: aDomMessage.threadId, michael@0: delivery: aDomMessage.delivery, michael@0: deliveryInfo: aDomMessage.deliveryInfo, michael@0: sender: aDomMessage.sender, michael@0: receivers: aDomMessage.receivers, michael@0: timestamp: aDomMessage.timestamp, michael@0: sentTimestamp: aDomMessage.sentTimestamp, michael@0: read: aDomMessage.read, michael@0: subject: aDomMessage.subject, michael@0: smil: aDomMessage.smil, michael@0: attachments: aDomMessage.attachments, michael@0: expiryDate: aDomMessage.expiryDate, michael@0: readReportRequested: aDomMessage.readReportRequested michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * A helper function to broadcast system message and notify observers that michael@0: * an MMS is sent. michael@0: * michael@0: * @params aDomMessage michael@0: * The nsIDOMMozMmsMessage object. michael@0: */ michael@0: broadcastSentMessageEvent: function(aDomMessage) { michael@0: // Broadcasting a 'sms-sent' system message to open apps. michael@0: this.broadcastMmsSystemMessage(kSmsSentObserverTopic, aDomMessage); michael@0: michael@0: // Notifying observers an MMS message is sent. michael@0: Services.obs.notifyObservers(aDomMessage, kSmsSentObserverTopic, null); michael@0: }, michael@0: michael@0: /** michael@0: * A helper function to broadcast system message and notify observers that michael@0: * an MMS is received. michael@0: * michael@0: * @params aDomMessage michael@0: * The nsIDOMMozMmsMessage object. michael@0: */ michael@0: broadcastReceivedMessageEvent :function broadcastReceivedMessageEvent(aDomMessage) { michael@0: // Broadcasting a 'sms-received' system message to open apps. michael@0: this.broadcastMmsSystemMessage(kSmsReceivedObserverTopic, aDomMessage); michael@0: michael@0: // Notifying observers an MMS message is received. michael@0: Services.obs.notifyObservers(aDomMessage, kSmsReceivedObserverTopic, null); michael@0: }, michael@0: michael@0: /** michael@0: * Callback for retrieveMessage. michael@0: */ michael@0: retrieveMessageCallback: function(mmsConnection, wish, savableMessage, michael@0: mmsStatus, retrievedMessage) { michael@0: if (DEBUG) debug("retrievedMessage = " + JSON.stringify(retrievedMessage)); michael@0: michael@0: let transactionId = savableMessage.headers["x-mms-transaction-id"]; michael@0: michael@0: // The absence of the field does not indicate any default michael@0: // value. So we go check the same field in the retrieved michael@0: // message instead. michael@0: if (wish == null && retrievedMessage) { michael@0: wish = retrievedMessage.headers["x-mms-delivery-report"]; michael@0: } michael@0: michael@0: let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport, michael@0: wish); michael@0: // If the mmsStatus isn't MMS_PDU_STATUS_RETRIEVED after retrieving, michael@0: // something must be wrong with MMSC, so stop updating the DB record. michael@0: // We could send a message to content to notify the user the MMS michael@0: // retrieving failed. The end user has to retrieve the MMS again. michael@0: if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) { michael@0: if (mmsStatus != _MMS_ERROR_RADIO_DISABLED && michael@0: mmsStatus != _MMS_ERROR_NO_SIM_CARD && michael@0: mmsStatus != _MMS_ERROR_SIM_CARD_CHANGED) { michael@0: let transaction = new NotifyResponseTransaction(mmsConnection, michael@0: transactionId, michael@0: mmsStatus, michael@0: reportAllowed); michael@0: transaction.run(); michael@0: } michael@0: // Retrieved fail after retry, so we update the delivery status in DB and michael@0: // notify this domMessage that error happen. michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryByMessageId(savableMessage.id, michael@0: null, michael@0: null, michael@0: DELIVERY_STATUS_ERROR, michael@0: null, michael@0: (function(rv, domMessage) { michael@0: this.broadcastReceivedMessageEvent(domMessage); michael@0: }).bind(this)); michael@0: return; michael@0: } michael@0: michael@0: savableMessage = this.mergeRetrievalConfirmation(mmsConnection, michael@0: retrievedMessage, michael@0: savableMessage); michael@0: gMobileMessageDatabaseService.saveReceivedMessage(savableMessage, michael@0: (function(rv, domMessage) { michael@0: let success = Components.isSuccessCode(rv); michael@0: michael@0: // Cite 6.2.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A: michael@0: // The M-NotifyResp.ind response PDU SHALL provide a message retrieval michael@0: // status code. The status ‘retrieved’ SHALL be used only if the MMS michael@0: // Client has successfully retrieved the MM prior to sending the michael@0: // NotifyResp.ind response PDU. michael@0: let transaction = michael@0: new NotifyResponseTransaction(mmsConnection, michael@0: transactionId, michael@0: success ? MMS.MMS_PDU_STATUS_RETRIEVED michael@0: : MMS.MMS_PDU_STATUS_DEFERRED, michael@0: reportAllowed); michael@0: transaction.run(); michael@0: michael@0: if (!success) { michael@0: // At this point we could send a message to content to notify the user michael@0: // that storing an incoming MMS failed, most likely due to a full disk. michael@0: // The end user has to retrieve the MMS again. michael@0: if (DEBUG) debug("Could not store MMS , error code " + rv); michael@0: return; michael@0: } michael@0: michael@0: this.broadcastReceivedMessageEvent(domMessage); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Callback for saveReceivedMessage. michael@0: */ michael@0: saveReceivedMessageCallback: function(mmsConnection, retrievalMode, michael@0: savableMessage, rv, domMessage) { michael@0: let success = Components.isSuccessCode(rv); michael@0: if (!success) { michael@0: // At this point we could send a message to content to notify the michael@0: // user that storing an incoming MMS notification indication failed, michael@0: // ost likely due to a full disk. michael@0: if (DEBUG) debug("Could not store MMS " + JSON.stringify(savableMessage) + michael@0: ", error code " + rv); michael@0: // Because MMSC will resend the notification indication once we don't michael@0: // response the notification. Hope the end user will clean some space michael@0: // for the resent notification indication. michael@0: return; michael@0: } michael@0: michael@0: // For X-Mms-Report-Allowed and X-Mms-Transaction-Id michael@0: let wish = savableMessage.headers["x-mms-delivery-report"]; michael@0: let transactionId = savableMessage.headers["x-mms-transaction-id"]; michael@0: michael@0: this.broadcastReceivedMessageEvent(domMessage); michael@0: michael@0: // To avoid costing money, we only send notify response when it's under michael@0: // the "automatic" retrieval mode or it's not in the roaming environment. michael@0: if (retrievalMode !== RETRIEVAL_MODE_AUTOMATIC && michael@0: mmsConnection.isVoiceRoaming()) { michael@0: return; michael@0: } michael@0: michael@0: if (RETRIEVAL_MODE_MANUAL === retrievalMode || michael@0: RETRIEVAL_MODE_NEVER === retrievalMode) { michael@0: let mmsStatus = RETRIEVAL_MODE_NEVER === retrievalMode michael@0: ? MMS.MMS_PDU_STATUS_REJECTED michael@0: : MMS.MMS_PDU_STATUS_DEFERRED; michael@0: michael@0: // For X-Mms-Report-Allowed michael@0: let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport, michael@0: wish); michael@0: michael@0: let transaction = new NotifyResponseTransaction(mmsConnection, michael@0: transactionId, michael@0: mmsStatus, michael@0: reportAllowed); michael@0: transaction.run(); michael@0: return; michael@0: } michael@0: michael@0: let url = savableMessage.headers["x-mms-content-location"].uri; michael@0: michael@0: // For RETRIEVAL_MODE_AUTOMATIC or RETRIEVAL_MODE_AUTOMATIC_HOME but not michael@0: // roaming, proceed to retrieve MMS. michael@0: this.retrieveMessage(mmsConnection, michael@0: url, michael@0: this.retrieveMessageCallback.bind(this, michael@0: mmsConnection, michael@0: wish, michael@0: savableMessage), michael@0: domMessage); michael@0: }, michael@0: michael@0: /** michael@0: * Handle incoming M-Notification.ind PDU. michael@0: * michael@0: * @param serviceId michael@0: * The ID of the service for receiving the PDU data. michael@0: * @param notification michael@0: * The parsed MMS message object. michael@0: */ michael@0: handleNotificationIndication: function(serviceId, notification) { michael@0: let transactionId = notification.headers["x-mms-transaction-id"]; michael@0: gMobileMessageDatabaseService.getMessageRecordByTransactionId(transactionId, michael@0: (function(aRv, aMessageRecord) { michael@0: if (Components.isSuccessCode(aRv) && aMessageRecord) { michael@0: if (DEBUG) debug("We already got the NotificationIndication with transactionId = " michael@0: + transactionId + " before."); michael@0: return; michael@0: } michael@0: michael@0: let retrievalMode = RETRIEVAL_MODE_MANUAL; michael@0: try { michael@0: retrievalMode = Services.prefs.getCharPref(kPrefRetrievalMode); michael@0: } catch (e) {} michael@0: michael@0: // Under the "automatic"/"automatic-home" retrieval mode, we switch to michael@0: // the "manual" retrieval mode to download MMS for non-active SIM. michael@0: if ((retrievalMode == RETRIEVAL_MODE_AUTOMATIC || michael@0: retrievalMode == RETRIEVAL_MODE_AUTOMATIC_HOME) && michael@0: serviceId != this.mmsDefaultServiceId) { michael@0: if (DEBUG) { michael@0: debug("Switch to 'manual' mode to download MMS for non-active SIM: " + michael@0: "serviceId = " + serviceId + " doesn't equal to " + michael@0: "mmsDefaultServiceId = " + this.mmsDefaultServiceId); michael@0: } michael@0: michael@0: retrievalMode = RETRIEVAL_MODE_MANUAL; michael@0: } michael@0: michael@0: let mmsConnection = gMmsConnections.getConnByServiceId(serviceId); michael@0: michael@0: let savableMessage = this.convertIntermediateToSavable(mmsConnection, michael@0: notification, michael@0: retrievalMode); michael@0: michael@0: gMobileMessageDatabaseService michael@0: .saveReceivedMessage(savableMessage, michael@0: this.saveReceivedMessageCallback michael@0: .bind(this, michael@0: mmsConnection, michael@0: retrievalMode, michael@0: savableMessage)); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Handle incoming M-Delivery.ind PDU. michael@0: * michael@0: * @param aMsg michael@0: * The MMS message object. michael@0: */ michael@0: handleDeliveryIndication: function(aMsg) { michael@0: let headers = aMsg.headers; michael@0: let envelopeId = headers["message-id"]; michael@0: let address = headers.to.address; michael@0: let mmsStatus = headers["x-mms-status"]; michael@0: if (DEBUG) { michael@0: debug("Start updating the delivery status for envelopeId: " + envelopeId + michael@0: " address: " + address + " mmsStatus: " + mmsStatus); michael@0: } michael@0: michael@0: // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.3 "X-Mms-Status", michael@0: // in the M-Delivery.ind the X-Mms-Status could be MMS.MMS_PDU_STATUS_{ michael@0: // EXPIRED, RETRIEVED, REJECTED, DEFERRED, UNRECOGNISED, INDETERMINATE, michael@0: // FORWARDED, UNREACHABLE }. michael@0: let deliveryStatus; michael@0: switch (mmsStatus) { michael@0: case MMS.MMS_PDU_STATUS_RETRIEVED: michael@0: deliveryStatus = DELIVERY_STATUS_SUCCESS; michael@0: break; michael@0: case MMS.MMS_PDU_STATUS_EXPIRED: michael@0: case MMS.MMS_PDU_STATUS_REJECTED: michael@0: case MMS.MMS_PDU_STATUS_UNRECOGNISED: michael@0: case MMS.MMS_PDU_STATUS_UNREACHABLE: michael@0: deliveryStatus = DELIVERY_STATUS_REJECTED; michael@0: break; michael@0: case MMS.MMS_PDU_STATUS_DEFERRED: michael@0: deliveryStatus = DELIVERY_STATUS_PENDING; michael@0: break; michael@0: case MMS.MMS_PDU_STATUS_INDETERMINATE: michael@0: deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE; michael@0: break; michael@0: default: michael@0: if (DEBUG) debug("Cannot handle this MMS status. Returning."); michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) debug("Updating the delivery status to: " + deliveryStatus); michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryStatusByEnvelopeId(envelopeId, address, deliveryStatus, michael@0: (function(aRv, aDomMessage) { michael@0: if (DEBUG) debug("Marking the delivery status is done."); michael@0: // TODO bug 832140 handle !Components.isSuccessCode(aRv) michael@0: michael@0: let topic; michael@0: if (mmsStatus === MMS.MMS_PDU_STATUS_RETRIEVED) { michael@0: topic = kSmsDeliverySuccessObserverTopic; michael@0: michael@0: // Broadcasting a 'sms-delivery-success' system message to open apps. michael@0: this.broadcastMmsSystemMessage(topic, aDomMessage); michael@0: } else if (mmsStatus === MMS.MMS_PDU_STATUS_REJECTED) { michael@0: topic = kSmsDeliveryErrorObserverTopic; michael@0: } else { michael@0: if (DEBUG) debug("Needn't fire event for this MMS status. Returning."); michael@0: return; michael@0: } michael@0: michael@0: // Notifying observers the delivery status is updated. michael@0: Services.obs.notifyObservers(aDomMessage, topic, null); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Handle incoming M-Read-Orig.ind PDU. michael@0: * michael@0: * @param aIndication michael@0: * The MMS message object. michael@0: */ michael@0: handleReadOriginateIndication: function(aIndication) { michael@0: michael@0: let headers = aIndication.headers; michael@0: let envelopeId = headers["message-id"]; michael@0: let address = headers.from.address; michael@0: let mmsReadStatus = headers["x-mms-read-status"]; michael@0: if (DEBUG) { michael@0: debug("Start updating the read status for envelopeId: " + envelopeId + michael@0: ", address: " + address + ", mmsReadStatus: " + mmsReadStatus); michael@0: } michael@0: michael@0: // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.4 "X-Mms-Read-Status", michael@0: // in M-Read-Rec-Orig.ind the X-Mms-Read-Status could be michael@0: // MMS.MMS_READ_STATUS_{ READ, DELETED_WITHOUT_BEING_READ }. michael@0: let readStatus = mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ michael@0: ? MMS.DOM_READ_STATUS_SUCCESS michael@0: : MMS.DOM_READ_STATUS_ERROR; michael@0: if (DEBUG) debug("Updating the read status to: " + readStatus); michael@0: michael@0: gMobileMessageDatabaseService michael@0: .setMessageReadStatusByEnvelopeId(envelopeId, address, readStatus, michael@0: (function(aRv, aDomMessage) { michael@0: if (!Components.isSuccessCode(aRv)) { michael@0: // Notifying observers the read status is error. michael@0: Services.obs.notifyObservers(aDomMessage, kSmsReadSuccessObserverTopic, null); michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) debug("Marking the read status is done."); michael@0: let topic; michael@0: if (mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ) { michael@0: topic = kSmsReadSuccessObserverTopic; michael@0: michael@0: // Broadcasting a 'sms-read-success' system message to open apps. michael@0: this.broadcastMmsSystemMessage(topic, aDomMessage); michael@0: } else { michael@0: topic = kSmsReadErrorObserverTopic; michael@0: } michael@0: michael@0: // Notifying observers the read status is updated. michael@0: Services.obs.notifyObservers(aDomMessage, topic, null); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * A utility function to convert the MmsParameters dictionary object michael@0: * to a database-savable message. michael@0: * michael@0: * @param aMmsConnection michael@0: * The MMS connection. michael@0: * @param aParams michael@0: * The MmsParameters dictionay object. michael@0: * @param aMessage (output) michael@0: * The database-savable message. michael@0: * Return the error code by veryfying if the |aParams| is valid or not. michael@0: * michael@0: * Notes: michael@0: * michael@0: * OMA-TS-MMS-CONF-V1_3-20110913-A section 10.2.2 "Message Content Encoding": michael@0: * michael@0: * A name for multipart object SHALL be encoded using name-parameter for Content-Type michael@0: * header in WSP multipart headers. In decoding, name-parameter of Content-Type SHALL michael@0: * be used if available. If name-parameter of Content-Type is not available, filename michael@0: * parameter of Content-Disposition header SHALL be used if available. If neither michael@0: * name-parameter of Content-Type header nor filename parameter of Content-Disposition michael@0: * header is available, Content-Location header SHALL be used if available. michael@0: */ michael@0: createSavableFromParams: function(aMmsConnection, aParams, aMessage) { michael@0: if (DEBUG) debug("createSavableFromParams: aParams: " + JSON.stringify(aParams)); michael@0: michael@0: let isAddrValid = true; michael@0: let smil = aParams.smil; michael@0: michael@0: // |aMessage.headers| michael@0: let headers = aMessage["headers"] = {}; michael@0: let receivers = aParams.receivers; michael@0: if (receivers.length != 0) { michael@0: let headersTo = headers["to"] = []; michael@0: for (let i = 0; i < receivers.length; i++) { michael@0: let receiver = receivers[i]; michael@0: let type = MMS.Address.resolveType(receiver); michael@0: let address; michael@0: if (type == "PLMN") { michael@0: address = PhoneNumberUtils.normalize(receiver, false); michael@0: if (!PhoneNumberUtils.isPlainPhoneNumber(address)) { michael@0: isAddrValid = false; michael@0: } michael@0: if (DEBUG) debug("createSavableFromParams: normalize phone number " + michael@0: "from " + receiver + " to " + address); michael@0: } else { michael@0: address = receiver; michael@0: isAddrValid = false; michael@0: if (DEBUG) debug("Error! Address is invalid to send MMS: " + address); michael@0: } michael@0: headersTo.push({"address": address, "type": type}); michael@0: } michael@0: } michael@0: if (aParams.subject) { michael@0: headers["subject"] = aParams.subject; michael@0: } michael@0: michael@0: // |aMessage.parts| michael@0: let attachments = aParams.attachments; michael@0: if (attachments.length != 0 || smil) { michael@0: let parts = aMessage["parts"] = []; michael@0: michael@0: // Set the SMIL part if needed. michael@0: if (smil) { michael@0: let part = { michael@0: "headers": { michael@0: "content-type": { michael@0: "media": "application/smil", michael@0: "params": { michael@0: "name": "smil.xml", michael@0: "charset": { michael@0: "charset": "utf-8" michael@0: } michael@0: } michael@0: }, michael@0: "content-location": "smil.xml", michael@0: "content-id": "" michael@0: }, michael@0: "content": smil michael@0: }; michael@0: parts.push(part); michael@0: } michael@0: michael@0: // Set other parts for attachments if needed. michael@0: for (let i = 0; i < attachments.length; i++) { michael@0: let attachment = attachments[i]; michael@0: let content = attachment.content; michael@0: let location = attachment.location; michael@0: michael@0: let params = { michael@0: "name": location michael@0: }; michael@0: michael@0: if (content.type && content.type.indexOf("text/") == 0) { michael@0: params.charset = { michael@0: "charset": "utf-8" michael@0: }; michael@0: } michael@0: michael@0: let part = { michael@0: "headers": { michael@0: "content-type": { michael@0: "media": content.type, michael@0: "params": params michael@0: }, michael@0: "content-location": location, michael@0: "content-id": attachment.id michael@0: }, michael@0: "content": content michael@0: }; michael@0: parts.push(part); michael@0: } michael@0: } michael@0: michael@0: // The following attributes are needed for saving message into DB. michael@0: aMessage["type"] = "mms"; michael@0: aMessage["timestamp"] = Date.now(); michael@0: aMessage["receivers"] = receivers; michael@0: aMessage["sender"] = aMmsConnection.getPhoneNumber(); michael@0: aMessage["iccId"] = aMmsConnection.getIccId(); michael@0: try { michael@0: aMessage["deliveryStatusRequested"] = michael@0: Services.prefs.getBoolPref("dom.mms.requestStatusReport"); michael@0: } catch (e) { michael@0: aMessage["deliveryStatusRequested"] = false; michael@0: } michael@0: michael@0: if (DEBUG) debug("createSavableFromParams: aMessage: " + michael@0: JSON.stringify(aMessage)); michael@0: michael@0: return isAddrValid ? Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR michael@0: : Ci.nsIMobileMessageCallback.INVALID_ADDRESS_ERROR; michael@0: }, michael@0: michael@0: // nsIMmsService michael@0: michael@0: mmsDefaultServiceId: 0, michael@0: michael@0: send: function(aServiceId, aParams, aRequest) { michael@0: if (DEBUG) debug("send: aParams: " + JSON.stringify(aParams)); michael@0: michael@0: // Note that the following sanity checks for |aParams| should be consistent michael@0: // with the checks in SmsIPCService.GetSendMmsMessageRequestFromParams. michael@0: michael@0: // Check if |aParams| is valid. michael@0: if (aParams == null || typeof aParams != "object") { michael@0: if (DEBUG) debug("Error! 'aParams' should be a non-null object."); michael@0: throw Cr.NS_ERROR_INVALID_ARG; michael@0: return; michael@0: } michael@0: michael@0: // Check if |receivers| is valid. michael@0: if (!Array.isArray(aParams.receivers)) { michael@0: if (DEBUG) debug("Error! 'receivers' should be an array."); michael@0: throw Cr.NS_ERROR_INVALID_ARG; michael@0: return; michael@0: } michael@0: michael@0: // Check if |subject| is valid. michael@0: if (aParams.subject != null && typeof aParams.subject != "string") { michael@0: if (DEBUG) debug("Error! 'subject' should be a string if passed."); michael@0: throw Cr.NS_ERROR_INVALID_ARG; michael@0: return; michael@0: } michael@0: michael@0: // Check if |smil| is valid. michael@0: if (aParams.smil != null && typeof aParams.smil != "string") { michael@0: if (DEBUG) debug("Error! 'smil' should be a string if passed."); michael@0: throw Cr.NS_ERROR_INVALID_ARG; michael@0: return; michael@0: } michael@0: michael@0: // Check if |attachments| is valid. michael@0: if (!Array.isArray(aParams.attachments)) { michael@0: if (DEBUG) debug("Error! 'attachments' should be an array."); michael@0: throw Cr.NS_ERROR_INVALID_ARG; michael@0: return; michael@0: } michael@0: michael@0: let self = this; michael@0: michael@0: let sendTransactionCb = function sendTransactionCb(aDomMessage, michael@0: aErrorCode, michael@0: aEnvelopeId) { michael@0: if (DEBUG) { michael@0: debug("The returned status of sending transaction: " + michael@0: "aErrorCode: " + aErrorCode + " aEnvelopeId: " + aEnvelopeId); michael@0: } michael@0: michael@0: // If the messsage has been deleted (because the sending process is michael@0: // cancelled), we don't need to reset the its delievery state/status. michael@0: if (aErrorCode == Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR) { michael@0: aRequest.notifySendMessageFailed(aErrorCode); michael@0: Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null); michael@0: return; michael@0: } michael@0: michael@0: let isSentSuccess = (aErrorCode == Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR); michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryByMessageId(aDomMessage.id, michael@0: null, michael@0: isSentSuccess ? DELIVERY_SENT : DELIVERY_ERROR, michael@0: isSentSuccess ? null : DELIVERY_STATUS_ERROR, michael@0: aEnvelopeId, michael@0: function notifySetDeliveryResult(aRv, aDomMessage) { michael@0: if (DEBUG) debug("Marking the delivery state/staus is done. Notify sent or failed."); michael@0: // TODO bug 832140 handle !Components.isSuccessCode(aRv) michael@0: if (!isSentSuccess) { michael@0: if (DEBUG) debug("Sending MMS failed."); michael@0: aRequest.notifySendMessageFailed(aErrorCode); michael@0: Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null); michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) debug("Sending MMS succeeded."); michael@0: michael@0: // Notifying observers the MMS message is sent. michael@0: self.broadcastSentMessageEvent(aDomMessage); michael@0: michael@0: // Return the request after sending the MMS message successfully. michael@0: aRequest.notifyMessageSent(aDomMessage); michael@0: }); michael@0: }; michael@0: michael@0: let mmsConnection = gMmsConnections.getConnByServiceId(aServiceId); michael@0: michael@0: let savableMessage = {}; michael@0: let errorCode = this.createSavableFromParams(mmsConnection, aParams, michael@0: savableMessage); michael@0: gMobileMessageDatabaseService michael@0: .saveSendingMessage(savableMessage, michael@0: function notifySendingResult(aRv, aDomMessage) { michael@0: if (!Components.isSuccessCode(aRv)) { michael@0: if (DEBUG) debug("Error! Fail to save sending message! rv = " + aRv); michael@0: aRequest.notifySendMessageFailed( michael@0: gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(aRv)); michael@0: Services.obs.notifyObservers(aDomMessage, kSmsFailedObserverTopic, null); michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) debug("Saving sending message is done. Start to send."); michael@0: michael@0: Services.obs.notifyObservers(aDomMessage, kSmsSendingObserverTopic, null); michael@0: michael@0: if (errorCode !== Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR) { michael@0: if (DEBUG) debug("Error! The params for sending MMS are invalid."); michael@0: sendTransactionCb(aDomMessage, errorCode, null); michael@0: return; michael@0: } michael@0: michael@0: // Check radio state in prior to default service Id. michael@0: if (getRadioDisabledState()) { michael@0: if (DEBUG) debug("Error! Radio is disabled when sending MMS."); michael@0: sendTransactionCb(aDomMessage, michael@0: Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR, michael@0: null); michael@0: return; michael@0: } michael@0: michael@0: // To support DSDS, we have to stop users sending MMS when the selected michael@0: // SIM is not active, thus avoiding the data disconnection of the current michael@0: // SIM. Users have to manually swith the default SIM before sending. michael@0: if (mmsConnection.serviceId != self.mmsDefaultServiceId) { michael@0: if (DEBUG) debug("RIL service is not active to send MMS."); michael@0: sendTransactionCb(aDomMessage, michael@0: Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR, michael@0: null); michael@0: return; michael@0: } michael@0: michael@0: // This is the entry point starting to send MMS. michael@0: let sendTransaction; michael@0: try { michael@0: sendTransaction = michael@0: new SendTransaction(mmsConnection, aDomMessage.id, savableMessage, michael@0: savableMessage["deliveryStatusRequested"]); michael@0: } catch (e) { michael@0: if (DEBUG) debug("Exception: fail to create a SendTransaction instance."); michael@0: sendTransactionCb(aDomMessage, michael@0: Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null); michael@0: return; michael@0: } michael@0: sendTransaction.run(function callback(aMmsStatus, aMsg) { michael@0: if (DEBUG) debug("The sending status of sendTransaction.run(): " + aMmsStatus); michael@0: let errorCode; michael@0: if (aMmsStatus == _MMS_ERROR_MESSAGE_DELETED) { michael@0: errorCode = Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR; michael@0: } else if (aMmsStatus == _MMS_ERROR_RADIO_DISABLED) { michael@0: errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR; michael@0: } else if (aMmsStatus == _MMS_ERROR_NO_SIM_CARD) { michael@0: errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR; michael@0: } else if (aMmsStatus == _MMS_ERROR_SIM_CARD_CHANGED) { michael@0: errorCode = Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR; michael@0: } else if (aMmsStatus != MMS.MMS_PDU_ERROR_OK) { michael@0: errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR; michael@0: } else { michael@0: errorCode = Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR; michael@0: } michael@0: let envelopeId = michael@0: aMsg && aMsg.headers && aMsg.headers["message-id"] || null; michael@0: sendTransactionCb(aDomMessage, errorCode, envelopeId); michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: retrieve: function(aMessageId, aRequest) { michael@0: if (DEBUG) debug("Retrieving message with ID " + aMessageId); michael@0: gMobileMessageDatabaseService.getMessageRecordById(aMessageId, michael@0: (function notifyResult(aRv, aMessageRecord, aDomMessage) { michael@0: if (!Components.isSuccessCode(aRv)) { michael@0: if (DEBUG) debug("Function getMessageRecordById() return error: " + aRv); michael@0: aRequest.notifyGetMessageFailed( michael@0: gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(aRv)); michael@0: return; michael@0: } michael@0: if ("mms" != aMessageRecord.type) { michael@0: if (DEBUG) debug("Type of message record is not 'mms'."); michael@0: aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); michael@0: return; michael@0: } michael@0: if (!aMessageRecord.headers) { michael@0: if (DEBUG) debug("Must need the MMS' headers to proceed the retrieve."); michael@0: aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); michael@0: return; michael@0: } michael@0: if (!aMessageRecord.headers["x-mms-content-location"]) { michael@0: if (DEBUG) debug("Can't find mms content url in database."); michael@0: aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); michael@0: return; michael@0: } michael@0: if (DELIVERY_NOT_DOWNLOADED != aMessageRecord.delivery) { michael@0: if (DEBUG) debug("Delivery of message record is not 'not-downloaded'."); michael@0: aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); michael@0: return; michael@0: } michael@0: let deliveryStatus = aMessageRecord.deliveryInfo[0].deliveryStatus; michael@0: if (DELIVERY_STATUS_PENDING == deliveryStatus) { michael@0: if (DEBUG) debug("Delivery status of message record is 'pending'."); michael@0: aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); michael@0: return; michael@0: } michael@0: michael@0: // Cite 6.2 "Multimedia Message Notification" in OMA-TS-MMS_ENC-V1_3-20110913-A: michael@0: // The field has only one format, relative. The recipient client calculates michael@0: // this length of time relative to the time it receives the notification. michael@0: if (aMessageRecord.headers["x-mms-expiry"] != undefined) { michael@0: let expiryDate = aMessageRecord.timestamp + michael@0: aMessageRecord.headers["x-mms-expiry"] * 1000; michael@0: if (expiryDate < Date.now()) { michael@0: if (DEBUG) debug("The message to be retrieved is expired."); michael@0: aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // IccInfo in RadioInterface is not available when radio is off and michael@0: // NO_SIM_CARD_ERROR will be replied instead of RADIO_DISABLED_ERROR. michael@0: // Hence, for manual retrieving, instead of checking radio state later michael@0: // in MmsConnection.acquire(), We have to check radio state in prior to michael@0: // iccId to return the error correctly. michael@0: if (getRadioDisabledState()) { michael@0: if (DEBUG) debug("Error! Radio is disabled when retrieving MMS."); michael@0: aRequest.notifyGetMessageFailed( michael@0: Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR); michael@0: return; michael@0: } michael@0: michael@0: // Get MmsConnection based on the saved MMS message record's ICC ID, michael@0: // which could fail when the corresponding SIM card isn't installed. michael@0: let mmsConnection; michael@0: try { michael@0: mmsConnection = gMmsConnections.getConnByIccId(aMessageRecord.iccId); michael@0: } catch (e) { michael@0: if (DEBUG) debug("Failed to get connection by IccId. e= " + e); michael@0: let error = (e === _MMS_ERROR_SIM_NOT_MATCHED) ? michael@0: Ci.nsIMobileMessageCallback.SIM_NOT_MATCHED_ERROR : michael@0: Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR; michael@0: aRequest.notifyGetMessageFailed(error); michael@0: return; michael@0: } michael@0: michael@0: // To support DSDS, we have to stop users retrieving MMS when the needed michael@0: // SIM is not active, thus avoiding the data disconnection of the current michael@0: // SIM. Users have to manually swith the default SIM before retrieving. michael@0: if (mmsConnection.serviceId != this.mmsDefaultServiceId) { michael@0: if (DEBUG) debug("RIL service is not active to retrieve MMS."); michael@0: aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR); michael@0: return; michael@0: } michael@0: michael@0: let url = aMessageRecord.headers["x-mms-content-location"].uri; michael@0: // For X-Mms-Report-Allowed michael@0: let wish = aMessageRecord.headers["x-mms-delivery-report"]; michael@0: let responseNotify = function responseNotify(mmsStatus, retrievedMsg) { michael@0: // If the messsage has been deleted (because the retrieving process is michael@0: // cancelled), we don't need to reset the its delievery state/status. michael@0: if (mmsStatus == _MMS_ERROR_MESSAGE_DELETED) { michael@0: aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR); michael@0: return; michael@0: } michael@0: michael@0: // If the mmsStatus is still MMS_PDU_STATUS_DEFERRED after retry, michael@0: // we should not store it into database and update its delivery michael@0: // status to 'error'. michael@0: if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) { michael@0: if (DEBUG) debug("RetrieveMessage fail after retry."); michael@0: let errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR; michael@0: if (mmsStatus == _MMS_ERROR_RADIO_DISABLED) { michael@0: errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR; michael@0: } else if (mmsStatus == _MMS_ERROR_NO_SIM_CARD) { michael@0: errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR; michael@0: } else if (mmsStatus == _MMS_ERROR_SIM_CARD_CHANGED) { michael@0: errorCode = Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR; michael@0: } michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryByMessageId(aMessageId, michael@0: null, michael@0: null, michael@0: DELIVERY_STATUS_ERROR, michael@0: null, michael@0: function() { michael@0: aRequest.notifyGetMessageFailed(errorCode); michael@0: }); michael@0: return; michael@0: } michael@0: // In OMA-TS-MMS_ENC-V1_3, Table 5 in page 25. This header field michael@0: // (x-mms-transaction-id) SHALL be present when the MMS Proxy relay michael@0: // seeks an acknowledgement for the MM delivered though M-Retrieve.conf michael@0: // PDU during deferred retrieval. This transaction ID is used by the MMS michael@0: // Client and MMS Proxy-Relay to provide linkage between the originated michael@0: // M-Retrieve.conf and the response M-Acknowledge.ind PDUs. michael@0: let transactionId = retrievedMsg.headers["x-mms-transaction-id"]; michael@0: michael@0: // The absence of the field does not indicate any default michael@0: // value. So we go checking the same field in retrieved michael@0: // message instead. michael@0: if (wish == null && retrievedMsg) { michael@0: wish = retrievedMsg.headers["x-mms-delivery-report"]; michael@0: } michael@0: let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport, michael@0: wish); michael@0: michael@0: if (DEBUG) debug("retrievedMsg = " + JSON.stringify(retrievedMsg)); michael@0: aMessageRecord = this.mergeRetrievalConfirmation(mmsConnection, michael@0: retrievedMsg, michael@0: aMessageRecord); michael@0: michael@0: gMobileMessageDatabaseService.saveReceivedMessage(aMessageRecord, michael@0: (function(rv, domMessage) { michael@0: let success = Components.isSuccessCode(rv); michael@0: if (!success) { michael@0: // At this point we could send a message to content to michael@0: // notify the user that storing an incoming MMS failed, most michael@0: // likely due to a full disk. michael@0: if (DEBUG) debug("Could not store MMS, error code " + rv); michael@0: aRequest.notifyGetMessageFailed( michael@0: gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv)); michael@0: return; michael@0: } michael@0: michael@0: // Notifying observers a new MMS message is retrieved. michael@0: this.broadcastReceivedMessageEvent(domMessage); michael@0: michael@0: // Return the request after retrieving the MMS message successfully. michael@0: aRequest.notifyMessageGot(domMessage); michael@0: michael@0: // Cite 6.3.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A: michael@0: // If an acknowledgement is requested, the MMS Client SHALL respond michael@0: // with an M-Acknowledge.ind PDU to the MMS Proxy-Relay that supports michael@0: // the specific MMS Client. The M-Acknowledge.ind PDU confirms michael@0: // successful message retrieval to the MMS Proxy Relay. michael@0: let transaction = new AcknowledgeTransaction(mmsConnection, michael@0: transactionId, michael@0: reportAllowed); michael@0: transaction.run(); michael@0: }).bind(this)); michael@0: }; michael@0: michael@0: // Update the delivery status to pending in DB. michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryByMessageId(aMessageId, michael@0: null, michael@0: null, michael@0: DELIVERY_STATUS_PENDING, michael@0: null, michael@0: (function(rv) { michael@0: let success = Components.isSuccessCode(rv); michael@0: if (!success) { michael@0: if (DEBUG) debug("Could not change the delivery status, error code " + rv); michael@0: aRequest.notifyGetMessageFailed( michael@0: gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv)); michael@0: return; michael@0: } michael@0: michael@0: this.retrieveMessage(mmsConnection, michael@0: url, michael@0: responseNotify.bind(this), michael@0: aDomMessage); michael@0: }).bind(this)); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: sendReadReport: function(messageID, toAddress, iccId) { michael@0: if (DEBUG) { michael@0: debug("messageID: " + messageID + " toAddress: " + michael@0: JSON.stringify(toAddress)); michael@0: } michael@0: michael@0: // Get MmsConnection based on the saved MMS message record's ICC ID, michael@0: // which could fail when the corresponding SIM card isn't installed. michael@0: let mmsConnection; michael@0: try { michael@0: mmsConnection = gMmsConnections.getConnByIccId(iccId); michael@0: } catch (e) { michael@0: if (DEBUG) debug("Failed to get connection by IccId. e = " + e); michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: let transaction = michael@0: new ReadRecTransaction(mmsConnection, messageID, toAddress); michael@0: transaction.run(); michael@0: } catch (e) { michael@0: if (DEBUG) debug("sendReadReport fail. e = " + e); michael@0: } michael@0: }, michael@0: michael@0: // nsIWapPushApplication michael@0: michael@0: receiveWapPush: function(array, length, offset, options) { michael@0: let data = {array: array, offset: offset}; michael@0: let msg = MMS.PduHelper.parse(data, null); michael@0: if (!msg) { michael@0: return false; michael@0: } michael@0: if (DEBUG) debug("receiveWapPush: msg = " + JSON.stringify(msg)); michael@0: michael@0: switch (msg.type) { michael@0: case MMS.MMS_PDU_TYPE_NOTIFICATION_IND: michael@0: this.handleNotificationIndication(options.serviceId, msg); michael@0: break; michael@0: case MMS.MMS_PDU_TYPE_DELIVERY_IND: michael@0: this.handleDeliveryIndication(msg); michael@0: break; michael@0: case MMS.MMS_PDU_TYPE_READ_ORIG_IND: michael@0: this.handleReadOriginateIndication(msg); michael@0: break; michael@0: default: michael@0: if (DEBUG) debug("Unsupported X-MMS-Message-Type: " + msg.type); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: // nsIObserver michael@0: michael@0: observe: function(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: michael@0: if (aData === kPrefDefaultServiceId) { michael@0: this.mmsDefaultServiceId = getDefaultServiceId(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MmsService]);