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