dom/mobilemessage/src/gonk/MmsService.js

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

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

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

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

mercurial