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