|
1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors |
|
2 * |
|
3 * Licensed under the Apache License, Version 2.0 (the "License"); |
|
4 * you may not use this file except in compliance with the License. |
|
5 * You may obtain a copy of the License at |
|
6 * |
|
7 * http://www.apache.org/licenses/LICENSE-2.0 |
|
8 * |
|
9 * Unless required by applicable law or agreed to in writing, software |
|
10 * distributed under the License is distributed on an "AS IS" BASIS, |
|
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
12 * See the License for the specific language governing permissions and |
|
13 * limitations under the License. |
|
14 */ |
|
15 |
|
16 "use strict"; |
|
17 |
|
18 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; |
|
19 |
|
20 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
21 Cu.import("resource://gre/modules/Services.jsm"); |
|
22 Cu.import("resource://gre/modules/Sntp.jsm"); |
|
23 Cu.import("resource://gre/modules/systemlibs.js"); |
|
24 Cu.import("resource://gre/modules/Promise.jsm"); |
|
25 Cu.import("resource://gre/modules/FileUtils.jsm"); |
|
26 |
|
27 var RIL = {}; |
|
28 Cu.import("resource://gre/modules/ril_consts.js", RIL); |
|
29 |
|
30 // set to true in ril_consts.js to see debug messages |
|
31 var DEBUG = RIL.DEBUG_RIL; |
|
32 |
|
33 // Read debug setting from pref |
|
34 let debugPref = false; |
|
35 try { |
|
36 debugPref = Services.prefs.getBoolPref("ril.debugging.enabled"); |
|
37 } catch(e) { |
|
38 debugPref = false; |
|
39 } |
|
40 DEBUG = RIL.DEBUG_RIL || debugPref; |
|
41 |
|
42 function debug(s) { |
|
43 dump("-*- RadioInterfaceLayer: " + s + "\n"); |
|
44 } |
|
45 |
|
46 // Ril quirk to attach data registration on demand. |
|
47 let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = |
|
48 libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true"; |
|
49 |
|
50 // Ril quirk to always turn the radio off for the client without SIM card |
|
51 // except hw default client. |
|
52 let RILQUIRKS_RADIO_OFF_WO_CARD = |
|
53 libcutils.property_get("ro.moz.ril.radio_off_wo_card", "false") == "true"; |
|
54 |
|
55 // Ril quirk to enable IPv6 protocol/roaming protocol in APN settings. |
|
56 let RILQUIRKS_HAVE_IPV6 = |
|
57 libcutils.property_get("ro.moz.ril.ipv6", "false") == "true"; |
|
58 |
|
59 const RADIOINTERFACELAYER_CID = |
|
60 Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}"); |
|
61 const RADIOINTERFACE_CID = |
|
62 Components.ID("{6a7c91f0-a2b3-4193-8562-8969296c0b54}"); |
|
63 const RILNETWORKINTERFACE_CID = |
|
64 Components.ID("{3bdd52a9-3965-4130-b569-0ac5afed045e}"); |
|
65 const GSMICCINFO_CID = |
|
66 Components.ID("{d90c4261-a99d-47bc-8b05-b057bb7e8f8a}"); |
|
67 const CDMAICCINFO_CID = |
|
68 Components.ID("{39ba3c08-aacc-46d0-8c04-9b619c387061}"); |
|
69 |
|
70 const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown"; |
|
71 const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed"; |
|
72 const kNetworkConnStateChangedTopic = "network-connection-state-changed"; |
|
73 const kNetworkActiveChangedTopic = "network-active-changed"; |
|
74 const kSmsReceivedObserverTopic = "sms-received"; |
|
75 const kSilentSmsReceivedObserverTopic = "silent-sms-received"; |
|
76 const kSmsSendingObserverTopic = "sms-sending"; |
|
77 const kSmsSentObserverTopic = "sms-sent"; |
|
78 const kSmsFailedObserverTopic = "sms-failed"; |
|
79 const kSmsDeliverySuccessObserverTopic = "sms-delivery-success"; |
|
80 const kSmsDeliveryErrorObserverTopic = "sms-delivery-error"; |
|
81 const kMozSettingsChangedObserverTopic = "mozsettings-changed"; |
|
82 const kSysMsgListenerReadyObserverTopic = "system-message-listener-ready"; |
|
83 const kSysClockChangeObserverTopic = "system-clock-change"; |
|
84 const kScreenStateChangedTopic = "screen-state-changed"; |
|
85 |
|
86 const kSettingsCellBroadcastSearchList = "ril.cellbroadcast.searchlist"; |
|
87 const kSettingsClockAutoUpdateEnabled = "time.clock.automatic-update.enabled"; |
|
88 const kSettingsClockAutoUpdateAvailable = "time.clock.automatic-update.available"; |
|
89 const kSettingsTimezoneAutoUpdateEnabled = "time.timezone.automatic-update.enabled"; |
|
90 const kSettingsTimezoneAutoUpdateAvailable = "time.timezone.automatic-update.available"; |
|
91 |
|
92 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; |
|
93 |
|
94 const kPrefCellBroadcastDisabled = "ril.cellbroadcast.disabled"; |
|
95 const kPrefClirModePreference = "ril.clirMode"; |
|
96 const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces"; |
|
97 |
|
98 const DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED = "received"; |
|
99 const DOM_MOBILE_MESSAGE_DELIVERY_SENDING = "sending"; |
|
100 const DOM_MOBILE_MESSAGE_DELIVERY_SENT = "sent"; |
|
101 const DOM_MOBILE_MESSAGE_DELIVERY_ERROR = "error"; |
|
102 |
|
103 const RADIO_POWER_OFF_TIMEOUT = 30000; |
|
104 const SMS_HANDLED_WAKELOCK_TIMEOUT = 5000; |
|
105 const HW_DEFAULT_CLIENT_ID = 0; |
|
106 |
|
107 const RIL_IPC_MOBILECONNECTION_MSG_NAMES = [ |
|
108 "RIL:GetRilContext", |
|
109 "RIL:GetAvailableNetworks", |
|
110 "RIL:SelectNetwork", |
|
111 "RIL:SelectNetworkAuto", |
|
112 "RIL:SetPreferredNetworkType", |
|
113 "RIL:GetPreferredNetworkType", |
|
114 "RIL:SendMMI", |
|
115 "RIL:CancelMMI", |
|
116 "RIL:RegisterMobileConnectionMsg", |
|
117 "RIL:SetCallForwardingOptions", |
|
118 "RIL:GetCallForwardingOptions", |
|
119 "RIL:SetCallBarringOptions", |
|
120 "RIL:GetCallBarringOptions", |
|
121 "RIL:ChangeCallBarringPassword", |
|
122 "RIL:SetCallWaitingOptions", |
|
123 "RIL:GetCallWaitingOptions", |
|
124 "RIL:SetCallingLineIdRestriction", |
|
125 "RIL:GetCallingLineIdRestriction", |
|
126 "RIL:SetRoamingPreference", |
|
127 "RIL:GetRoamingPreference", |
|
128 "RIL:ExitEmergencyCbMode", |
|
129 "RIL:SetRadioEnabled", |
|
130 "RIL:SetVoicePrivacyMode", |
|
131 "RIL:GetVoicePrivacyMode", |
|
132 "RIL:GetSupportedNetworkTypes" |
|
133 ]; |
|
134 |
|
135 const RIL_IPC_MOBILENETWORK_MSG_NAMES = [ |
|
136 "RIL:GetLastKnownNetwork", |
|
137 "RIL:GetLastKnownHomeNetwork" |
|
138 ]; |
|
139 |
|
140 const RIL_IPC_ICCMANAGER_MSG_NAMES = [ |
|
141 "RIL:SendStkResponse", |
|
142 "RIL:SendStkMenuSelection", |
|
143 "RIL:SendStkTimerExpiration", |
|
144 "RIL:SendStkEventDownload", |
|
145 "RIL:GetCardLockState", |
|
146 "RIL:UnlockCardLock", |
|
147 "RIL:SetCardLock", |
|
148 "RIL:GetCardLockRetryCount", |
|
149 "RIL:IccOpenChannel", |
|
150 "RIL:IccExchangeAPDU", |
|
151 "RIL:IccCloseChannel", |
|
152 "RIL:ReadIccContacts", |
|
153 "RIL:UpdateIccContact", |
|
154 "RIL:RegisterIccMsg", |
|
155 "RIL:MatchMvno" |
|
156 ]; |
|
157 |
|
158 const RIL_IPC_VOICEMAIL_MSG_NAMES = [ |
|
159 "RIL:RegisterVoicemailMsg", |
|
160 "RIL:GetVoicemailInfo" |
|
161 ]; |
|
162 |
|
163 const RIL_IPC_CELLBROADCAST_MSG_NAMES = [ |
|
164 "RIL:RegisterCellBroadcastMsg" |
|
165 ]; |
|
166 |
|
167 XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService", |
|
168 "@mozilla.org/power/powermanagerservice;1", |
|
169 "nsIPowerManagerService"); |
|
170 |
|
171 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService", |
|
172 "@mozilla.org/mobilemessage/mobilemessageservice;1", |
|
173 "nsIMobileMessageService"); |
|
174 |
|
175 XPCOMUtils.defineLazyServiceGetter(this, "gSmsService", |
|
176 "@mozilla.org/sms/smsservice;1", |
|
177 "nsISmsService"); |
|
178 |
|
179 XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageDatabaseService", |
|
180 "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1", |
|
181 "nsIRilMobileMessageDatabaseService"); |
|
182 |
|
183 XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
|
184 "@mozilla.org/parentprocessmessagemanager;1", |
|
185 "nsIMessageBroadcaster"); |
|
186 |
|
187 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService", |
|
188 "@mozilla.org/settingsService;1", |
|
189 "nsISettingsService"); |
|
190 |
|
191 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger", |
|
192 "@mozilla.org/system-message-internal;1", |
|
193 "nsISystemMessagesInternal"); |
|
194 |
|
195 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager", |
|
196 "@mozilla.org/network/manager;1", |
|
197 "nsINetworkManager"); |
|
198 |
|
199 XPCOMUtils.defineLazyServiceGetter(this, "gTimeService", |
|
200 "@mozilla.org/time/timeservice;1", |
|
201 "nsITimeService"); |
|
202 |
|
203 XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager", |
|
204 "@mozilla.org/telephony/system-worker-manager;1", |
|
205 "nsISystemWorkerManager"); |
|
206 |
|
207 XPCOMUtils.defineLazyServiceGetter(this, "gTelephonyProvider", |
|
208 "@mozilla.org/telephony/telephonyprovider;1", |
|
209 "nsIGonkTelephonyProvider"); |
|
210 |
|
211 XPCOMUtils.defineLazyGetter(this, "WAP", function() { |
|
212 let wap = {}; |
|
213 Cu.import("resource://gre/modules/WapPushManager.js", wap); |
|
214 return wap; |
|
215 }); |
|
216 |
|
217 XPCOMUtils.defineLazyGetter(this, "PhoneNumberUtils", function() { |
|
218 let ns = {}; |
|
219 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns); |
|
220 return ns.PhoneNumberUtils; |
|
221 }); |
|
222 |
|
223 XPCOMUtils.defineLazyGetter(this, "gMessageManager", function() { |
|
224 return { |
|
225 QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener, |
|
226 Ci.nsIObserver]), |
|
227 |
|
228 ril: null, |
|
229 |
|
230 // Manage message targets in terms of topic. Only the authorized and |
|
231 // registered contents can receive related messages. |
|
232 targetsByTopic: {}, |
|
233 topics: [], |
|
234 |
|
235 targetMessageQueue: [], |
|
236 ready: false, |
|
237 |
|
238 init: function(ril) { |
|
239 this.ril = ril; |
|
240 |
|
241 Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); |
|
242 Services.obs.addObserver(this, kSysMsgListenerReadyObserverTopic, false); |
|
243 this._registerMessageListeners(); |
|
244 }, |
|
245 |
|
246 _shutdown: function() { |
|
247 this.ril = null; |
|
248 |
|
249 Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); |
|
250 this._unregisterMessageListeners(); |
|
251 }, |
|
252 |
|
253 _registerMessageListeners: function() { |
|
254 ppmm.addMessageListener("child-process-shutdown", this); |
|
255 for (let msgname of RIL_IPC_MOBILECONNECTION_MSG_NAMES) { |
|
256 ppmm.addMessageListener(msgname, this); |
|
257 } |
|
258 for (let msgname of RIL_IPC_MOBILENETWORK_MSG_NAMES) { |
|
259 ppmm.addMessageListener(msgname, this); |
|
260 } |
|
261 for (let msgName of RIL_IPC_ICCMANAGER_MSG_NAMES) { |
|
262 ppmm.addMessageListener(msgName, this); |
|
263 } |
|
264 for (let msgname of RIL_IPC_VOICEMAIL_MSG_NAMES) { |
|
265 ppmm.addMessageListener(msgname, this); |
|
266 } |
|
267 for (let msgname of RIL_IPC_CELLBROADCAST_MSG_NAMES) { |
|
268 ppmm.addMessageListener(msgname, this); |
|
269 } |
|
270 }, |
|
271 |
|
272 _unregisterMessageListeners: function() { |
|
273 ppmm.removeMessageListener("child-process-shutdown", this); |
|
274 for (let msgname of RIL_IPC_MOBILECONNECTION_MSG_NAMES) { |
|
275 ppmm.removeMessageListener(msgname, this); |
|
276 } |
|
277 for (let msgname of RIL_IPC_MOBILENETWORK_MSG_NAMES) { |
|
278 ppmm.removeMessageListener(msgname, this); |
|
279 } |
|
280 for (let msgName of RIL_IPC_ICCMANAGER_MSG_NAMES) { |
|
281 ppmm.removeMessageListener(msgName, this); |
|
282 } |
|
283 for (let msgname of RIL_IPC_VOICEMAIL_MSG_NAMES) { |
|
284 ppmm.removeMessageListener(msgname, this); |
|
285 } |
|
286 for (let msgname of RIL_IPC_CELLBROADCAST_MSG_NAMES) { |
|
287 ppmm.removeMessageListener(msgname, this); |
|
288 } |
|
289 ppmm = null; |
|
290 }, |
|
291 |
|
292 _registerMessageTarget: function(topic, target) { |
|
293 let targets = this.targetsByTopic[topic]; |
|
294 if (!targets) { |
|
295 targets = this.targetsByTopic[topic] = []; |
|
296 let list = this.topics; |
|
297 if (list.indexOf(topic) == -1) { |
|
298 list.push(topic); |
|
299 } |
|
300 } |
|
301 |
|
302 if (targets.indexOf(target) != -1) { |
|
303 if (DEBUG) debug("Already registered this target!"); |
|
304 return; |
|
305 } |
|
306 |
|
307 targets.push(target); |
|
308 if (DEBUG) debug("Registered " + topic + " target: " + target); |
|
309 }, |
|
310 |
|
311 _unregisterMessageTarget: function(topic, target) { |
|
312 if (topic == null) { |
|
313 // Unregister the target for every topic when no topic is specified. |
|
314 for (let type of this.topics) { |
|
315 this._unregisterMessageTarget(type, target); |
|
316 } |
|
317 return; |
|
318 } |
|
319 |
|
320 // Unregister the target for a specified topic. |
|
321 let targets = this.targetsByTopic[topic]; |
|
322 if (!targets) { |
|
323 return; |
|
324 } |
|
325 |
|
326 let index = targets.indexOf(target); |
|
327 if (index != -1) { |
|
328 targets.splice(index, 1); |
|
329 if (DEBUG) debug("Unregistered " + topic + " target: " + target); |
|
330 } |
|
331 }, |
|
332 |
|
333 _enqueueTargetMessage: function(topic, message, options) { |
|
334 let msg = { topic : topic, |
|
335 message : message, |
|
336 options : options }; |
|
337 // Remove previous queued message with the same message type and client Id |
|
338 // , only one message per (message type + client Id) is allowed in queue. |
|
339 let messageQueue = this.targetMessageQueue; |
|
340 for(let i = 0; i < messageQueue.length; i++) { |
|
341 if (messageQueue[i].message === message && |
|
342 messageQueue[i].options.clientId === options.clientId) { |
|
343 messageQueue.splice(i, 1); |
|
344 break; |
|
345 } |
|
346 } |
|
347 |
|
348 messageQueue.push(msg); |
|
349 }, |
|
350 |
|
351 _sendTargetMessage: function(topic, message, options) { |
|
352 if (!this.ready) { |
|
353 this._enqueueTargetMessage(topic, message, options); |
|
354 return; |
|
355 } |
|
356 |
|
357 let targets = this.targetsByTopic[topic]; |
|
358 if (!targets) { |
|
359 return; |
|
360 } |
|
361 |
|
362 for (let target of targets) { |
|
363 target.sendAsyncMessage(message, options); |
|
364 } |
|
365 }, |
|
366 |
|
367 _resendQueuedTargetMessage: function() { |
|
368 this.ready = true; |
|
369 |
|
370 // Here uses this._sendTargetMessage() to resend message, which will |
|
371 // enqueue message if listener is not ready. |
|
372 // So only resend after listener is ready, or it will cause infinate loop and |
|
373 // hang the system. |
|
374 |
|
375 // Dequeue and resend messages. |
|
376 for each (let msg in this.targetMessageQueue) { |
|
377 this._sendTargetMessage(msg.topic, msg.message, msg.options); |
|
378 } |
|
379 this.targetMessageQueue = null; |
|
380 }, |
|
381 |
|
382 /** |
|
383 * nsIMessageListener interface methods. |
|
384 */ |
|
385 |
|
386 receiveMessage: function(msg) { |
|
387 if (DEBUG) debug("Received '" + msg.name + "' message from content process"); |
|
388 if (msg.name == "child-process-shutdown") { |
|
389 // By the time we receive child-process-shutdown, the child process has |
|
390 // already forgotten its permissions so we need to unregister the target |
|
391 // for every permission. |
|
392 this._unregisterMessageTarget(null, msg.target); |
|
393 return null; |
|
394 } |
|
395 |
|
396 if (RIL_IPC_MOBILECONNECTION_MSG_NAMES.indexOf(msg.name) != -1) { |
|
397 if (!msg.target.assertPermission("mobileconnection")) { |
|
398 if (DEBUG) { |
|
399 debug("MobileConnection message " + msg.name + |
|
400 " from a content process with no 'mobileconnection' privileges."); |
|
401 } |
|
402 return null; |
|
403 } |
|
404 } else if (RIL_IPC_MOBILENETWORK_MSG_NAMES.indexOf(msg.name) != -1) { |
|
405 if (!msg.target.assertPermission("mobilenetwork")) { |
|
406 if (DEBUG) { |
|
407 debug("MobileNetwork message " + msg.name + |
|
408 " from a content process with no 'mobilenetwork' privileges."); |
|
409 } |
|
410 return null; |
|
411 } |
|
412 } else if (RIL_IPC_ICCMANAGER_MSG_NAMES.indexOf(msg.name) != -1) { |
|
413 if (!msg.target.assertPermission("mobileconnection")) { |
|
414 if (DEBUG) { |
|
415 debug("IccManager message " + msg.name + |
|
416 " from a content process with no 'mobileconnection' privileges."); |
|
417 } |
|
418 return null; |
|
419 } |
|
420 } else if (RIL_IPC_VOICEMAIL_MSG_NAMES.indexOf(msg.name) != -1) { |
|
421 if (!msg.target.assertPermission("voicemail")) { |
|
422 if (DEBUG) { |
|
423 debug("Voicemail message " + msg.name + |
|
424 " from a content process with no 'voicemail' privileges."); |
|
425 } |
|
426 return null; |
|
427 } |
|
428 } else if (RIL_IPC_CELLBROADCAST_MSG_NAMES.indexOf(msg.name) != -1) { |
|
429 if (!msg.target.assertPermission("cellbroadcast")) { |
|
430 if (DEBUG) { |
|
431 debug("Cell Broadcast message " + msg.name + |
|
432 " from a content process with no 'cellbroadcast' privileges."); |
|
433 } |
|
434 return null; |
|
435 } |
|
436 } else { |
|
437 if (DEBUG) debug("Ignoring unknown message type: " + msg.name); |
|
438 return null; |
|
439 } |
|
440 |
|
441 switch (msg.name) { |
|
442 case "RIL:RegisterMobileConnectionMsg": |
|
443 this._registerMessageTarget("mobileconnection", msg.target); |
|
444 return null; |
|
445 case "RIL:RegisterIccMsg": |
|
446 this._registerMessageTarget("icc", msg.target); |
|
447 return null; |
|
448 case "RIL:RegisterVoicemailMsg": |
|
449 this._registerMessageTarget("voicemail", msg.target); |
|
450 return null; |
|
451 case "RIL:RegisterCellBroadcastMsg": |
|
452 this._registerMessageTarget("cellbroadcast", msg.target); |
|
453 return null; |
|
454 } |
|
455 |
|
456 let clientId = msg.json.clientId || 0; |
|
457 let radioInterface = this.ril.getRadioInterface(clientId); |
|
458 if (!radioInterface) { |
|
459 if (DEBUG) debug("No such radio interface: " + clientId); |
|
460 return null; |
|
461 } |
|
462 |
|
463 if (msg.name === "RIL:SetRadioEnabled") { |
|
464 // Special handler for SetRadioEnabled. |
|
465 return gRadioEnabledController.receiveMessage(msg); |
|
466 } |
|
467 |
|
468 return radioInterface.receiveMessage(msg); |
|
469 }, |
|
470 |
|
471 /** |
|
472 * nsIObserver interface methods. |
|
473 */ |
|
474 |
|
475 observe: function(subject, topic, data) { |
|
476 switch (topic) { |
|
477 case kSysMsgListenerReadyObserverTopic: |
|
478 Services.obs.removeObserver(this, kSysMsgListenerReadyObserverTopic); |
|
479 this._resendQueuedTargetMessage(); |
|
480 break; |
|
481 case NS_XPCOM_SHUTDOWN_OBSERVER_ID: |
|
482 this._shutdown(); |
|
483 break; |
|
484 } |
|
485 }, |
|
486 |
|
487 sendMobileConnectionMessage: function(message, clientId, data) { |
|
488 this._sendTargetMessage("mobileconnection", message, { |
|
489 clientId: clientId, |
|
490 data: data |
|
491 }); |
|
492 }, |
|
493 |
|
494 sendVoicemailMessage: function(message, clientId, data) { |
|
495 this._sendTargetMessage("voicemail", message, { |
|
496 clientId: clientId, |
|
497 data: data |
|
498 }); |
|
499 }, |
|
500 |
|
501 sendCellBroadcastMessage: function(message, clientId, data) { |
|
502 this._sendTargetMessage("cellbroadcast", message, { |
|
503 clientId: clientId, |
|
504 data: data |
|
505 }); |
|
506 }, |
|
507 |
|
508 sendIccMessage: function(message, clientId, data) { |
|
509 this._sendTargetMessage("icc", message, { |
|
510 clientId: clientId, |
|
511 data: data |
|
512 }); |
|
513 } |
|
514 }; |
|
515 }); |
|
516 |
|
517 XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function() { |
|
518 let _ril = null; |
|
519 let _pendingMessages = []; // For queueing "RIL =SetRadioEnabled" messages. |
|
520 let _isProcessingPending = false; |
|
521 let _timer = null; |
|
522 let _request = null; |
|
523 let _deactivatingDeferred = {}; |
|
524 let _initializedCardState = {}; |
|
525 let _allCardStateInitialized = !RILQUIRKS_RADIO_OFF_WO_CARD; |
|
526 |
|
527 return { |
|
528 init: function(ril) { |
|
529 _ril = ril; |
|
530 }, |
|
531 |
|
532 receiveCardState: function(clientId) { |
|
533 if (_allCardStateInitialized) { |
|
534 return; |
|
535 } |
|
536 |
|
537 if (DEBUG) debug("RadioControl: receive cardState from " + clientId); |
|
538 _initializedCardState[clientId] = true; |
|
539 if (Object.keys(_initializedCardState).length == _ril.numRadioInterfaces) { |
|
540 _allCardStateInitialized = true; |
|
541 this._startProcessingPending(); |
|
542 } |
|
543 }, |
|
544 |
|
545 receiveMessage: function(msg) { |
|
546 if (DEBUG) debug("RadioControl: receiveMessage: " + JSON.stringify(msg)); |
|
547 _pendingMessages.push(msg); |
|
548 this._startProcessingPending(); |
|
549 }, |
|
550 |
|
551 isDeactivatingDataCalls: function() { |
|
552 return _request !== null; |
|
553 }, |
|
554 |
|
555 finishDeactivatingDataCalls: function(clientId) { |
|
556 if (DEBUG) debug("RadioControl: finishDeactivatingDataCalls: " + clientId); |
|
557 let deferred = _deactivatingDeferred[clientId]; |
|
558 if (deferred) { |
|
559 deferred.resolve(); |
|
560 } |
|
561 }, |
|
562 |
|
563 _startProcessingPending: function() { |
|
564 if (!_isProcessingPending) { |
|
565 if (DEBUG) debug("RadioControl: start dequeue"); |
|
566 _isProcessingPending = true; |
|
567 this._processNextMessage(); |
|
568 } |
|
569 }, |
|
570 |
|
571 _processNextMessage: function() { |
|
572 if (_pendingMessages.length === 0 || !_allCardStateInitialized) { |
|
573 if (DEBUG) debug("RadioControl: stop dequeue"); |
|
574 _isProcessingPending = false; |
|
575 return; |
|
576 } |
|
577 |
|
578 let msg = _pendingMessages.shift(); |
|
579 this._handleMessage(msg); |
|
580 }, |
|
581 |
|
582 _getNumCards: function() { |
|
583 let numCards = 0; |
|
584 for (let i = 0, N = _ril.numRadioInterfaces; i < N; ++i) { |
|
585 if (this._isCardPresentAtClient(i)) { |
|
586 numCards++; |
|
587 } |
|
588 } |
|
589 return numCards; |
|
590 }, |
|
591 |
|
592 _isCardPresentAtClient: function(clientId) { |
|
593 let cardState = _ril.getRadioInterface(clientId).rilContext.cardState; |
|
594 return cardState !== RIL.GECKO_CARDSTATE_UNDETECTED && |
|
595 cardState !== RIL.GECKO_CARDSTATE_UNKNOWN; |
|
596 }, |
|
597 |
|
598 _isRadioAbleToEnableAtClient: function(clientId, numCards) { |
|
599 if (!RILQUIRKS_RADIO_OFF_WO_CARD) { |
|
600 return true; |
|
601 } |
|
602 |
|
603 // We could only turn on the radio for clientId if |
|
604 // 1. a SIM card is presented or |
|
605 // 2. it is the default clientId and there is no any SIM card at any client. |
|
606 |
|
607 if (this._isCardPresentAtClient(clientId)) { |
|
608 return true; |
|
609 } |
|
610 |
|
611 numCards = numCards == null ? this._getNumCards() : numCards; |
|
612 if (clientId === HW_DEFAULT_CLIENT_ID && numCards === 0) { |
|
613 return true; |
|
614 } |
|
615 |
|
616 return false; |
|
617 }, |
|
618 |
|
619 _handleMessage: function(msg) { |
|
620 if (DEBUG) debug("RadioControl: handleMessage: " + JSON.stringify(msg)); |
|
621 let clientId = msg.json.clientId || 0; |
|
622 let radioInterface = _ril.getRadioInterface(clientId); |
|
623 |
|
624 if (!radioInterface.isValidStateForSetRadioEnabled()) { |
|
625 radioInterface.setRadioEnabledResponse(msg.target, msg.json.data, |
|
626 "InvalidStateError"); |
|
627 this._processNextMessage(); |
|
628 return; |
|
629 } |
|
630 |
|
631 if (radioInterface.isDummyForSetRadioEnabled(msg.json.data)) { |
|
632 radioInterface.setRadioEnabledResponse(msg.target, msg.json.data); |
|
633 this._processNextMessage(); |
|
634 return; |
|
635 } |
|
636 |
|
637 if (msg.json.data.enabled) { |
|
638 if (this._isRadioAbleToEnableAtClient(clientId)) { |
|
639 radioInterface.receiveMessage(msg); |
|
640 } else { |
|
641 // Not really do it but respond success. |
|
642 radioInterface.setRadioEnabledResponse(msg.target, msg.json.data); |
|
643 } |
|
644 |
|
645 this._processNextMessage(); |
|
646 } else { |
|
647 _request = function() { |
|
648 radioInterface.receiveMessage(msg); |
|
649 }; |
|
650 |
|
651 // In 2G network, modem takes 35+ seconds to process deactivate data |
|
652 // call request if device has active voice call (please see bug 964974 |
|
653 // for more details). Therefore we should hangup all active voice calls |
|
654 // first. And considering some DSDS architecture, toggling one radio may |
|
655 // toggle both, so we send hangUpAll to all clients. |
|
656 for (let i = 0, N = _ril.numRadioInterfaces; i < N; ++i) { |
|
657 let iface = _ril.getRadioInterface(i); |
|
658 iface.workerMessenger.send("hangUpAll"); |
|
659 } |
|
660 |
|
661 // In some DSDS architecture with only one modem, toggling one radio may |
|
662 // toggle both. Therefore, for safely turning off, we should first |
|
663 // explicitly deactivate all data calls from all clients. |
|
664 this._deactivateDataCalls().then(() => { |
|
665 if (DEBUG) debug("RadioControl: deactivation done"); |
|
666 this._executeRequest(); |
|
667 }); |
|
668 |
|
669 this._createTimer(); |
|
670 } |
|
671 }, |
|
672 |
|
673 _deactivateDataCalls: function() { |
|
674 if (DEBUG) debug("RadioControl: deactivating data calls..."); |
|
675 _deactivatingDeferred = {}; |
|
676 |
|
677 let promise = Promise.resolve(); |
|
678 for (let i = 0, N = _ril.numRadioInterfaces; i < N; ++i) { |
|
679 promise = promise.then(this._deactivateDataCallsForClient(i)); |
|
680 } |
|
681 |
|
682 return promise; |
|
683 }, |
|
684 |
|
685 _deactivateDataCallsForClient: function(clientId) { |
|
686 return function() { |
|
687 let deferred = _deactivatingDeferred[clientId] = Promise.defer(); |
|
688 let dataConnectionHandler = gDataConnectionManager.getConnectionHandler(clientId); |
|
689 dataConnectionHandler.deactivateDataCalls(); |
|
690 return deferred.promise; |
|
691 }; |
|
692 }, |
|
693 |
|
694 _createTimer: function() { |
|
695 if (!_timer) { |
|
696 _timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
|
697 } |
|
698 _timer.initWithCallback(this._executeRequest.bind(this), |
|
699 RADIO_POWER_OFF_TIMEOUT, |
|
700 Ci.nsITimer.TYPE_ONE_SHOT); |
|
701 }, |
|
702 |
|
703 _cancelTimer: function() { |
|
704 if (_timer) { |
|
705 _timer.cancel(); |
|
706 } |
|
707 }, |
|
708 |
|
709 _executeRequest: function() { |
|
710 if (typeof _request === "function") { |
|
711 if (DEBUG) debug("RadioControl: executeRequest"); |
|
712 this._cancelTimer(); |
|
713 _request(); |
|
714 _request = null; |
|
715 } |
|
716 this._processNextMessage(); |
|
717 }, |
|
718 }; |
|
719 }); |
|
720 |
|
721 XPCOMUtils.defineLazyGetter(this, "gDataConnectionManager", function () { |
|
722 return { |
|
723 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, |
|
724 Ci.nsISettingsServiceCallback]), |
|
725 |
|
726 _connectionHandlers: null, |
|
727 |
|
728 // Flag to determine the data state to start with when we boot up. It |
|
729 // corresponds to the 'ril.data.enabled' setting from the UI. |
|
730 _dataEnabled: false, |
|
731 |
|
732 // Flag to record the default client id for data call. It corresponds to |
|
733 // the 'ril.data.defaultServiceId' setting from the UI. |
|
734 _dataDefaultClientId: -1, |
|
735 |
|
736 // Flag to record the current default client id for data call. |
|
737 // It differs from _dataDefaultClientId in that it is set only when |
|
738 // the switch of client id process is done. |
|
739 _currentDataClientId: -1, |
|
740 |
|
741 // Pending function to execute when we are notified that another data call has |
|
742 // been disconnected. |
|
743 _pendingDataCallRequest: null, |
|
744 |
|
745 debug: function(s) { |
|
746 dump("-*- DataConnectionManager: " + s + "\n"); |
|
747 }, |
|
748 |
|
749 init: function(ril) { |
|
750 if (!ril) { |
|
751 return; |
|
752 } |
|
753 |
|
754 this._connectionHandlers = []; |
|
755 for (let clientId = 0; clientId < ril.numRadioInterfaces; clientId++) { |
|
756 let radioInterface = ril.getRadioInterface(clientId); |
|
757 this._connectionHandlers.push( |
|
758 new DataConnectionHandler(clientId, radioInterface)); |
|
759 } |
|
760 |
|
761 let lock = gSettingsService.createLock(); |
|
762 // Read the APN data from the settings DB. |
|
763 lock.get("ril.data.apnSettings", this); |
|
764 // Read the data enabled setting from DB. |
|
765 lock.get("ril.data.enabled", this); |
|
766 lock.get("ril.data.roaming_enabled", this); |
|
767 // Read the default client id for data call. |
|
768 lock.get("ril.data.defaultServiceId", this); |
|
769 |
|
770 Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); |
|
771 Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false); |
|
772 Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic, false); |
|
773 }, |
|
774 |
|
775 getConnectionHandler: function(clientId) { |
|
776 return this._connectionHandlers[clientId]; |
|
777 }, |
|
778 |
|
779 _handleDataClientIdChange: function(newDefault) { |
|
780 if (this._dataDefaultClientId === newDefault) { |
|
781 return; |
|
782 } |
|
783 this._dataDefaultClientId = newDefault; |
|
784 |
|
785 if (this._currentDataClientId == -1) { |
|
786 // This is to handle boot up stage. |
|
787 this._currentDataClientId = this._dataDefaultClientId; |
|
788 let connHandler = this._connectionHandlers[this._currentDataClientId]; |
|
789 let radioInterface = connHandler.radioInterface; |
|
790 if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { |
|
791 radioInterface.setDataRegistration(true); |
|
792 } |
|
793 if (this._dataEnabled) { |
|
794 let settings = connHandler.dataCallSettings; |
|
795 settings.oldEnabled = settings.enabled; |
|
796 settings.enabled = true; |
|
797 connHandler.updateRILNetworkInterface(); |
|
798 } |
|
799 return; |
|
800 } |
|
801 |
|
802 let oldConnHandler = this._connectionHandlers[this._currentDataClientId]; |
|
803 let oldIface = oldConnHandler.radioInterface; |
|
804 let oldSettings = oldConnHandler.dataCallSettings; |
|
805 let newConnHandler = this._connectionHandlers[this._dataDefaultClientId]; |
|
806 let newIface = newConnHandler.radioInterface; |
|
807 let newSettings = newConnHandler.dataCallSettings; |
|
808 |
|
809 if (!this._dataEnabled) { |
|
810 if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { |
|
811 oldIface.setDataRegistration(false); |
|
812 newIface.setDataRegistration(true); |
|
813 } |
|
814 this._currentDataClientId = this._dataDefaultClientId; |
|
815 return; |
|
816 } |
|
817 |
|
818 oldSettings.oldEnabled = oldSettings.enabled; |
|
819 oldSettings.enabled = false; |
|
820 |
|
821 if (oldConnHandler.anyDataConnected()) { |
|
822 this._pendingDataCallRequest = function () { |
|
823 if (DEBUG) { |
|
824 this.debug("Executing pending data call request."); |
|
825 } |
|
826 if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { |
|
827 newIface.setDataRegistration(true); |
|
828 } |
|
829 newSettings.oldEnabled = newSettings.enabled; |
|
830 newSettings.enabled = this._dataEnabled; |
|
831 |
|
832 this._currentDataClientId = this._dataDefaultClientId; |
|
833 newConnHandler.updateRILNetworkInterface(); |
|
834 }; |
|
835 |
|
836 if (DEBUG) { |
|
837 this.debug("_handleDataClientIdChange: existing data call(s) active" + |
|
838 ", wait for them to get disconnected."); |
|
839 } |
|
840 oldConnHandler.deactivateDataCalls(); |
|
841 return; |
|
842 } |
|
843 |
|
844 newSettings.oldEnabled = newSettings.enabled; |
|
845 newSettings.enabled = true; |
|
846 |
|
847 this._currentDataClientId = this._dataDefaultClientId; |
|
848 if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { |
|
849 oldIface.setDataRegistration(false); |
|
850 newIface.setDataRegistration(true); |
|
851 } |
|
852 newConnHandler.updateRILNetworkInterface(); |
|
853 }, |
|
854 |
|
855 _shutdown: function() { |
|
856 for (let handler of this._connectionHandlers) { |
|
857 handler.shutdown(); |
|
858 } |
|
859 this._connectionHandlers = null; |
|
860 Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); |
|
861 Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic); |
|
862 Services.obs.removeObserver(this, kNetworkInterfaceStateChangedTopic); |
|
863 }, |
|
864 |
|
865 /** |
|
866 * nsISettingsServiceCallback |
|
867 */ |
|
868 handle: function(name, result) { |
|
869 switch(name) { |
|
870 case "ril.data.apnSettings": |
|
871 if (DEBUG) { |
|
872 this.debug("'ril.data.apnSettings' is now " + |
|
873 JSON.stringify(result)); |
|
874 } |
|
875 if (!result) { |
|
876 break; |
|
877 } |
|
878 for (let clientId in this._connectionHandlers) { |
|
879 let handler = this._connectionHandlers[clientId]; |
|
880 let apnSetting = result[clientId]; |
|
881 if (handler && apnSetting) { |
|
882 handler.updateApnSettings(apnSetting); |
|
883 handler.updateRILNetworkInterface(); |
|
884 } |
|
885 } |
|
886 break; |
|
887 case "ril.data.enabled": |
|
888 if (DEBUG) { |
|
889 this.debug("'ril.data.enabled' is now " + result); |
|
890 } |
|
891 if (this._dataEnabled === result) { |
|
892 break; |
|
893 } |
|
894 this._dataEnabled = result; |
|
895 |
|
896 if (DEBUG) { |
|
897 this.debug("Default id for data call: " + this._dataDefaultClientId); |
|
898 } |
|
899 if (this._dataDefaultClientId === -1) { |
|
900 // We haven't got the default id for data from db. |
|
901 break; |
|
902 } |
|
903 |
|
904 let connHandler = this._connectionHandlers[this._dataDefaultClientId]; |
|
905 let settings = connHandler.dataCallSettings; |
|
906 settings.oldEnabled = settings.enabled; |
|
907 settings.enabled = result; |
|
908 connHandler.updateRILNetworkInterface(); |
|
909 break; |
|
910 case "ril.data.roaming_enabled": |
|
911 if (DEBUG) { |
|
912 this.debug("'ril.data.roaming_enabled' is now " + result); |
|
913 this.debug("Default id for data call: " + this._dataDefaultClientId); |
|
914 } |
|
915 for (let clientId = 0; clientId < this._connectionHandlers.length; clientId++) { |
|
916 let connHandler = this._connectionHandlers[clientId]; |
|
917 let settings = connHandler.dataCallSettings; |
|
918 settings.roamingEnabled = Array.isArray(result) ? result[clientId] : result; |
|
919 } |
|
920 if (this._dataDefaultClientId === -1) { |
|
921 // We haven't got the default id for data from db. |
|
922 break; |
|
923 } |
|
924 this._connectionHandlers[this._dataDefaultClientId].updateRILNetworkInterface(); |
|
925 break; |
|
926 case "ril.data.defaultServiceId": |
|
927 result = result || 0; |
|
928 if (DEBUG) { |
|
929 this.debug("'ril.data.defaultServiceId' is now " + result); |
|
930 } |
|
931 this._handleDataClientIdChange(result); |
|
932 break; |
|
933 } |
|
934 }, |
|
935 |
|
936 handleError: function(errorMessage) { |
|
937 if (DEBUG) { |
|
938 this.debug("There was an error while reading RIL settings."); |
|
939 } |
|
940 }, |
|
941 |
|
942 /** |
|
943 * nsIObserver interface methods. |
|
944 */ |
|
945 observe: function(subject, topic, data) { |
|
946 switch (topic) { |
|
947 case kMozSettingsChangedObserverTopic: |
|
948 let setting = JSON.parse(data); |
|
949 this.handle(setting.key, setting.value); |
|
950 break; |
|
951 case kNetworkInterfaceStateChangedTopic: |
|
952 let network = subject.QueryInterface(Ci.nsINetworkInterface); |
|
953 // DSDS: setup pending data connection when switching the default id |
|
954 // for data call. We can not use network.type to tell if it's |
|
955 // NETWORK_TYPE_MOBILE, since the type is removed from |
|
956 // RILNetworkInterface.connectedTypes on disconnect(). |
|
957 if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN) { |
|
958 let connHandler = this._connectionHandlers[this._currentDataClientId]; |
|
959 let radioInterface = connHandler.radioInterface; |
|
960 if (connHandler.allDataDisconnected() && |
|
961 typeof this._pendingDataCallRequest === "function") { |
|
962 if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { |
|
963 radioInterface.setDataRegistration(false); |
|
964 } |
|
965 if (DEBUG) { |
|
966 this.debug("All data calls disconnected, setup pending data call."); |
|
967 } |
|
968 this._pendingDataCallRequest(); |
|
969 this._pendingDataCallRequest = null; |
|
970 } |
|
971 } |
|
972 break; |
|
973 case NS_XPCOM_SHUTDOWN_OBSERVER_ID: |
|
974 this._shutdown(); |
|
975 break; |
|
976 } |
|
977 }, |
|
978 }; |
|
979 }); |
|
980 |
|
981 // Initialize shared preference "ril.numRadioInterfaces" according to system |
|
982 // property. |
|
983 try { |
|
984 Services.prefs.setIntPref(kPrefRilNumRadioInterfaces, (function() { |
|
985 // When Gonk property "ro.moz.ril.numclients" is not set, return 1; if |
|
986 // explicitly set to any number larger-equal than 0, return num; else, return |
|
987 // 1 for compatibility. |
|
988 try { |
|
989 let numString = libcutils.property_get("ro.moz.ril.numclients", "1"); |
|
990 let num = parseInt(numString, 10); |
|
991 if (num >= 0) { |
|
992 return num; |
|
993 } |
|
994 } catch (e) {} |
|
995 |
|
996 return 1; |
|
997 })()); |
|
998 } catch (e) {} |
|
999 |
|
1000 function IccInfo() {} |
|
1001 IccInfo.prototype = { |
|
1002 iccType: null, |
|
1003 iccid: null, |
|
1004 mcc: null, |
|
1005 mnc: null, |
|
1006 spn: null, |
|
1007 isDisplayNetworkNameRequired: null, |
|
1008 isDisplaySpnRequired: null |
|
1009 }; |
|
1010 |
|
1011 function GsmIccInfo() {} |
|
1012 GsmIccInfo.prototype = { |
|
1013 __proto__: IccInfo.prototype, |
|
1014 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozGsmIccInfo]), |
|
1015 classID: GSMICCINFO_CID, |
|
1016 classInfo: XPCOMUtils.generateCI({ |
|
1017 classID: GSMICCINFO_CID, |
|
1018 classDescription: "MozGsmIccInfo", |
|
1019 flags: Ci.nsIClassInfo.DOM_OBJECT, |
|
1020 interfaces: [Ci.nsIDOMMozGsmIccInfo] |
|
1021 }), |
|
1022 |
|
1023 // nsIDOMMozGsmIccInfo |
|
1024 |
|
1025 msisdn: null |
|
1026 }; |
|
1027 |
|
1028 function CdmaIccInfo() {} |
|
1029 CdmaIccInfo.prototype = { |
|
1030 __proto__: IccInfo.prototype, |
|
1031 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozCdmaIccInfo]), |
|
1032 classID: CDMAICCINFO_CID, |
|
1033 classInfo: XPCOMUtils.generateCI({ |
|
1034 classID: CDMAICCINFO_CID, |
|
1035 classDescription: "MozCdmaIccInfo", |
|
1036 flags: Ci.nsIClassInfo.DOM_OBJECT, |
|
1037 interfaces: [Ci.nsIDOMMozCdmaIccInfo] |
|
1038 }), |
|
1039 |
|
1040 // nsIDOMMozCdmaIccInfo |
|
1041 |
|
1042 mdn: null, |
|
1043 prlVersion: 0 |
|
1044 }; |
|
1045 |
|
1046 function DataConnectionHandler(clientId, radioInterface) { |
|
1047 // Initial owning attributes. |
|
1048 this.clientId = clientId; |
|
1049 this.radioInterface = radioInterface; |
|
1050 this.dataCallSettings = { |
|
1051 oldEnabled: false, |
|
1052 enabled: false, |
|
1053 roamingEnabled: false |
|
1054 }; |
|
1055 this._dataCallbacks = []; |
|
1056 |
|
1057 // This matrix is used to keep all the APN settings. |
|
1058 // - |byApn| object makes it easier to get the corresponding APN setting |
|
1059 // via a given set of APN, user name and password. |
|
1060 // - |byType| object makes it easier to get the corresponding APN setting |
|
1061 // via a given APN type. |
|
1062 this.apnSettings = { |
|
1063 byType: {}, |
|
1064 byApn: {} |
|
1065 }; |
|
1066 } |
|
1067 DataConnectionHandler.prototype = { |
|
1068 clientId: 0, |
|
1069 radioInterface: null, |
|
1070 // Data calls setting. |
|
1071 dataCallSettings: null, |
|
1072 apnSettings: null, |
|
1073 |
|
1074 // Apn settings to be setup after data call are cleared. |
|
1075 _pendingApnSettings: null, |
|
1076 |
|
1077 debug: function(s) { |
|
1078 dump("-*- DataConnectionHandler[" + this.clientId + "]: " + s + "\n"); |
|
1079 }, |
|
1080 |
|
1081 shutdown: function() { |
|
1082 // Shutdown all RIL network interfaces |
|
1083 for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { |
|
1084 if (apnSetting.iface) { |
|
1085 apnSetting.iface.shutdown(); |
|
1086 } |
|
1087 } |
|
1088 this.clientId = null; |
|
1089 this.radioInterface = null; |
|
1090 }, |
|
1091 |
|
1092 /** |
|
1093 * Check if we get all necessary APN data. |
|
1094 */ |
|
1095 _validateApnSetting: function(apnSetting) { |
|
1096 return (apnSetting && |
|
1097 apnSetting.apn && |
|
1098 apnSetting.types && |
|
1099 apnSetting.types.length); |
|
1100 }, |
|
1101 |
|
1102 _deliverDataCallCallback: function(name, args) { |
|
1103 // We need to worry about callback registration state mutations during the |
|
1104 // callback firing. The behaviour we want is to *not* call any callbacks |
|
1105 // that are added during the firing and to *not* call any callbacks that are |
|
1106 // removed during the firing. To address this, we make a copy of the |
|
1107 // callback list before dispatching and then double-check that each callback |
|
1108 // is still registered before calling it. |
|
1109 let callbacks = this._dataCallbacks.slice(); |
|
1110 for (let callback of callbacks) { |
|
1111 if (this._dataCallbacks.indexOf(callback) == -1) { |
|
1112 continue; |
|
1113 } |
|
1114 try { |
|
1115 let handler = callback[name]; |
|
1116 if (typeof handler !== "function") { |
|
1117 throw new Error("No handler for " + name); |
|
1118 } |
|
1119 handler.apply(callback, args); |
|
1120 } catch (e) { |
|
1121 if (DEBUG) { |
|
1122 this.debug("callback handler for " + name + " threw an exception: " + e); |
|
1123 } |
|
1124 } |
|
1125 } |
|
1126 }, |
|
1127 |
|
1128 /** |
|
1129 * This function will do the following steps: |
|
1130 * 1. Clear the cached APN settings in the RIL. |
|
1131 * 2. Combine APN, user name, and password as the key of |byApn| object to |
|
1132 * refer to the corresponding APN setting. |
|
1133 * 3. Use APN type as the index of |byType| object to refer to the |
|
1134 * corresponding APN setting. |
|
1135 * 4. Create RilNetworkInterface for each APN setting created at step 2. |
|
1136 */ |
|
1137 _setupApnSettings: function(newApnSettings) { |
|
1138 if (!newApnSettings) { |
|
1139 return; |
|
1140 } |
|
1141 if (DEBUG) this.debug("setupApnSettings: " + JSON.stringify(newApnSettings)); |
|
1142 |
|
1143 // Unregister anything from iface and delete it. |
|
1144 for (let [, apnSetting] in Iterator(this.apnSettings.byApn)) { |
|
1145 if (apnSetting.iface.name in gNetworkManager.networkInterfaces) { |
|
1146 gNetworkManager.unregisterNetworkInterface(apnSetting.iface); |
|
1147 } |
|
1148 this.unregisterDataCallCallback(apnSetting.iface); |
|
1149 delete apnSetting.iface; |
|
1150 } |
|
1151 this.apnSettings.byApn = {}; |
|
1152 this.apnSettings.byType = {}; |
|
1153 |
|
1154 // Cache the APN settings by APNs and by types in the RIL. |
|
1155 for (let inputApnSetting of newApnSettings) { |
|
1156 if (!this._validateApnSetting(inputApnSetting)) { |
|
1157 continue; |
|
1158 } |
|
1159 |
|
1160 // Combine APN, user name, and password as the key of |byApn| object to |
|
1161 // refer to the corresponding APN setting. |
|
1162 let apnKey = inputApnSetting.apn + |
|
1163 (inputApnSetting.user || "") + |
|
1164 (inputApnSetting.password || ""); |
|
1165 |
|
1166 if (!this.apnSettings.byApn[apnKey]) { |
|
1167 this.apnSettings.byApn[apnKey] = inputApnSetting; |
|
1168 } else { |
|
1169 this.apnSettings.byApn[apnKey].types = |
|
1170 this.apnSettings.byApn[apnKey].types.concat(inputApnSetting.types); |
|
1171 } |
|
1172 |
|
1173 // Use APN type as the index of |byType| object to refer to the |
|
1174 // corresponding APN setting. |
|
1175 for (let type of inputApnSetting.types) { |
|
1176 this.apnSettings.byType[type] = this.apnSettings.byApn[apnKey]; |
|
1177 } |
|
1178 } |
|
1179 |
|
1180 // Create RilNetworkInterface for each APN setting that just cached. |
|
1181 for (let [, apnSetting] in Iterator(this.apnSettings.byApn)) { |
|
1182 apnSetting.iface = new RILNetworkInterface(this, apnSetting); |
|
1183 } |
|
1184 }, |
|
1185 |
|
1186 /** |
|
1187 * Check if all data is disconnected. |
|
1188 */ |
|
1189 allDataDisconnected: function() { |
|
1190 for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { |
|
1191 let iface = apnSetting.iface; |
|
1192 if (iface && iface.state != RIL.GECKO_NETWORK_STATE_UNKNOWN && |
|
1193 iface.state != RIL.GECKO_NETWORK_STATE_DISCONNECTED) { |
|
1194 return false; |
|
1195 } |
|
1196 } |
|
1197 return true; |
|
1198 }, |
|
1199 |
|
1200 /** |
|
1201 * Check if there is any activated data connection. |
|
1202 */ |
|
1203 anyDataConnected: function() { |
|
1204 for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { |
|
1205 let iface = apnSetting.iface; |
|
1206 if (iface && iface.state == RIL.GECKO_NETWORK_STATE_CONNECTED) { |
|
1207 return true; |
|
1208 } |
|
1209 } |
|
1210 return false; |
|
1211 }, |
|
1212 |
|
1213 updateApnSettings: function(newApnSettings) { |
|
1214 if (!newApnSettings) { |
|
1215 return; |
|
1216 } |
|
1217 if (this._pendingApnSettings) { |
|
1218 // Change of apn settings in process, just update to the newest. |
|
1219 this._pengingApnSettings = newApnSettings; |
|
1220 return; |
|
1221 } |
|
1222 |
|
1223 let isDeactivatingDataCalls = false; |
|
1224 // Clear the cached APN settings in the RIL. |
|
1225 for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { |
|
1226 // Clear all existing connections based on APN types. |
|
1227 for (let type of apnSetting.types) { |
|
1228 if (this.getDataCallStateByType(type) == |
|
1229 RIL.GECKO_NETWORK_STATE_CONNECTED) { |
|
1230 this.deactivateDataCallByType(type); |
|
1231 isDeactivatingDataCalls = true; |
|
1232 } |
|
1233 } |
|
1234 } |
|
1235 if (isDeactivatingDataCalls) { |
|
1236 // Defer apn settings setup until all data calls are cleared. |
|
1237 this._pendingApnSettings = newApnSettings; |
|
1238 return; |
|
1239 } |
|
1240 this._setupApnSettings(newApnSettings); |
|
1241 }, |
|
1242 |
|
1243 updateRILNetworkInterface: function() { |
|
1244 let apnSetting = this.apnSettings.byType.default; |
|
1245 if (!this._validateApnSetting(apnSetting)) { |
|
1246 if (DEBUG) { |
|
1247 this.debug("We haven't gotten completely the APN data."); |
|
1248 } |
|
1249 return; |
|
1250 } |
|
1251 |
|
1252 // This check avoids data call connection if the radio is not ready |
|
1253 // yet after toggling off airplane mode. |
|
1254 let rilContext = this.radioInterface.rilContext; |
|
1255 if (rilContext.radioState != RIL.GECKO_RADIOSTATE_READY) { |
|
1256 if (DEBUG) { |
|
1257 this.debug("RIL is not ready for data connection: radio's not ready"); |
|
1258 } |
|
1259 return; |
|
1260 } |
|
1261 |
|
1262 // We only watch at "ril.data.enabled" flag changes for connecting or |
|
1263 // disconnecting the data call. If the value of "ril.data.enabled" is |
|
1264 // true and any of the remaining flags change the setting application |
|
1265 // should turn this flag to false and then to true in order to reload |
|
1266 // the new values and reconnect the data call. |
|
1267 if (this.dataCallSettings.oldEnabled === this.dataCallSettings.enabled) { |
|
1268 if (DEBUG) { |
|
1269 this.debug("No changes for ril.data.enabled flag. Nothing to do."); |
|
1270 } |
|
1271 return; |
|
1272 } |
|
1273 |
|
1274 let defaultDataCallState = this.getDataCallStateByType("default"); |
|
1275 if (defaultDataCallState == RIL.GECKO_NETWORK_STATE_CONNECTING || |
|
1276 defaultDataCallState == RIL.GECKO_NETWORK_STATE_DISCONNECTING) { |
|
1277 if (DEBUG) { |
|
1278 this.debug("Nothing to do during connecting/disconnecting in progress."); |
|
1279 } |
|
1280 return; |
|
1281 } |
|
1282 |
|
1283 let dataInfo = rilContext.data; |
|
1284 let isRegistered = |
|
1285 dataInfo.state == RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED; |
|
1286 let haveDataConnection = |
|
1287 dataInfo.type != RIL.GECKO_MOBILE_CONNECTION_STATE_UNKNOWN; |
|
1288 if (!isRegistered || !haveDataConnection) { |
|
1289 if (DEBUG) { |
|
1290 this.debug("RIL is not ready for data connection: Phone's not " + |
|
1291 "registered or doesn't have data connection."); |
|
1292 } |
|
1293 return; |
|
1294 } |
|
1295 let wifi_active = false; |
|
1296 if (gNetworkManager.active && |
|
1297 gNetworkManager.active.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { |
|
1298 wifi_active = true; |
|
1299 } |
|
1300 |
|
1301 let defaultDataCallConnected = defaultDataCallState == |
|
1302 RIL.GECKO_NETWORK_STATE_CONNECTED; |
|
1303 if (defaultDataCallConnected && |
|
1304 (!this.dataCallSettings.enabled || |
|
1305 (dataInfo.roaming && !this.dataCallSettings.roamingEnabled))) { |
|
1306 if (DEBUG) { |
|
1307 this.debug("Data call settings: disconnect data call."); |
|
1308 } |
|
1309 this.deactivateDataCallByType("default"); |
|
1310 return; |
|
1311 } |
|
1312 |
|
1313 if (defaultDataCallConnected && wifi_active) { |
|
1314 if (DEBUG) { |
|
1315 this.debug("Disconnect data call when Wifi is connected."); |
|
1316 } |
|
1317 this.deactivateDataCallByType("default"); |
|
1318 return; |
|
1319 } |
|
1320 |
|
1321 if (!this.dataCallSettings.enabled || defaultDataCallConnected) { |
|
1322 if (DEBUG) { |
|
1323 this.debug("Data call settings: nothing to do."); |
|
1324 } |
|
1325 return; |
|
1326 } |
|
1327 if (dataInfo.roaming && !this.dataCallSettings.roamingEnabled) { |
|
1328 if (DEBUG) { |
|
1329 this.debug("We're roaming, but data roaming is disabled."); |
|
1330 } |
|
1331 return; |
|
1332 } |
|
1333 if (wifi_active) { |
|
1334 if (DEBUG) { |
|
1335 this.debug("Don't connect data call when Wifi is connected."); |
|
1336 } |
|
1337 return; |
|
1338 } |
|
1339 if (this._pendingApnSettings) { |
|
1340 if (DEBUG) this.debug("We're changing apn settings, ignore any changes."); |
|
1341 return; |
|
1342 } |
|
1343 |
|
1344 let detailedRadioState = rilContext.detailedRadioState; |
|
1345 if (gRadioEnabledController.isDeactivatingDataCalls() || |
|
1346 detailedRadioState == RIL.GECKO_DETAILED_RADIOSTATE_ENABLING || |
|
1347 detailedRadioState == RIL.GECKO_DETAILED_RADIOSTATE_DISABLING) { |
|
1348 // We're changing the radio power currently, ignore any changes. |
|
1349 return; |
|
1350 } |
|
1351 |
|
1352 if (DEBUG) { |
|
1353 this.debug("Data call settings: connect data call."); |
|
1354 } |
|
1355 this.setupDataCallByType("default"); |
|
1356 }, |
|
1357 |
|
1358 getDataCallStateByType: function(apnType) { |
|
1359 let apnSetting = this.apnSettings.byType[apnType]; |
|
1360 if (!apnSetting) { |
|
1361 return RIL.GECKO_NETWORK_STATE_UNKNOWN; |
|
1362 } |
|
1363 if (!apnSetting.iface.inConnectedTypes(apnType)) { |
|
1364 return RIL.GECKO_NETWORK_STATE_DISCONNECTED; |
|
1365 } |
|
1366 return apnSetting.iface.state; |
|
1367 }, |
|
1368 |
|
1369 setupDataCallByType: function(apnType) { |
|
1370 if (DEBUG) { |
|
1371 this.debug("setupDataCallByType: " + apnType); |
|
1372 } |
|
1373 let apnSetting = this.apnSettings.byType[apnType]; |
|
1374 if (!apnSetting) { |
|
1375 if (DEBUG) { |
|
1376 this.debug("No apn setting for type: " + apnType); |
|
1377 } |
|
1378 return; |
|
1379 } |
|
1380 |
|
1381 let dataInfo = this.radioInterface.rilContext.data; |
|
1382 if (dataInfo.state != RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED || |
|
1383 dataInfo.type == RIL.GECKO_MOBILE_CONNECTION_STATE_UNKNOWN) { |
|
1384 return; |
|
1385 } |
|
1386 |
|
1387 apnSetting.iface.connect(apnType); |
|
1388 // We just call connect() function, so this interface should be in |
|
1389 // connecting state. If this interface is already in connected state, we |
|
1390 // are sure that this interface have successfully established connection |
|
1391 // for other data call types before we call connect() function for current |
|
1392 // data call type. In this circumstance, we have to directly update the |
|
1393 // necessary data call and interface information to RILContentHelper |
|
1394 // and network manager for current data call type. |
|
1395 if (apnSetting.iface.connected) { |
|
1396 // Update the interface status via-registration if the interface has |
|
1397 // already been registered in the network manager. |
|
1398 if (apnSetting.iface.name in gNetworkManager.networkInterfaces) { |
|
1399 gNetworkManager.unregisterNetworkInterface(apnSetting.iface); |
|
1400 } |
|
1401 gNetworkManager.registerNetworkInterface(apnSetting.iface); |
|
1402 |
|
1403 Services.obs.notifyObservers(apnSetting.iface, |
|
1404 kNetworkInterfaceStateChangedTopic, |
|
1405 null); |
|
1406 } |
|
1407 }, |
|
1408 |
|
1409 deactivateDataCallByType: function(apnType) { |
|
1410 if (DEBUG) { |
|
1411 this.debug("deactivateDataCallByType: " + apnType); |
|
1412 } |
|
1413 let apnSetting = this.apnSettings.byType[apnType]; |
|
1414 if (!apnSetting) { |
|
1415 if (DEBUG) { |
|
1416 this.debug("No apn setting for type: " + apnType); |
|
1417 } |
|
1418 return; |
|
1419 } |
|
1420 |
|
1421 apnSetting.iface.disconnect(apnType); |
|
1422 // We just call disconnect() function, so this interface should be in |
|
1423 // disconnecting state. If this interface is still in connected state, we |
|
1424 // are sure that other data call types still need this connection of this |
|
1425 // interface. In this circumstance, we have to directly update the |
|
1426 // necessary data call and interface information to RILContentHelper |
|
1427 // and network manager for current data call type. |
|
1428 if (apnSetting.iface.connectedTypes.length && apnSetting.iface.connected) { |
|
1429 // Update the interface status via-registration if the interface has |
|
1430 // already been registered in the network manager. |
|
1431 if (apnSetting.iface.name in gNetworkManager.networkInterfaces) { |
|
1432 gNetworkManager.unregisterNetworkInterface(apnSetting.iface); |
|
1433 } |
|
1434 gNetworkManager.registerNetworkInterface(apnSetting.iface); |
|
1435 |
|
1436 Services.obs.notifyObservers(apnSetting.iface, |
|
1437 kNetworkInterfaceStateChangedTopic, |
|
1438 null); |
|
1439 } |
|
1440 }, |
|
1441 |
|
1442 deactivateDataCalls: function() { |
|
1443 let dataDisconnecting = false; |
|
1444 for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { |
|
1445 for (let type of apnSetting.types) { |
|
1446 if (this.getDataCallStateByType(type) == |
|
1447 RIL.GECKO_NETWORK_STATE_CONNECTED) { |
|
1448 this.deactivateDataCallByType(type); |
|
1449 dataDisconnecting = true; |
|
1450 } |
|
1451 } |
|
1452 } |
|
1453 |
|
1454 // No data calls exist. It's safe to proceed the pending radio power off |
|
1455 // request. |
|
1456 if (gRadioEnabledController.isDeactivatingDataCalls() && !dataDisconnecting) { |
|
1457 gRadioEnabledController.finishDeactivatingDataCalls(this.clientId); |
|
1458 } |
|
1459 }, |
|
1460 |
|
1461 registerDataCallCallback: function(callback) { |
|
1462 if (this._dataCallbacks.indexOf(callback) != -1) { |
|
1463 throw new Error("Already registered this callback: " + callback); |
|
1464 } |
|
1465 this._dataCallbacks.push(callback); |
|
1466 if (DEBUG) { |
|
1467 this.debug("Registering callback: " + callback); |
|
1468 } |
|
1469 }, |
|
1470 |
|
1471 unregisterDataCallCallback: function(callback) { |
|
1472 let index = this._dataCallbacks.indexOf(callback); |
|
1473 if (index != -1) { |
|
1474 this._dataCallbacks.splice(index, 1); |
|
1475 if (DEBUG) { |
|
1476 this.debug("Unregistering callback: " + callback); |
|
1477 } |
|
1478 } |
|
1479 }, |
|
1480 |
|
1481 /** |
|
1482 * Handle data errors. |
|
1483 */ |
|
1484 handleDataCallError: function(message) { |
|
1485 // Notify data call error only for data APN |
|
1486 let apnSetting = this.apnSettings && this.apnSettings.byType.default; |
|
1487 if (apnSetting) { |
|
1488 if (message.apn == apnSetting.apn && |
|
1489 apnSetting.iface.inConnectedTypes("default")) { |
|
1490 gMessageManager.sendMobileConnectionMessage("RIL:DataError", |
|
1491 this.clientId, message); |
|
1492 } |
|
1493 } |
|
1494 |
|
1495 this._deliverDataCallCallback("dataCallError", [message]); |
|
1496 }, |
|
1497 |
|
1498 /** |
|
1499 * Handle data call state changes. |
|
1500 */ |
|
1501 handleDataCallState: function(datacall) { |
|
1502 this._deliverDataCallCallback("dataCallStateChanged", [datacall]); |
|
1503 |
|
1504 // Process pending radio power off request after all data calls |
|
1505 // are disconnected. |
|
1506 if (datacall.state == RIL.GECKO_NETWORK_STATE_UNKNOWN && |
|
1507 this.allDataDisconnected()) { |
|
1508 if (gRadioEnabledController.isDeactivatingDataCalls()) { |
|
1509 if (DEBUG) { |
|
1510 this.debug("All data connections are disconnected."); |
|
1511 } |
|
1512 gRadioEnabledController.finishDeactivatingDataCalls(this.clientId); |
|
1513 } |
|
1514 |
|
1515 if (this._pendingApnSettings) { |
|
1516 if (DEBUG) { |
|
1517 this.debug("Setup pending apn settings."); |
|
1518 } |
|
1519 this._setupApnSettings(this._pendingApnSettings); |
|
1520 this._pendingApnSettings = null; |
|
1521 this.updateRILNetworkInterface(); |
|
1522 } |
|
1523 } |
|
1524 }, |
|
1525 }; |
|
1526 |
|
1527 function RadioInterfaceLayer() { |
|
1528 let workerMessenger = new WorkerMessenger(); |
|
1529 workerMessenger.init(); |
|
1530 |
|
1531 let numIfaces = this.numRadioInterfaces; |
|
1532 if (DEBUG) debug(numIfaces + " interfaces"); |
|
1533 this.radioInterfaces = []; |
|
1534 for (let clientId = 0; clientId < numIfaces; clientId++) { |
|
1535 this.radioInterfaces.push(new RadioInterface(clientId, workerMessenger)); |
|
1536 } |
|
1537 |
|
1538 Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); |
|
1539 |
|
1540 gMessageManager.init(this); |
|
1541 gRadioEnabledController.init(this); |
|
1542 gDataConnectionManager.init(this); |
|
1543 } |
|
1544 RadioInterfaceLayer.prototype = { |
|
1545 |
|
1546 classID: RADIOINTERFACELAYER_CID, |
|
1547 classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACELAYER_CID, |
|
1548 classDescription: "RadioInterfaceLayer", |
|
1549 interfaces: [Ci.nsIRadioInterfaceLayer]}), |
|
1550 |
|
1551 QueryInterface: XPCOMUtils.generateQI([Ci.nsIRadioInterfaceLayer, |
|
1552 Ci.nsIObserver]), |
|
1553 |
|
1554 /** |
|
1555 * nsIObserver interface methods. |
|
1556 */ |
|
1557 |
|
1558 observe: function(subject, topic, data) { |
|
1559 switch (topic) { |
|
1560 case NS_XPCOM_SHUTDOWN_OBSERVER_ID: |
|
1561 for (let radioInterface of this.radioInterfaces) { |
|
1562 radioInterface.shutdown(); |
|
1563 } |
|
1564 this.radioInterfaces = null; |
|
1565 Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); |
|
1566 break; |
|
1567 } |
|
1568 }, |
|
1569 |
|
1570 /** |
|
1571 * nsIRadioInterfaceLayer interface methods. |
|
1572 */ |
|
1573 |
|
1574 getRadioInterface: function(clientId) { |
|
1575 return this.radioInterfaces[clientId]; |
|
1576 }, |
|
1577 |
|
1578 setMicrophoneMuted: function(muted) { |
|
1579 for (let clientId = 0; clientId < this.numRadioInterfaces; clientId++) { |
|
1580 let radioInterface = this.radioInterfaces[clientId]; |
|
1581 radioInterface.workerMessenger.send("setMute", { muted: muted }); |
|
1582 } |
|
1583 } |
|
1584 }; |
|
1585 |
|
1586 XPCOMUtils.defineLazyGetter(RadioInterfaceLayer.prototype, |
|
1587 "numRadioInterfaces", function() { |
|
1588 try { |
|
1589 return Services.prefs.getIntPref(kPrefRilNumRadioInterfaces); |
|
1590 } catch(e) {} |
|
1591 |
|
1592 return 1; |
|
1593 }); |
|
1594 |
|
1595 function WorkerMessenger() { |
|
1596 // Initial owning attributes. |
|
1597 this.radioInterfaces = []; |
|
1598 this.tokenCallbackMap = {}; |
|
1599 |
|
1600 this.worker = new ChromeWorker("resource://gre/modules/ril_worker.js"); |
|
1601 this.worker.onerror = this.onerror.bind(this); |
|
1602 this.worker.onmessage = this.onmessage.bind(this); |
|
1603 } |
|
1604 WorkerMessenger.prototype = { |
|
1605 radioInterfaces: null, |
|
1606 worker: null, |
|
1607 |
|
1608 // This gets incremented each time we send out a message. |
|
1609 token: 1, |
|
1610 |
|
1611 // Maps tokens we send out with messages to the message callback. |
|
1612 tokenCallbackMap: null, |
|
1613 |
|
1614 init: function() { |
|
1615 let options = { |
|
1616 debug: DEBUG, |
|
1617 cellBroadcastDisabled: false, |
|
1618 clirMode: RIL.CLIR_DEFAULT, |
|
1619 quirks: { |
|
1620 callstateExtraUint32: |
|
1621 libcutils.property_get("ro.moz.ril.callstate_extra_int", "false") === "true", |
|
1622 v5Legacy: |
|
1623 libcutils.property_get("ro.moz.ril.v5_legacy", "true") === "true", |
|
1624 requestUseDialEmergencyCall: |
|
1625 libcutils.property_get("ro.moz.ril.dial_emergency_call", "false") === "true", |
|
1626 simAppStateExtraFields: |
|
1627 libcutils.property_get("ro.moz.ril.simstate_extra_field", "false") === "true", |
|
1628 extraUint2ndCall: |
|
1629 libcutils.property_get("ro.moz.ril.extra_int_2nd_call", "false") == "true", |
|
1630 haveQueryIccLockRetryCount: |
|
1631 libcutils.property_get("ro.moz.ril.query_icc_count", "false") == "true", |
|
1632 sendStkProfileDownload: |
|
1633 libcutils.property_get("ro.moz.ril.send_stk_profile_dl", "false") == "true", |
|
1634 dataRegistrationOnDemand: |
|
1635 libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true" |
|
1636 }, |
|
1637 rilEmergencyNumbers: libcutils.property_get("ril.ecclist") || |
|
1638 libcutils.property_get("ro.ril.ecclist") |
|
1639 }; |
|
1640 |
|
1641 try { |
|
1642 options.cellBroadcastDisabled = |
|
1643 Services.prefs.getBoolPref(kPrefCellBroadcastDisabled); |
|
1644 } catch(e) {} |
|
1645 |
|
1646 try { |
|
1647 options.clirMode = Services.prefs.getIntPref(kPrefClirModePreference); |
|
1648 } catch(e) {} |
|
1649 |
|
1650 this.send(null, "setInitialOptions", options); |
|
1651 }, |
|
1652 |
|
1653 debug: function(aClientId, aMessage) { |
|
1654 // We use the same debug subject with RadioInterface's here. |
|
1655 dump("-*- RadioInterface[" + aClientId + "]: " + aMessage + "\n"); |
|
1656 }, |
|
1657 |
|
1658 onerror: function(event) { |
|
1659 if (DEBUG) { |
|
1660 this.debug("X", "Got an error: " + event.filename + ":" + |
|
1661 event.lineno + ": " + event.message + "\n"); |
|
1662 } |
|
1663 event.preventDefault(); |
|
1664 }, |
|
1665 |
|
1666 /** |
|
1667 * Process the incoming message from the RIL worker. |
|
1668 */ |
|
1669 onmessage: function(event) { |
|
1670 let message = event.data; |
|
1671 let clientId = message.rilMessageClientId; |
|
1672 if (clientId === null) { |
|
1673 return; |
|
1674 } |
|
1675 |
|
1676 if (DEBUG) { |
|
1677 this.debug(clientId, "Received message from worker: " + JSON.stringify(message)); |
|
1678 } |
|
1679 |
|
1680 let token = message.rilMessageToken; |
|
1681 if (token == null) { |
|
1682 // That's an unsolicited message. Pass to RadioInterface directly. |
|
1683 let radioInterface = this.radioInterfaces[clientId]; |
|
1684 radioInterface.handleUnsolicitedWorkerMessage(message); |
|
1685 return; |
|
1686 } |
|
1687 |
|
1688 let callback = this.tokenCallbackMap[message.rilMessageToken]; |
|
1689 if (!callback) { |
|
1690 if (DEBUG) this.debug(clientId, "Ignore orphan token: " + message.rilMessageToken); |
|
1691 return; |
|
1692 } |
|
1693 |
|
1694 let keep = false; |
|
1695 try { |
|
1696 keep = callback(message); |
|
1697 } catch(e) { |
|
1698 if (DEBUG) this.debug(clientId, "callback throws an exception: " + e); |
|
1699 } |
|
1700 |
|
1701 if (!keep) { |
|
1702 delete this.tokenCallbackMap[message.rilMessageToken]; |
|
1703 } |
|
1704 }, |
|
1705 |
|
1706 registerClient: function(aClientId, aRadioInterface) { |
|
1707 if (DEBUG) this.debug(aClientId, "Starting RIL Worker"); |
|
1708 |
|
1709 // Keep a reference so that we can dispatch unsolicited messages to it. |
|
1710 this.radioInterfaces[aClientId] = aRadioInterface; |
|
1711 |
|
1712 this.send(null, "registerClient", { clientId: aClientId }); |
|
1713 gSystemWorkerManager.registerRilWorker(aClientId, this.worker); |
|
1714 }, |
|
1715 |
|
1716 /** |
|
1717 * Send arbitrary message to worker. |
|
1718 * |
|
1719 * @param rilMessageType |
|
1720 * A text message type. |
|
1721 * @param message [optional] |
|
1722 * An optional message object to send. |
|
1723 * @param callback [optional] |
|
1724 * An optional callback function which is called when worker replies |
|
1725 * with an message containing a "rilMessageToken" attribute of the |
|
1726 * same value we passed. This callback function accepts only one |
|
1727 * parameter -- the reply from worker. It also returns a boolean |
|
1728 * value true to keep current token-callback mapping and wait for |
|
1729 * another worker reply, or false to remove the mapping. |
|
1730 */ |
|
1731 send: function(clientId, rilMessageType, message, callback) { |
|
1732 message = message || {}; |
|
1733 |
|
1734 message.rilMessageClientId = clientId; |
|
1735 message.rilMessageToken = this.token; |
|
1736 this.token++; |
|
1737 |
|
1738 if (callback) { |
|
1739 // Only create the map if callback is provided. For sending a request |
|
1740 // and intentionally leaving the callback undefined, that reply will |
|
1741 // be dropped in |this.onmessage| because of that orphan token. |
|
1742 // |
|
1743 // For sending a request that never replied at all, we're fine with this |
|
1744 // because no callback shall be passed and we leave nothing to be cleaned |
|
1745 // up later. |
|
1746 this.tokenCallbackMap[message.rilMessageToken] = callback; |
|
1747 } |
|
1748 |
|
1749 message.rilMessageType = rilMessageType; |
|
1750 this.worker.postMessage(message); |
|
1751 }, |
|
1752 |
|
1753 /** |
|
1754 * Send message to worker and return worker reply to RILContentHelper. |
|
1755 * |
|
1756 * @param msg |
|
1757 * A message object from ppmm. |
|
1758 * @param rilMessageType |
|
1759 * A text string for worker message type. |
|
1760 * @param ipcType [optinal] |
|
1761 * A text string for ipc message type. "msg.name" if omitted. |
|
1762 * |
|
1763 * @TODO: Bug 815526 - deprecate RILContentHelper. |
|
1764 */ |
|
1765 sendWithIPCMessage: function(clientId, msg, rilMessageType, ipcType) { |
|
1766 this.send(clientId, rilMessageType, msg.json.data, (function(reply) { |
|
1767 ipcType = ipcType || msg.name; |
|
1768 msg.target.sendAsyncMessage(ipcType, { |
|
1769 clientId: clientId, |
|
1770 data: reply |
|
1771 }); |
|
1772 return false; |
|
1773 }).bind(this)); |
|
1774 } |
|
1775 }; |
|
1776 |
|
1777 function RadioInterface(aClientId, aWorkerMessenger) { |
|
1778 this.clientId = aClientId; |
|
1779 this.workerMessenger = { |
|
1780 send: aWorkerMessenger.send.bind(aWorkerMessenger, aClientId), |
|
1781 sendWithIPCMessage: |
|
1782 aWorkerMessenger.sendWithIPCMessage.bind(aWorkerMessenger, aClientId), |
|
1783 }; |
|
1784 aWorkerMessenger.registerClient(aClientId, this); |
|
1785 |
|
1786 this.supportedNetworkTypes = this.getSupportedNetworkTypes(); |
|
1787 |
|
1788 this.rilContext = { |
|
1789 radioState: RIL.GECKO_RADIOSTATE_UNAVAILABLE, |
|
1790 detailedRadioState: null, |
|
1791 cardState: RIL.GECKO_CARDSTATE_UNKNOWN, |
|
1792 networkSelectionMode: RIL.GECKO_NETWORK_SELECTION_UNKNOWN, |
|
1793 iccInfo: null, |
|
1794 imsi: null, |
|
1795 |
|
1796 // These objects implement the nsIDOMMozMobileConnectionInfo interface, |
|
1797 // although the actual implementation lives in the content process. So are |
|
1798 // the child attributes `network` and `cell`, which implement |
|
1799 // nsIDOMMozMobileNetworkInfo and nsIDOMMozMobileCellInfo respectively. |
|
1800 voice: {connected: false, |
|
1801 emergencyCallsOnly: false, |
|
1802 roaming: false, |
|
1803 network: null, |
|
1804 cell: null, |
|
1805 type: null, |
|
1806 signalStrength: null, |
|
1807 relSignalStrength: null}, |
|
1808 data: {connected: false, |
|
1809 emergencyCallsOnly: false, |
|
1810 roaming: false, |
|
1811 network: null, |
|
1812 cell: null, |
|
1813 type: null, |
|
1814 signalStrength: null, |
|
1815 relSignalStrength: null}, |
|
1816 }; |
|
1817 |
|
1818 this.voicemailInfo = { |
|
1819 number: null, |
|
1820 displayName: null |
|
1821 }; |
|
1822 |
|
1823 this.operatorInfo = {}; |
|
1824 |
|
1825 let lock = gSettingsService.createLock(); |
|
1826 |
|
1827 // Read the "time.clock.automatic-update.enabled" setting to see if |
|
1828 // we need to adjust the system clock time by NITZ or SNTP. |
|
1829 lock.get(kSettingsClockAutoUpdateEnabled, this); |
|
1830 |
|
1831 // Read the "time.timezone.automatic-update.enabled" setting to see if |
|
1832 // we need to adjust the system timezone by NITZ. |
|
1833 lock.get(kSettingsTimezoneAutoUpdateEnabled, this); |
|
1834 |
|
1835 // Set "time.clock.automatic-update.available" to false when starting up. |
|
1836 this.setClockAutoUpdateAvailable(false); |
|
1837 |
|
1838 // Set "time.timezone.automatic-update.available" to false when starting up. |
|
1839 this.setTimezoneAutoUpdateAvailable(false); |
|
1840 |
|
1841 // Read the Cell Broadcast Search List setting, string of integers or integer |
|
1842 // ranges separated by comma, to set listening channels. |
|
1843 lock.get(kSettingsCellBroadcastSearchList, this); |
|
1844 |
|
1845 Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false); |
|
1846 Services.obs.addObserver(this, kSysClockChangeObserverTopic, false); |
|
1847 Services.obs.addObserver(this, kScreenStateChangedTopic, false); |
|
1848 |
|
1849 Services.obs.addObserver(this, kNetworkConnStateChangedTopic, false); |
|
1850 Services.obs.addObserver(this, kNetworkActiveChangedTopic, false); |
|
1851 Services.prefs.addObserver(kPrefCellBroadcastDisabled, this, false); |
|
1852 |
|
1853 this.portAddressedSmsApps = {}; |
|
1854 this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this); |
|
1855 |
|
1856 this._receivedSmsSegmentsMap = {}; |
|
1857 |
|
1858 this._sntp = new Sntp(this.setClockBySntp.bind(this), |
|
1859 Services.prefs.getIntPref("network.sntp.maxRetryCount"), |
|
1860 Services.prefs.getIntPref("network.sntp.refreshPeriod"), |
|
1861 Services.prefs.getIntPref("network.sntp.timeout"), |
|
1862 Services.prefs.getCharPref("network.sntp.pools").split(";"), |
|
1863 Services.prefs.getIntPref("network.sntp.port")); |
|
1864 } |
|
1865 |
|
1866 RadioInterface.prototype = { |
|
1867 |
|
1868 classID: RADIOINTERFACE_CID, |
|
1869 classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACE_CID, |
|
1870 classDescription: "RadioInterface", |
|
1871 interfaces: [Ci.nsIRadioInterface]}), |
|
1872 |
|
1873 QueryInterface: XPCOMUtils.generateQI([Ci.nsIRadioInterface, |
|
1874 Ci.nsIObserver, |
|
1875 Ci.nsISettingsServiceCallback]), |
|
1876 |
|
1877 // A private wrapped WorkerMessenger instance. |
|
1878 workerMessenger: null, |
|
1879 |
|
1880 debug: function(s) { |
|
1881 dump("-*- RadioInterface[" + this.clientId + "]: " + s + "\n"); |
|
1882 }, |
|
1883 |
|
1884 shutdown: function() { |
|
1885 // Release the CPU wake lock for handling the received SMS. |
|
1886 this._releaseSmsHandledWakeLock(); |
|
1887 |
|
1888 Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic); |
|
1889 Services.obs.removeObserver(this, kSysClockChangeObserverTopic); |
|
1890 Services.obs.removeObserver(this, kScreenStateChangedTopic); |
|
1891 Services.obs.removeObserver(this, kNetworkConnStateChangedTopic); |
|
1892 Services.obs.removeObserver(this, kNetworkActiveChangedTopic); |
|
1893 }, |
|
1894 |
|
1895 /** |
|
1896 * A utility function to copy objects. The srcInfo may contain |
|
1897 * "rilMessageType", should ignore it. |
|
1898 */ |
|
1899 updateInfo: function(srcInfo, destInfo) { |
|
1900 for (let key in srcInfo) { |
|
1901 if (key === "rilMessageType") { |
|
1902 continue; |
|
1903 } |
|
1904 destInfo[key] = srcInfo[key]; |
|
1905 } |
|
1906 }, |
|
1907 |
|
1908 /** |
|
1909 * A utility function to compare objects. The srcInfo may contain |
|
1910 * "rilMessageType", should ignore it. |
|
1911 */ |
|
1912 isInfoChanged: function(srcInfo, destInfo) { |
|
1913 if (!destInfo) { |
|
1914 return true; |
|
1915 } |
|
1916 |
|
1917 for (let key in srcInfo) { |
|
1918 if (key === "rilMessageType") { |
|
1919 continue; |
|
1920 } |
|
1921 if (srcInfo[key] !== destInfo[key]) { |
|
1922 return true; |
|
1923 } |
|
1924 } |
|
1925 |
|
1926 return false; |
|
1927 }, |
|
1928 |
|
1929 /** |
|
1930 * A utility function to get supportedNetworkTypes from system property |
|
1931 */ |
|
1932 getSupportedNetworkTypes: function() { |
|
1933 let key = "ro.moz.ril." + this.clientId + ".network_types"; |
|
1934 let supportedNetworkTypes = libcutils.property_get(key, "").split(","); |
|
1935 for (let type of supportedNetworkTypes) { |
|
1936 // If the value in system property is not valid, use the default one which |
|
1937 // is defined in ril_consts.js. |
|
1938 if (RIL.GECKO_SUPPORTED_NETWORK_TYPES.indexOf(type) < 0) { |
|
1939 if (DEBUG) this.debug("Unknown network type: " + type); |
|
1940 supportedNetworkTypes = |
|
1941 RIL.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT.split(","); |
|
1942 break; |
|
1943 } |
|
1944 } |
|
1945 if (DEBUG) this.debug("Supported Network Types: " + supportedNetworkTypes); |
|
1946 return supportedNetworkTypes; |
|
1947 }, |
|
1948 |
|
1949 /** |
|
1950 * Process a message from the content process. |
|
1951 */ |
|
1952 receiveMessage: function(msg) { |
|
1953 switch (msg.name) { |
|
1954 case "RIL:GetRilContext": |
|
1955 // This message is sync. |
|
1956 return this.rilContext; |
|
1957 case "RIL:GetLastKnownNetwork": |
|
1958 // This message is sync. |
|
1959 return this._lastKnownNetwork; |
|
1960 case "RIL:GetLastKnownHomeNetwork": |
|
1961 // This message is sync. |
|
1962 return this._lastKnownHomeNetwork; |
|
1963 case "RIL:GetAvailableNetworks": |
|
1964 this.workerMessenger.sendWithIPCMessage(msg, "getAvailableNetworks"); |
|
1965 break; |
|
1966 case "RIL:SelectNetwork": |
|
1967 this.workerMessenger.sendWithIPCMessage(msg, "selectNetwork"); |
|
1968 break; |
|
1969 case "RIL:SelectNetworkAuto": |
|
1970 this.workerMessenger.sendWithIPCMessage(msg, "selectNetworkAuto"); |
|
1971 break; |
|
1972 case "RIL:SetPreferredNetworkType": |
|
1973 this.setPreferredNetworkType(msg.target, msg.json.data); |
|
1974 break; |
|
1975 case "RIL:GetPreferredNetworkType": |
|
1976 this.getPreferredNetworkType(msg.target, msg.json.data); |
|
1977 break; |
|
1978 case "RIL:GetCardLockState": |
|
1979 this.workerMessenger.sendWithIPCMessage(msg, "iccGetCardLockState", |
|
1980 "RIL:CardLockResult"); |
|
1981 break; |
|
1982 case "RIL:UnlockCardLock": |
|
1983 this.workerMessenger.sendWithIPCMessage(msg, "iccUnlockCardLock", |
|
1984 "RIL:CardLockResult"); |
|
1985 break; |
|
1986 case "RIL:SetCardLock": |
|
1987 this.workerMessenger.sendWithIPCMessage(msg, "iccSetCardLock", |
|
1988 "RIL:CardLockResult"); |
|
1989 break; |
|
1990 case "RIL:GetCardLockRetryCount": |
|
1991 this.workerMessenger.sendWithIPCMessage(msg, "iccGetCardLockRetryCount", |
|
1992 "RIL:CardLockRetryCount"); |
|
1993 break; |
|
1994 case "RIL:SendMMI": |
|
1995 this.sendMMI(msg.target, msg.json.data); |
|
1996 break; |
|
1997 case "RIL:CancelMMI": |
|
1998 this.workerMessenger.sendWithIPCMessage(msg, "cancelUSSD"); |
|
1999 break; |
|
2000 case "RIL:SendStkResponse": |
|
2001 this.workerMessenger.send("sendStkTerminalResponse", msg.json.data); |
|
2002 break; |
|
2003 case "RIL:SendStkMenuSelection": |
|
2004 this.workerMessenger.send("sendStkMenuSelection", msg.json.data); |
|
2005 break; |
|
2006 case "RIL:SendStkTimerExpiration": |
|
2007 this.workerMessenger.send("sendStkTimerExpiration", msg.json.data); |
|
2008 break; |
|
2009 case "RIL:SendStkEventDownload": |
|
2010 this.workerMessenger.send("sendStkEventDownload", msg.json.data); |
|
2011 break; |
|
2012 case "RIL:IccOpenChannel": |
|
2013 this.workerMessenger.sendWithIPCMessage(msg, "iccOpenChannel"); |
|
2014 break; |
|
2015 case "RIL:IccCloseChannel": |
|
2016 this.workerMessenger.sendWithIPCMessage(msg, "iccCloseChannel"); |
|
2017 break; |
|
2018 case "RIL:IccExchangeAPDU": |
|
2019 this.workerMessenger.sendWithIPCMessage(msg, "iccExchangeAPDU"); |
|
2020 break; |
|
2021 case "RIL:ReadIccContacts": |
|
2022 this.workerMessenger.sendWithIPCMessage(msg, "readICCContacts"); |
|
2023 break; |
|
2024 case "RIL:UpdateIccContact": |
|
2025 this.workerMessenger.sendWithIPCMessage(msg, "updateICCContact"); |
|
2026 break; |
|
2027 case "RIL:MatchMvno": |
|
2028 this.matchMvno(msg.target, msg.json.data); |
|
2029 break; |
|
2030 case "RIL:SetCallForwardingOptions": |
|
2031 this.setCallForwardingOptions(msg.target, msg.json.data); |
|
2032 break; |
|
2033 case "RIL:GetCallForwardingOptions": |
|
2034 this.workerMessenger.sendWithIPCMessage(msg, "queryCallForwardStatus"); |
|
2035 break; |
|
2036 case "RIL:SetCallBarringOptions": |
|
2037 this.workerMessenger.sendWithIPCMessage(msg, "setCallBarring"); |
|
2038 break; |
|
2039 case "RIL:GetCallBarringOptions": |
|
2040 this.workerMessenger.sendWithIPCMessage(msg, "queryCallBarringStatus"); |
|
2041 break; |
|
2042 case "RIL:ChangeCallBarringPassword": |
|
2043 this.workerMessenger.sendWithIPCMessage(msg, "changeCallBarringPassword"); |
|
2044 break; |
|
2045 case "RIL:SetCallWaitingOptions": |
|
2046 this.workerMessenger.sendWithIPCMessage(msg, "setCallWaiting"); |
|
2047 break; |
|
2048 case "RIL:GetCallWaitingOptions": |
|
2049 this.workerMessenger.sendWithIPCMessage(msg, "queryCallWaiting"); |
|
2050 break; |
|
2051 case "RIL:SetCallingLineIdRestriction": |
|
2052 this.setCallingLineIdRestriction(msg.target, msg.json.data); |
|
2053 break; |
|
2054 case "RIL:GetCallingLineIdRestriction": |
|
2055 this.workerMessenger.sendWithIPCMessage(msg, "getCLIR"); |
|
2056 break; |
|
2057 case "RIL:ExitEmergencyCbMode": |
|
2058 this.workerMessenger.sendWithIPCMessage(msg, "exitEmergencyCbMode"); |
|
2059 break; |
|
2060 case "RIL:SetRadioEnabled": |
|
2061 this.setRadioEnabled(msg.target, msg.json.data); |
|
2062 break; |
|
2063 case "RIL:GetVoicemailInfo": |
|
2064 // This message is sync. |
|
2065 return this.voicemailInfo; |
|
2066 case "RIL:SetRoamingPreference": |
|
2067 this.workerMessenger.sendWithIPCMessage(msg, "setRoamingPreference"); |
|
2068 break; |
|
2069 case "RIL:GetRoamingPreference": |
|
2070 this.workerMessenger.sendWithIPCMessage(msg, "queryRoamingPreference"); |
|
2071 break; |
|
2072 case "RIL:SetVoicePrivacyMode": |
|
2073 this.workerMessenger.sendWithIPCMessage(msg, "setVoicePrivacyMode"); |
|
2074 break; |
|
2075 case "RIL:GetVoicePrivacyMode": |
|
2076 this.workerMessenger.sendWithIPCMessage(msg, "queryVoicePrivacyMode"); |
|
2077 break; |
|
2078 case "RIL:GetSupportedNetworkTypes": |
|
2079 // This message is sync. |
|
2080 return this.supportedNetworkTypes; |
|
2081 } |
|
2082 return null; |
|
2083 }, |
|
2084 |
|
2085 handleUnsolicitedWorkerMessage: function(message) { |
|
2086 let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); |
|
2087 switch (message.rilMessageType) { |
|
2088 case "callRing": |
|
2089 gTelephonyProvider.notifyCallRing(); |
|
2090 break; |
|
2091 case "callStateChange": |
|
2092 gTelephonyProvider.notifyCallStateChanged(this.clientId, message.call); |
|
2093 break; |
|
2094 case "callDisconnected": |
|
2095 gTelephonyProvider.notifyCallDisconnected(this.clientId, message.call); |
|
2096 break; |
|
2097 case "conferenceCallStateChanged": |
|
2098 gTelephonyProvider.notifyConferenceCallStateChanged(message.state); |
|
2099 break; |
|
2100 case "cdmaCallWaiting": |
|
2101 gTelephonyProvider.notifyCdmaCallWaiting(this.clientId, message.number); |
|
2102 break; |
|
2103 case "suppSvcNotification": |
|
2104 gTelephonyProvider.notifySupplementaryService(this.clientId, |
|
2105 message.callIndex, |
|
2106 message.notification); |
|
2107 break; |
|
2108 case "datacallerror": |
|
2109 connHandler.handleDataCallError(message); |
|
2110 break; |
|
2111 case "datacallstatechange": |
|
2112 let addresses = []; |
|
2113 for (let i = 0; i < message.addresses.length; i++) { |
|
2114 let [address, prefixLength] = message.addresses[i].split("/"); |
|
2115 // From AOSP hardware/ril/include/telephony/ril.h, that address prefix |
|
2116 // is said to be OPTIONAL, but we never met such case before. |
|
2117 addresses.push({ |
|
2118 address: address, |
|
2119 prefixLength: prefixLength ? parseInt(prefixLength, 10) : 0 |
|
2120 }); |
|
2121 } |
|
2122 message.addresses = addresses; |
|
2123 connHandler.handleDataCallState(message); |
|
2124 break; |
|
2125 case "emergencyCbModeChange": |
|
2126 this.handleEmergencyCbModeChange(message); |
|
2127 break; |
|
2128 case "networkinfochanged": |
|
2129 this.updateNetworkInfo(message); |
|
2130 break; |
|
2131 case "networkselectionmodechange": |
|
2132 this.updateNetworkSelectionMode(message); |
|
2133 break; |
|
2134 case "voiceregistrationstatechange": |
|
2135 this.updateVoiceConnection(message); |
|
2136 break; |
|
2137 case "dataregistrationstatechange": |
|
2138 this.updateDataConnection(message); |
|
2139 break; |
|
2140 case "signalstrengthchange": |
|
2141 this.handleSignalStrengthChange(message); |
|
2142 break; |
|
2143 case "operatorchange": |
|
2144 this.handleOperatorChange(message); |
|
2145 break; |
|
2146 case "otastatuschange": |
|
2147 this.handleOtaStatus(message); |
|
2148 break; |
|
2149 case "radiostatechange": |
|
2150 this.handleRadioStateChange(message); |
|
2151 break; |
|
2152 case "cardstatechange": |
|
2153 this.rilContext.cardState = message.cardState; |
|
2154 gRadioEnabledController.receiveCardState(this.clientId); |
|
2155 gMessageManager.sendIccMessage("RIL:CardStateChanged", |
|
2156 this.clientId, message); |
|
2157 break; |
|
2158 case "sms-received": |
|
2159 this.handleSmsMultipart(message); |
|
2160 break; |
|
2161 case "cellbroadcast-received": |
|
2162 message.timestamp = Date.now(); |
|
2163 gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived", |
|
2164 this.clientId, message); |
|
2165 break; |
|
2166 case "nitzTime": |
|
2167 this.handleNitzTime(message); |
|
2168 break; |
|
2169 case "iccinfochange": |
|
2170 this.handleIccInfoChange(message); |
|
2171 break; |
|
2172 case "iccimsi": |
|
2173 this.rilContext.imsi = message.imsi; |
|
2174 break; |
|
2175 case "iccmbdn": |
|
2176 this.handleIccMbdn(message); |
|
2177 break; |
|
2178 case "iccmwis": |
|
2179 gMessageManager.sendVoicemailMessage("RIL:VoicemailNotification", |
|
2180 this.clientId, message.mwi); |
|
2181 break; |
|
2182 case "USSDReceived": |
|
2183 if (DEBUG) this.debug("USSDReceived " + JSON.stringify(message)); |
|
2184 this.handleUSSDReceived(message); |
|
2185 break; |
|
2186 case "stkcommand": |
|
2187 this.handleStkProactiveCommand(message); |
|
2188 break; |
|
2189 case "stksessionend": |
|
2190 gMessageManager.sendIccMessage("RIL:StkSessionEnd", this.clientId, null); |
|
2191 break; |
|
2192 case "exitEmergencyCbMode": |
|
2193 this.handleExitEmergencyCbMode(message); |
|
2194 break; |
|
2195 case "cdma-info-rec-received": |
|
2196 if (DEBUG) this.debug("cdma-info-rec-received: " + JSON.stringify(message)); |
|
2197 gSystemMessenger.broadcastMessage("cdma-info-rec-received", message); |
|
2198 break; |
|
2199 default: |
|
2200 throw new Error("Don't know about this message type: " + |
|
2201 message.rilMessageType); |
|
2202 } |
|
2203 }, |
|
2204 |
|
2205 /** |
|
2206 * Get phone number from iccInfo. |
|
2207 * |
|
2208 * If the icc card is gsm card, the phone number is in msisdn. |
|
2209 * @see nsIDOMMozGsmIccInfo |
|
2210 * |
|
2211 * Otherwise, the phone number is in mdn. |
|
2212 * @see nsIDOMMozCdmaIccInfo |
|
2213 */ |
|
2214 getPhoneNumber: function() { |
|
2215 let iccInfo = this.rilContext.iccInfo; |
|
2216 |
|
2217 if (!iccInfo) { |
|
2218 return null; |
|
2219 } |
|
2220 |
|
2221 // After moving SMS code out of RadioInterfaceLayer, we could use |
|
2222 // |iccInfo instanceof Ci.nsIDOMMozGsmIccInfo| here. |
|
2223 // TODO: Bug 873351 - B2G SMS: move SMS code out of RadioInterfaceLayer to |
|
2224 // SmsService |
|
2225 let number = (iccInfo instanceof GsmIccInfo) ? iccInfo.msisdn : iccInfo.mdn; |
|
2226 |
|
2227 // Workaround an xpconnect issue with undefined string objects. |
|
2228 // See bug 808220 |
|
2229 if (number === undefined || number === "undefined") { |
|
2230 return null; |
|
2231 } |
|
2232 |
|
2233 return number; |
|
2234 }, |
|
2235 |
|
2236 /** |
|
2237 * A utility function to get the ICC ID of the SIM card (if installed). |
|
2238 */ |
|
2239 getIccId: function() { |
|
2240 let iccInfo = this.rilContext.iccInfo; |
|
2241 |
|
2242 if (!iccInfo) { |
|
2243 return null; |
|
2244 } |
|
2245 |
|
2246 let iccId = iccInfo.iccid; |
|
2247 |
|
2248 // Workaround an xpconnect issue with undefined string objects. |
|
2249 // See bug 808220 |
|
2250 if (iccId === undefined || iccId === "undefined") { |
|
2251 return null; |
|
2252 } |
|
2253 |
|
2254 return iccId; |
|
2255 }, |
|
2256 |
|
2257 // Matches the mvnoData pattern with imsi. Characters 'x' and 'X' are skipped |
|
2258 // and not compared. E.g., if the mvnoData passed is '310260x10xxxxxx', |
|
2259 // then the function returns true only if imsi has the same first 6 digits, |
|
2260 // 8th and 9th digit. |
|
2261 isImsiMatches: function(mvnoData) { |
|
2262 let imsi = this.rilContext.imsi; |
|
2263 |
|
2264 // This should not be an error, but a mismatch. |
|
2265 if (mvnoData.length > imsi.length) { |
|
2266 return false; |
|
2267 } |
|
2268 |
|
2269 for (let i = 0; i < mvnoData.length; i++) { |
|
2270 let c = mvnoData[i]; |
|
2271 if ((c !== 'x') && (c !== 'X') && (c !== imsi[i])) { |
|
2272 return false; |
|
2273 } |
|
2274 } |
|
2275 return true; |
|
2276 }, |
|
2277 |
|
2278 matchMvno: function(target, message) { |
|
2279 if (DEBUG) this.debug("matchMvno: " + JSON.stringify(message)); |
|
2280 |
|
2281 if (!message || !message.mvnoType || !message.mvnoData) { |
|
2282 message.errorMsg = RIL.GECKO_ERROR_INVALID_PARAMETER; |
|
2283 } |
|
2284 // Currently we only support imsi matching. |
|
2285 if (message.mvnoType != "imsi") { |
|
2286 message.errorMsg = RIL.GECKO_ERROR_MODE_NOT_SUPPORTED; |
|
2287 } |
|
2288 // Fire error if mvnoType is imsi but imsi is not available. |
|
2289 if (!this.rilContext.imsi) { |
|
2290 message.errorMsg = RIL.GECKO_ERROR_GENERIC_FAILURE; |
|
2291 } |
|
2292 |
|
2293 if (!message.errorMsg) { |
|
2294 message.result = this.isImsiMatches(message.mvnoData); |
|
2295 } |
|
2296 |
|
2297 target.sendAsyncMessage("RIL:MatchMvno", { |
|
2298 clientId: this.clientId, |
|
2299 data: message |
|
2300 }); |
|
2301 }, |
|
2302 |
|
2303 updateNetworkInfo: function(message) { |
|
2304 let voiceMessage = message[RIL.NETWORK_INFO_VOICE_REGISTRATION_STATE]; |
|
2305 let dataMessage = message[RIL.NETWORK_INFO_DATA_REGISTRATION_STATE]; |
|
2306 let operatorMessage = message[RIL.NETWORK_INFO_OPERATOR]; |
|
2307 let selectionMessage = message[RIL.NETWORK_INFO_NETWORK_SELECTION_MODE]; |
|
2308 let signalMessage = message[RIL.NETWORK_INFO_SIGNAL]; |
|
2309 |
|
2310 // Batch the *InfoChanged messages together |
|
2311 if (voiceMessage) { |
|
2312 this.updateVoiceConnection(voiceMessage, true); |
|
2313 } |
|
2314 |
|
2315 if (dataMessage) { |
|
2316 this.updateDataConnection(dataMessage, true); |
|
2317 } |
|
2318 |
|
2319 if (operatorMessage) { |
|
2320 this.handleOperatorChange(operatorMessage, true); |
|
2321 } |
|
2322 |
|
2323 if (signalMessage) { |
|
2324 this.handleSignalStrengthChange(signalMessage, true); |
|
2325 } |
|
2326 |
|
2327 let voice = this.rilContext.voice; |
|
2328 let data = this.rilContext.data; |
|
2329 |
|
2330 this.checkRoamingBetweenOperators(voice); |
|
2331 this.checkRoamingBetweenOperators(data); |
|
2332 |
|
2333 if (voiceMessage || operatorMessage || signalMessage) { |
|
2334 gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", |
|
2335 this.clientId, voice); |
|
2336 } |
|
2337 if (dataMessage || operatorMessage || signalMessage) { |
|
2338 gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", |
|
2339 this.clientId, data); |
|
2340 } |
|
2341 |
|
2342 if (selectionMessage) { |
|
2343 this.updateNetworkSelectionMode(selectionMessage); |
|
2344 } |
|
2345 }, |
|
2346 |
|
2347 /** |
|
2348 * Fix the roaming. RIL can report roaming in some case it is not |
|
2349 * really the case. See bug 787967 |
|
2350 * |
|
2351 * @param registration The voiceMessage or dataMessage from which the |
|
2352 * roaming state will be changed (maybe, if needed). |
|
2353 */ |
|
2354 checkRoamingBetweenOperators: function(registration) { |
|
2355 let iccInfo = this.rilContext.iccInfo; |
|
2356 let operator = registration.network; |
|
2357 let state = registration.state; |
|
2358 |
|
2359 if (!iccInfo || !operator || |
|
2360 state != RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) { |
|
2361 return; |
|
2362 } |
|
2363 |
|
2364 let spn = iccInfo.spn && iccInfo.spn.toLowerCase(); |
|
2365 let longName = operator.longName && operator.longName.toLowerCase(); |
|
2366 let shortName = operator.shortName && operator.shortName.toLowerCase(); |
|
2367 |
|
2368 let equalsLongName = longName && (spn == longName); |
|
2369 let equalsShortName = shortName && (spn == shortName); |
|
2370 let equalsMcc = iccInfo.mcc == operator.mcc; |
|
2371 |
|
2372 registration.roaming = registration.roaming && |
|
2373 !(equalsMcc && (equalsLongName || equalsShortName)); |
|
2374 }, |
|
2375 |
|
2376 /** |
|
2377 * Handle data connection changes. |
|
2378 * |
|
2379 * @param newInfo The new voice connection information. |
|
2380 * @param batch When batch is true, the RIL:VoiceInfoChanged message will |
|
2381 * not be sent. |
|
2382 */ |
|
2383 updateVoiceConnection: function(newInfo, batch) { |
|
2384 let voiceInfo = this.rilContext.voice; |
|
2385 voiceInfo.state = newInfo.state; |
|
2386 voiceInfo.connected = newInfo.connected; |
|
2387 voiceInfo.roaming = newInfo.roaming; |
|
2388 voiceInfo.emergencyCallsOnly = newInfo.emergencyCallsOnly; |
|
2389 voiceInfo.type = newInfo.type; |
|
2390 |
|
2391 // Make sure we also reset the operator and signal strength information |
|
2392 // if we drop off the network. |
|
2393 if (newInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) { |
|
2394 voiceInfo.cell = null; |
|
2395 voiceInfo.network = null; |
|
2396 voiceInfo.signalStrength = null; |
|
2397 voiceInfo.relSignalStrength = null; |
|
2398 } else { |
|
2399 voiceInfo.cell = newInfo.cell; |
|
2400 voiceInfo.network = this.operatorInfo; |
|
2401 } |
|
2402 |
|
2403 if (!batch) { |
|
2404 gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", |
|
2405 this.clientId, voiceInfo); |
|
2406 } |
|
2407 }, |
|
2408 |
|
2409 /** |
|
2410 * Handle the data connection's state has changed. |
|
2411 * |
|
2412 * @param newInfo The new data connection information. |
|
2413 * @param batch When batch is true, the RIL:DataInfoChanged message will |
|
2414 * not be sent. |
|
2415 */ |
|
2416 updateDataConnection: function(newInfo, batch) { |
|
2417 let dataInfo = this.rilContext.data; |
|
2418 dataInfo.state = newInfo.state; |
|
2419 dataInfo.roaming = newInfo.roaming; |
|
2420 dataInfo.emergencyCallsOnly = newInfo.emergencyCallsOnly; |
|
2421 dataInfo.type = newInfo.type; |
|
2422 // For the data connection, the `connected` flag indicates whether |
|
2423 // there's an active data call. |
|
2424 dataInfo.connected = false; |
|
2425 if (gNetworkManager.active && |
|
2426 gNetworkManager.active.type === |
|
2427 Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE && |
|
2428 gNetworkManager.active.serviceId === this.clientId) { |
|
2429 dataInfo.connected = true; |
|
2430 } |
|
2431 |
|
2432 // Make sure we also reset the operator and signal strength information |
|
2433 // if we drop off the network. |
|
2434 if (newInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) { |
|
2435 dataInfo.cell = null; |
|
2436 dataInfo.network = null; |
|
2437 dataInfo.signalStrength = null; |
|
2438 dataInfo.relSignalStrength = null; |
|
2439 } else { |
|
2440 dataInfo.cell = newInfo.cell; |
|
2441 dataInfo.network = this.operatorInfo; |
|
2442 } |
|
2443 |
|
2444 if (!batch) { |
|
2445 gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", |
|
2446 this.clientId, dataInfo); |
|
2447 } |
|
2448 |
|
2449 let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); |
|
2450 connHandler.updateRILNetworkInterface(); |
|
2451 }, |
|
2452 |
|
2453 getPreferredNetworkType: function(target, message) { |
|
2454 this.workerMessenger.send("getPreferredNetworkType", message, (function(response) { |
|
2455 if (response.success) { |
|
2456 response.type = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[response.networkType]; |
|
2457 } |
|
2458 |
|
2459 target.sendAsyncMessage("RIL:GetPreferredNetworkType", { |
|
2460 clientId: this.clientId, |
|
2461 data: response |
|
2462 }); |
|
2463 return false; |
|
2464 }).bind(this)); |
|
2465 }, |
|
2466 |
|
2467 setPreferredNetworkType: function(target, message) { |
|
2468 let networkType = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.indexOf(message.type); |
|
2469 if (networkType < 0) { |
|
2470 message.errorMsg = RIL.GECKO_ERROR_INVALID_PARAMETER; |
|
2471 target.sendAsyncMessage("RIL:SetPreferredNetworkType", { |
|
2472 clientId: this.clientId, |
|
2473 data: message |
|
2474 }); |
|
2475 return false; |
|
2476 } |
|
2477 message.networkType = networkType; |
|
2478 |
|
2479 this.workerMessenger.send("setPreferredNetworkType", message, (function(response) { |
|
2480 target.sendAsyncMessage("RIL:SetPreferredNetworkType", { |
|
2481 clientId: this.clientId, |
|
2482 data: response |
|
2483 }); |
|
2484 return false; |
|
2485 }).bind(this)); |
|
2486 }, |
|
2487 |
|
2488 setCellBroadcastSearchList: function(newSearchList) { |
|
2489 if ((newSearchList == this._cellBroadcastSearchList) || |
|
2490 (newSearchList && this._cellBroadcastSearchList && |
|
2491 newSearchList.gsm == this._cellBroadcastSearchList.gsm && |
|
2492 newSearchList.cdma == this._cellBroadcastSearchList.cdma)) { |
|
2493 return; |
|
2494 } |
|
2495 |
|
2496 this.workerMessenger.send("setCellBroadcastSearchList", |
|
2497 { searchList: newSearchList }, |
|
2498 (function callback(response) { |
|
2499 if (!response.success) { |
|
2500 let lock = gSettingsService.createLock(); |
|
2501 lock.set(kSettingsCellBroadcastSearchList, |
|
2502 this._cellBroadcastSearchList, null); |
|
2503 } else { |
|
2504 this._cellBroadcastSearchList = response.searchList; |
|
2505 } |
|
2506 |
|
2507 return false; |
|
2508 }).bind(this)); |
|
2509 }, |
|
2510 |
|
2511 /** |
|
2512 * Handle signal strength changes. |
|
2513 * |
|
2514 * @param message The new signal strength. |
|
2515 * @param batch When batch is true, the RIL:VoiceInfoChanged and |
|
2516 * RIL:DataInfoChanged message will not be sent. |
|
2517 */ |
|
2518 handleSignalStrengthChange: function(message, batch) { |
|
2519 let voiceInfo = this.rilContext.voice; |
|
2520 // If the voice is not registered, need not to update signal information. |
|
2521 if (voiceInfo.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED && |
|
2522 this.isInfoChanged(message.voice, voiceInfo)) { |
|
2523 this.updateInfo(message.voice, voiceInfo); |
|
2524 if (!batch) { |
|
2525 gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", |
|
2526 this.clientId, voiceInfo); |
|
2527 } |
|
2528 } |
|
2529 |
|
2530 let dataInfo = this.rilContext.data; |
|
2531 // If the data is not registered, need not to update signal information. |
|
2532 if (dataInfo.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED && |
|
2533 this.isInfoChanged(message.data, dataInfo)) { |
|
2534 this.updateInfo(message.data, dataInfo); |
|
2535 if (!batch) { |
|
2536 gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", |
|
2537 this.clientId, dataInfo); |
|
2538 } |
|
2539 } |
|
2540 }, |
|
2541 |
|
2542 /** |
|
2543 * Handle operator information changes. |
|
2544 * |
|
2545 * @param message The new operator information. |
|
2546 * @param batch When batch is true, the RIL:VoiceInfoChanged and |
|
2547 * RIL:DataInfoChanged message will not be sent. |
|
2548 */ |
|
2549 handleOperatorChange: function(message, batch) { |
|
2550 let operatorInfo = this.operatorInfo; |
|
2551 let voice = this.rilContext.voice; |
|
2552 let data = this.rilContext.data; |
|
2553 |
|
2554 if (this.isInfoChanged(message, operatorInfo)) { |
|
2555 this.updateInfo(message, operatorInfo); |
|
2556 |
|
2557 // Update lastKnownNetwork |
|
2558 if (message.mcc && message.mnc) { |
|
2559 this._lastKnownNetwork = message.mcc + "-" + message.mnc; |
|
2560 } |
|
2561 |
|
2562 // If the voice is unregistered, no need to send RIL:VoiceInfoChanged. |
|
2563 if (voice.network && !batch) { |
|
2564 gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", |
|
2565 this.clientId, voice); |
|
2566 } |
|
2567 |
|
2568 // If the data is unregistered, no need to send RIL:DataInfoChanged. |
|
2569 if (data.network && !batch) { |
|
2570 gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", |
|
2571 this.clientId, data); |
|
2572 } |
|
2573 } |
|
2574 }, |
|
2575 |
|
2576 handleOtaStatus: function(message) { |
|
2577 if (message.status < 0 || |
|
2578 RIL.CDMA_OTA_PROVISION_STATUS_TO_GECKO.length <= message.status) { |
|
2579 return; |
|
2580 } |
|
2581 |
|
2582 let status = RIL.CDMA_OTA_PROVISION_STATUS_TO_GECKO[message.status]; |
|
2583 |
|
2584 gMessageManager.sendMobileConnectionMessage("RIL:OtaStatusChanged", |
|
2585 this.clientId, status); |
|
2586 }, |
|
2587 |
|
2588 _convertRadioState: function(state) { |
|
2589 switch (state) { |
|
2590 case RIL.GECKO_RADIOSTATE_OFF: |
|
2591 return RIL.GECKO_DETAILED_RADIOSTATE_DISABLED; |
|
2592 case RIL.GECKO_RADIOSTATE_READY: |
|
2593 return RIL.GECKO_DETAILED_RADIOSTATE_ENABLED; |
|
2594 default: |
|
2595 return RIL.GECKO_DETAILED_RADIOSTATE_UNKNOWN; |
|
2596 } |
|
2597 }, |
|
2598 |
|
2599 handleRadioStateChange: function(message) { |
|
2600 let newState = message.radioState; |
|
2601 if (this.rilContext.radioState == newState) { |
|
2602 return; |
|
2603 } |
|
2604 this.rilContext.radioState = newState; |
|
2605 this.handleDetailedRadioStateChanged(this._convertRadioState(newState)); |
|
2606 |
|
2607 //TODO Should we notify this change as a card state change? |
|
2608 }, |
|
2609 |
|
2610 handleDetailedRadioStateChanged: function(state) { |
|
2611 if (this.rilContext.detailedRadioState == state) { |
|
2612 return; |
|
2613 } |
|
2614 this.rilContext.detailedRadioState = state; |
|
2615 gMessageManager.sendMobileConnectionMessage("RIL:RadioStateChanged", |
|
2616 this.clientId, state); |
|
2617 }, |
|
2618 |
|
2619 setDataRegistration: function(attach) { |
|
2620 this.workerMessenger.send("setDataRegistration", {attach: attach}); |
|
2621 }, |
|
2622 |
|
2623 /** |
|
2624 * TODO: Bug 911713 - B2G NetworkManager: Move policy control logic to |
|
2625 * NetworkManager |
|
2626 */ |
|
2627 updateRILNetworkInterface: function() { |
|
2628 let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); |
|
2629 connHandler.updateRILNetworkInterface(); |
|
2630 }, |
|
2631 |
|
2632 /** |
|
2633 * Update network selection mode |
|
2634 */ |
|
2635 updateNetworkSelectionMode: function(message) { |
|
2636 if (DEBUG) this.debug("updateNetworkSelectionMode: " + JSON.stringify(message)); |
|
2637 this.rilContext.networkSelectionMode = message.mode; |
|
2638 gMessageManager.sendMobileConnectionMessage("RIL:NetworkSelectionModeChanged", |
|
2639 this.clientId, message); |
|
2640 }, |
|
2641 |
|
2642 /** |
|
2643 * Handle emergency callback mode change. |
|
2644 */ |
|
2645 handleEmergencyCbModeChange: function(message) { |
|
2646 if (DEBUG) this.debug("handleEmergencyCbModeChange: " + JSON.stringify(message)); |
|
2647 gMessageManager.sendMobileConnectionMessage("RIL:EmergencyCbModeChanged", |
|
2648 this.clientId, message); |
|
2649 }, |
|
2650 |
|
2651 /** |
|
2652 * Handle WDP port push PDU. Constructor WDP bearer information and deliver |
|
2653 * to WapPushManager. |
|
2654 * |
|
2655 * @param message |
|
2656 * A SMS message. |
|
2657 */ |
|
2658 handleSmsWdpPortPush: function(message) { |
|
2659 if (message.encoding != RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
|
2660 if (DEBUG) { |
|
2661 this.debug("Got port addressed SMS but not encoded in 8-bit alphabet." + |
|
2662 " Drop!"); |
|
2663 } |
|
2664 return; |
|
2665 } |
|
2666 |
|
2667 let options = { |
|
2668 bearer: WAP.WDP_BEARER_GSM_SMS_GSM_MSISDN, |
|
2669 sourceAddress: message.sender, |
|
2670 sourcePort: message.originatorPort, |
|
2671 destinationAddress: this.rilContext.iccInfo.msisdn, |
|
2672 destinationPort: message.destinationPort, |
|
2673 serviceId: this.clientId |
|
2674 }; |
|
2675 WAP.WapPushManager.receiveWdpPDU(message.fullData, message.fullData.length, |
|
2676 0, options); |
|
2677 }, |
|
2678 |
|
2679 /** |
|
2680 * A helper to broadcast the system message to launch registered apps |
|
2681 * like Costcontrol, Notification and Message app... etc. |
|
2682 * |
|
2683 * @param aName |
|
2684 * The system message name. |
|
2685 * @param aDomMessage |
|
2686 * The nsIDOMMozSmsMessage object. |
|
2687 */ |
|
2688 broadcastSmsSystemMessage: function(aName, aDomMessage) { |
|
2689 if (DEBUG) this.debug("Broadcasting the SMS system message: " + aName); |
|
2690 |
|
2691 // Sadly we cannot directly broadcast the aDomMessage object |
|
2692 // because the system message mechamism will rewrap the object |
|
2693 // based on the content window, which needs to know the properties. |
|
2694 gSystemMessenger.broadcastMessage(aName, { |
|
2695 iccId: aDomMessage.iccId, |
|
2696 type: aDomMessage.type, |
|
2697 id: aDomMessage.id, |
|
2698 threadId: aDomMessage.threadId, |
|
2699 delivery: aDomMessage.delivery, |
|
2700 deliveryStatus: aDomMessage.deliveryStatus, |
|
2701 sender: aDomMessage.sender, |
|
2702 receiver: aDomMessage.receiver, |
|
2703 body: aDomMessage.body, |
|
2704 messageClass: aDomMessage.messageClass, |
|
2705 timestamp: aDomMessage.timestamp, |
|
2706 sentTimestamp: aDomMessage.sentTimestamp, |
|
2707 deliveryTimestamp: aDomMessage.deliveryTimestamp, |
|
2708 read: aDomMessage.read |
|
2709 }); |
|
2710 }, |
|
2711 |
|
2712 // The following attributes/functions are used for acquiring/releasing the |
|
2713 // CPU wake lock when the RIL handles the received SMS. Note that we need |
|
2714 // a timer to bound the lock's life cycle to avoid exhausting the battery. |
|
2715 _smsHandledWakeLock: null, |
|
2716 _smsHandledWakeLockTimer: null, |
|
2717 |
|
2718 _acquireSmsHandledWakeLock: function() { |
|
2719 if (!this._smsHandledWakeLock) { |
|
2720 if (DEBUG) this.debug("Acquiring a CPU wake lock for handling SMS."); |
|
2721 this._smsHandledWakeLock = gPowerManagerService.newWakeLock("cpu"); |
|
2722 } |
|
2723 if (!this._smsHandledWakeLockTimer) { |
|
2724 if (DEBUG) this.debug("Creating a timer for releasing the CPU wake lock."); |
|
2725 this._smsHandledWakeLockTimer = |
|
2726 Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
|
2727 } |
|
2728 if (DEBUG) this.debug("Setting the timer for releasing the CPU wake lock."); |
|
2729 this._smsHandledWakeLockTimer |
|
2730 .initWithCallback(this._releaseSmsHandledWakeLock.bind(this), |
|
2731 SMS_HANDLED_WAKELOCK_TIMEOUT, |
|
2732 Ci.nsITimer.TYPE_ONE_SHOT); |
|
2733 }, |
|
2734 |
|
2735 _releaseSmsHandledWakeLock: function() { |
|
2736 if (DEBUG) this.debug("Releasing the CPU wake lock for handling SMS."); |
|
2737 if (this._smsHandledWakeLockTimer) { |
|
2738 this._smsHandledWakeLockTimer.cancel(); |
|
2739 } |
|
2740 if (this._smsHandledWakeLock) { |
|
2741 this._smsHandledWakeLock.unlock(); |
|
2742 this._smsHandledWakeLock = null; |
|
2743 } |
|
2744 }, |
|
2745 |
|
2746 /** |
|
2747 * Hash map for received multipart sms fragments. Messages are hashed with |
|
2748 * its sender address and concatenation reference number. Three additional |
|
2749 * attributes `segmentMaxSeq`, `receivedSegments`, `segments` are inserted. |
|
2750 */ |
|
2751 _receivedSmsSegmentsMap: null, |
|
2752 |
|
2753 /** |
|
2754 * Helper for processing received multipart SMS. |
|
2755 * |
|
2756 * @return null for handled segments, and an object containing full message |
|
2757 * body/data once all segments are received. |
|
2758 */ |
|
2759 _processReceivedSmsSegment: function(aSegment) { |
|
2760 |
|
2761 // Directly replace full message body for single SMS. |
|
2762 if (!(aSegment.segmentMaxSeq && (aSegment.segmentMaxSeq > 1))) { |
|
2763 if (aSegment.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
|
2764 aSegment.fullData = aSegment.data; |
|
2765 } else { |
|
2766 aSegment.fullBody = aSegment.body; |
|
2767 } |
|
2768 return aSegment; |
|
2769 } |
|
2770 |
|
2771 // Handle Concatenation for Class 0 SMS |
|
2772 let hash = aSegment.sender + ":" + |
|
2773 aSegment.segmentRef + ":" + |
|
2774 aSegment.segmentMaxSeq; |
|
2775 let seq = aSegment.segmentSeq; |
|
2776 |
|
2777 let options = this._receivedSmsSegmentsMap[hash]; |
|
2778 if (!options) { |
|
2779 options = aSegment; |
|
2780 this._receivedSmsSegmentsMap[hash] = options; |
|
2781 |
|
2782 options.receivedSegments = 0; |
|
2783 options.segments = []; |
|
2784 } else if (options.segments[seq]) { |
|
2785 // Duplicated segment? |
|
2786 if (DEBUG) { |
|
2787 this.debug("Got duplicated segment no." + seq + |
|
2788 " of a multipart SMS: " + JSON.stringify(aSegment)); |
|
2789 } |
|
2790 return null; |
|
2791 } |
|
2792 |
|
2793 if (options.receivedSegments > 0) { |
|
2794 // Update received timestamp. |
|
2795 options.timestamp = aSegment.timestamp; |
|
2796 } |
|
2797 |
|
2798 if (options.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
|
2799 options.segments[seq] = aSegment.data; |
|
2800 } else { |
|
2801 options.segments[seq] = aSegment.body; |
|
2802 } |
|
2803 options.receivedSegments++; |
|
2804 |
|
2805 // The port information is only available in 1st segment for CDMA WAP Push. |
|
2806 // If the segments of a WAP Push are not received in sequence |
|
2807 // (e.g., SMS with seq == 1 is not the 1st segment received by the device), |
|
2808 // we have to retrieve the port information from 1st segment and |
|
2809 // save it into the cached options. |
|
2810 if (aSegment.teleservice === RIL.PDU_CDMA_MSG_TELESERIVCIE_ID_WAP |
|
2811 && seq === 1) { |
|
2812 if (!options.originatorPort && aSegment.originatorPort) { |
|
2813 options.originatorPort = aSegment.originatorPort; |
|
2814 } |
|
2815 |
|
2816 if (!options.destinationPort && aSegment.destinationPort) { |
|
2817 options.destinationPort = aSegment.destinationPort; |
|
2818 } |
|
2819 } |
|
2820 |
|
2821 if (options.receivedSegments < options.segmentMaxSeq) { |
|
2822 if (DEBUG) { |
|
2823 this.debug("Got segment no." + seq + " of a multipart SMS: " + |
|
2824 JSON.stringify(options)); |
|
2825 } |
|
2826 return null; |
|
2827 } |
|
2828 |
|
2829 // Remove from map |
|
2830 delete this._receivedSmsSegmentsMap[hash]; |
|
2831 |
|
2832 // Rebuild full body |
|
2833 if (options.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
|
2834 // Uint8Array doesn't have `concat`, so we have to merge all segements |
|
2835 // by hand. |
|
2836 let fullDataLen = 0; |
|
2837 for (let i = 1; i <= options.segmentMaxSeq; i++) { |
|
2838 fullDataLen += options.segments[i].length; |
|
2839 } |
|
2840 |
|
2841 options.fullData = new Uint8Array(fullDataLen); |
|
2842 for (let d= 0, i = 1; i <= options.segmentMaxSeq; i++) { |
|
2843 let data = options.segments[i]; |
|
2844 for (let j = 0; j < data.length; j++) { |
|
2845 options.fullData[d++] = data[j]; |
|
2846 } |
|
2847 } |
|
2848 } else { |
|
2849 options.fullBody = options.segments.join(""); |
|
2850 } |
|
2851 |
|
2852 // Remove handy fields after completing the concatenation. |
|
2853 delete options.receivedSegments; |
|
2854 delete options.segments; |
|
2855 |
|
2856 if (DEBUG) { |
|
2857 this.debug("Got full multipart SMS: " + JSON.stringify(options)); |
|
2858 } |
|
2859 |
|
2860 return options; |
|
2861 }, |
|
2862 |
|
2863 /** |
|
2864 * Helper to create Savable SmsSegment. |
|
2865 */ |
|
2866 _createSavableSmsSegment: function(aMessage) { |
|
2867 // We precisely define what data fields to be stored into |
|
2868 // DB here for better data migration. |
|
2869 let segment = {}; |
|
2870 segment.messageType = aMessage.messageType; |
|
2871 segment.teleservice = aMessage.teleservice; |
|
2872 segment.SMSC = aMessage.SMSC; |
|
2873 segment.sentTimestamp = aMessage.sentTimestamp; |
|
2874 segment.timestamp = Date.now(); |
|
2875 segment.sender = aMessage.sender; |
|
2876 segment.pid = aMessage.pid; |
|
2877 segment.encoding = aMessage.encoding; |
|
2878 segment.messageClass = aMessage.messageClass; |
|
2879 segment.iccId = this.getIccId(); |
|
2880 if (aMessage.header) { |
|
2881 segment.segmentRef = aMessage.header.segmentRef; |
|
2882 segment.segmentSeq = aMessage.header.segmentSeq; |
|
2883 segment.segmentMaxSeq = aMessage.header.segmentMaxSeq; |
|
2884 segment.originatorPort = aMessage.header.originatorPort; |
|
2885 segment.destinationPort = aMessage.header.destinationPort; |
|
2886 } |
|
2887 segment.mwiPresent = (aMessage.mwi)? true: false; |
|
2888 segment.mwiDiscard = (segment.mwiPresent)? aMessage.mwi.discard: false; |
|
2889 segment.mwiMsgCount = (segment.mwiPresent)? aMessage.mwi.msgCount: 0; |
|
2890 segment.mwiActive = (segment.mwiPresent)? aMessage.mwi.active: false; |
|
2891 segment.serviceCategory = aMessage.serviceCategory; |
|
2892 segment.language = aMessage.language; |
|
2893 segment.data = aMessage.data; |
|
2894 segment.body = aMessage.body; |
|
2895 |
|
2896 return segment; |
|
2897 }, |
|
2898 |
|
2899 /** |
|
2900 * Helper to purge complete message. |
|
2901 * |
|
2902 * We remove unnessary fields defined in _createSavableSmsSegment() after |
|
2903 * completing the concatenation. |
|
2904 */ |
|
2905 _purgeCompleteSmsMessage: function(aMessage) { |
|
2906 // Purge concatenation info |
|
2907 delete aMessage.segmentRef; |
|
2908 delete aMessage.segmentSeq; |
|
2909 delete aMessage.segmentMaxSeq; |
|
2910 |
|
2911 // Purge partial message body |
|
2912 delete aMessage.data; |
|
2913 delete aMessage.body; |
|
2914 }, |
|
2915 |
|
2916 /** |
|
2917 * handle concatenation of received SMS. |
|
2918 */ |
|
2919 handleSmsMultipart: function(aMessage) { |
|
2920 if (DEBUG) this.debug("handleSmsMultipart: " + JSON.stringify(aMessage)); |
|
2921 |
|
2922 this._acquireSmsHandledWakeLock(); |
|
2923 |
|
2924 let segment = this._createSavableSmsSegment(aMessage); |
|
2925 |
|
2926 let isMultipart = (segment.segmentMaxSeq && (segment.segmentMaxSeq > 1)); |
|
2927 let messageClass = segment.messageClass; |
|
2928 |
|
2929 let handleReceivedAndAck = function(aRvOfIncompleteMsg, aCompleteMessage) { |
|
2930 if (aCompleteMessage) { |
|
2931 this._purgeCompleteSmsMessage(aCompleteMessage); |
|
2932 if (this.handleSmsReceived(aCompleteMessage)) { |
|
2933 this.sendAckSms(Cr.NS_OK, aCompleteMessage); |
|
2934 } |
|
2935 // else Ack will be sent after further process in handleSmsReceived. |
|
2936 } else { |
|
2937 this.sendAckSms(aRvOfIncompleteMsg, segment); |
|
2938 } |
|
2939 }.bind(this); |
|
2940 |
|
2941 // No need to access SmsSegmentStore for Class 0 SMS and Single SMS. |
|
2942 if (!isMultipart || |
|
2943 (messageClass == RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0])) { |
|
2944 // `When a mobile terminated message is class 0 and the MS has the |
|
2945 // capability of displaying short messages, the MS shall display the |
|
2946 // message immediately and send an acknowledgement to the SC when the |
|
2947 // message has successfully reached the MS irrespective of whether |
|
2948 // there is memory available in the (U)SIM or ME. The message shall |
|
2949 // not be automatically stored in the (U)SIM or ME.` |
|
2950 // ~ 3GPP 23.038 clause 4 |
|
2951 |
|
2952 handleReceivedAndAck(Cr.NS_OK, // ACK OK For Incomplete Class 0 |
|
2953 this._processReceivedSmsSegment(segment)); |
|
2954 } else { |
|
2955 gMobileMessageDatabaseService |
|
2956 .saveSmsSegment(segment, function notifyResult(aRv, aCompleteMessage) { |
|
2957 handleReceivedAndAck(aRv, // Ack according to the result after saving |
|
2958 aCompleteMessage); |
|
2959 }); |
|
2960 } |
|
2961 }, |
|
2962 |
|
2963 portAddressedSmsApps: null, |
|
2964 handleSmsReceived: function(message) { |
|
2965 if (DEBUG) this.debug("handleSmsReceived: " + JSON.stringify(message)); |
|
2966 |
|
2967 if (message.messageType == RIL.PDU_CDMA_MSG_TYPE_BROADCAST) { |
|
2968 gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived", |
|
2969 this.clientId, message); |
|
2970 return true; |
|
2971 } |
|
2972 |
|
2973 // Dispatch to registered handler if application port addressing is |
|
2974 // available. Note that the destination port can possibly be zero when |
|
2975 // representing a UDP/TCP port. |
|
2976 if (message.destinationPort != null) { |
|
2977 let handler = this.portAddressedSmsApps[message.destinationPort]; |
|
2978 if (handler) { |
|
2979 handler(message); |
|
2980 } |
|
2981 return true; |
|
2982 } |
|
2983 |
|
2984 if (message.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
|
2985 // Don't know how to handle binary data yet. |
|
2986 return true; |
|
2987 } |
|
2988 |
|
2989 message.type = "sms"; |
|
2990 message.sender = message.sender || null; |
|
2991 message.receiver = this.getPhoneNumber(); |
|
2992 message.body = message.fullBody = message.fullBody || null; |
|
2993 |
|
2994 if (gSmsService.isSilentNumber(message.sender)) { |
|
2995 message.id = -1; |
|
2996 message.threadId = 0; |
|
2997 message.delivery = DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED; |
|
2998 message.deliveryStatus = RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS; |
|
2999 message.read = false; |
|
3000 |
|
3001 let domMessage = |
|
3002 gMobileMessageService.createSmsMessage(message.id, |
|
3003 message.threadId, |
|
3004 message.iccId, |
|
3005 message.delivery, |
|
3006 message.deliveryStatus, |
|
3007 message.sender, |
|
3008 message.receiver, |
|
3009 message.body, |
|
3010 message.messageClass, |
|
3011 message.timestamp, |
|
3012 message.sentTimestamp, |
|
3013 0, |
|
3014 message.read); |
|
3015 |
|
3016 Services.obs.notifyObservers(domMessage, |
|
3017 kSilentSmsReceivedObserverTopic, |
|
3018 null); |
|
3019 return true; |
|
3020 } |
|
3021 |
|
3022 if (message.mwiPresent) { |
|
3023 let mwi = { |
|
3024 discard: message.mwiDiscard, |
|
3025 msgCount: message.mwiMsgCount, |
|
3026 active: message.mwiActive |
|
3027 }; |
|
3028 this.workerMessenger.send("updateMwis", { mwi: mwi }); |
|
3029 |
|
3030 mwi.returnNumber = message.sender; |
|
3031 mwi.returnMessage = message.fullBody; |
|
3032 gMessageManager.sendVoicemailMessage("RIL:VoicemailNotification", |
|
3033 this.clientId, mwi); |
|
3034 |
|
3035 // Dicarded MWI comes without text body. |
|
3036 // Hence, we discard it here after notifying the MWI status. |
|
3037 if (message.mwiDiscard) { |
|
3038 return true; |
|
3039 } |
|
3040 } |
|
3041 |
|
3042 let notifyReceived = function notifyReceived(rv, domMessage) { |
|
3043 let success = Components.isSuccessCode(rv); |
|
3044 |
|
3045 this.sendAckSms(rv, message); |
|
3046 |
|
3047 if (!success) { |
|
3048 // At this point we could send a message to content to notify the user |
|
3049 // that storing an incoming SMS failed, most likely due to a full disk. |
|
3050 if (DEBUG) { |
|
3051 this.debug("Could not store SMS, error code " + rv); |
|
3052 } |
|
3053 return; |
|
3054 } |
|
3055 |
|
3056 this.broadcastSmsSystemMessage(kSmsReceivedObserverTopic, domMessage); |
|
3057 Services.obs.notifyObservers(domMessage, kSmsReceivedObserverTopic, null); |
|
3058 }.bind(this); |
|
3059 |
|
3060 if (message.messageClass != RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0]) { |
|
3061 gMobileMessageDatabaseService.saveReceivedMessage(message, |
|
3062 notifyReceived); |
|
3063 } else { |
|
3064 message.id = -1; |
|
3065 message.threadId = 0; |
|
3066 message.delivery = DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED; |
|
3067 message.deliveryStatus = RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS; |
|
3068 message.read = false; |
|
3069 |
|
3070 let domMessage = |
|
3071 gMobileMessageService.createSmsMessage(message.id, |
|
3072 message.threadId, |
|
3073 message.iccId, |
|
3074 message.delivery, |
|
3075 message.deliveryStatus, |
|
3076 message.sender, |
|
3077 message.receiver, |
|
3078 message.body, |
|
3079 message.messageClass, |
|
3080 message.timestamp, |
|
3081 message.sentTimestamp, |
|
3082 0, |
|
3083 message.read); |
|
3084 |
|
3085 notifyReceived(Cr.NS_OK, domMessage); |
|
3086 } |
|
3087 |
|
3088 // SMS ACK will be sent in notifyReceived. Return false here. |
|
3089 return false; |
|
3090 }, |
|
3091 |
|
3092 /** |
|
3093 * Handle ACK response of received SMS. |
|
3094 */ |
|
3095 sendAckSms: function(aRv, aMessage) { |
|
3096 if (aMessage.messageClass === RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_2]) { |
|
3097 return; |
|
3098 } |
|
3099 |
|
3100 let result = RIL.PDU_FCS_OK; |
|
3101 if (!Components.isSuccessCode(aRv)) { |
|
3102 if (DEBUG) this.debug("Failed to handle received sms: " + aRv); |
|
3103 result = (aRv === Cr.NS_ERROR_FILE_NO_DEVICE_SPACE) |
|
3104 ? RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED |
|
3105 : RIL.PDU_FCS_UNSPECIFIED; |
|
3106 } |
|
3107 |
|
3108 this.workerMessenger.send("ackSMS", { result: result }); |
|
3109 |
|
3110 }, |
|
3111 |
|
3112 /** |
|
3113 * Set the setting value of "time.clock.automatic-update.available". |
|
3114 */ |
|
3115 setClockAutoUpdateAvailable: function(value) { |
|
3116 gSettingsService.createLock().set(kSettingsClockAutoUpdateAvailable, value, null, |
|
3117 "fromInternalSetting"); |
|
3118 }, |
|
3119 |
|
3120 /** |
|
3121 * Set the setting value of "time.timezone.automatic-update.available". |
|
3122 */ |
|
3123 setTimezoneAutoUpdateAvailable: function(value) { |
|
3124 gSettingsService.createLock().set(kSettingsTimezoneAutoUpdateAvailable, value, null, |
|
3125 "fromInternalSetting"); |
|
3126 }, |
|
3127 |
|
3128 /** |
|
3129 * Set the system clock by NITZ. |
|
3130 */ |
|
3131 setClockByNitz: function(message) { |
|
3132 // To set the system clock time. Note that there could be a time diff |
|
3133 // between when the NITZ was received and when the time is actually set. |
|
3134 gTimeService.set( |
|
3135 message.networkTimeInMS + (Date.now() - message.receiveTimeInMS)); |
|
3136 }, |
|
3137 |
|
3138 /** |
|
3139 * Set the system time zone by NITZ. |
|
3140 */ |
|
3141 setTimezoneByNitz: function(message) { |
|
3142 // To set the sytem timezone. Note that we need to convert the time zone |
|
3143 // value to a UTC repesentation string in the format of "UTC(+/-)hh:mm". |
|
3144 // Ex, time zone -480 is "UTC+08:00"; time zone 630 is "UTC-10:30". |
|
3145 // |
|
3146 // We can unapply the DST correction if we want the raw time zone offset: |
|
3147 // message.networkTimeZoneInMinutes -= message.networkDSTInMinutes; |
|
3148 if (message.networkTimeZoneInMinutes != (new Date()).getTimezoneOffset()) { |
|
3149 let absTimeZoneInMinutes = Math.abs(message.networkTimeZoneInMinutes); |
|
3150 let timeZoneStr = "UTC"; |
|
3151 timeZoneStr += (message.networkTimeZoneInMinutes > 0 ? "-" : "+"); |
|
3152 timeZoneStr += ("0" + Math.floor(absTimeZoneInMinutes / 60)).slice(-2); |
|
3153 timeZoneStr += ":"; |
|
3154 timeZoneStr += ("0" + absTimeZoneInMinutes % 60).slice(-2); |
|
3155 gSettingsService.createLock().set("time.timezone", timeZoneStr, null); |
|
3156 } |
|
3157 }, |
|
3158 |
|
3159 /** |
|
3160 * Handle the NITZ message. |
|
3161 */ |
|
3162 handleNitzTime: function(message) { |
|
3163 // Got the NITZ info received from the ril_worker. |
|
3164 this.setClockAutoUpdateAvailable(true); |
|
3165 this.setTimezoneAutoUpdateAvailable(true); |
|
3166 |
|
3167 // Cache the latest NITZ message whenever receiving it. |
|
3168 this._lastNitzMessage = message; |
|
3169 |
|
3170 // Set the received NITZ clock if the setting is enabled. |
|
3171 if (this._clockAutoUpdateEnabled) { |
|
3172 this.setClockByNitz(message); |
|
3173 } |
|
3174 // Set the received NITZ timezone if the setting is enabled. |
|
3175 if (this._timezoneAutoUpdateEnabled) { |
|
3176 this.setTimezoneByNitz(message); |
|
3177 } |
|
3178 }, |
|
3179 |
|
3180 /** |
|
3181 * Set the system clock by SNTP. |
|
3182 */ |
|
3183 setClockBySntp: function(offset) { |
|
3184 // Got the SNTP info. |
|
3185 this.setClockAutoUpdateAvailable(true); |
|
3186 if (!this._clockAutoUpdateEnabled) { |
|
3187 return; |
|
3188 } |
|
3189 if (this._lastNitzMessage) { |
|
3190 if (DEBUG) debug("SNTP: NITZ available, discard SNTP"); |
|
3191 return; |
|
3192 } |
|
3193 gTimeService.set(Date.now() + offset); |
|
3194 }, |
|
3195 |
|
3196 handleIccMbdn: function(message) { |
|
3197 let voicemailInfo = this.voicemailInfo; |
|
3198 |
|
3199 voicemailInfo.number = message.number; |
|
3200 voicemailInfo.displayName = message.alphaId; |
|
3201 |
|
3202 gMessageManager.sendVoicemailMessage("RIL:VoicemailInfoChanged", |
|
3203 this.clientId, voicemailInfo); |
|
3204 }, |
|
3205 |
|
3206 handleIccInfoChange: function(message) { |
|
3207 let oldSpn = this.rilContext.iccInfo ? this.rilContext.iccInfo.spn : null; |
|
3208 |
|
3209 if (!message || !message.iccType) { |
|
3210 // Card is not detected, clear iccInfo to null. |
|
3211 this.rilContext.iccInfo = null; |
|
3212 } else { |
|
3213 if (!this.rilContext.iccInfo) { |
|
3214 if (message.iccType === "ruim" || message.iccType === "csim") { |
|
3215 this.rilContext.iccInfo = new CdmaIccInfo(); |
|
3216 } else { |
|
3217 this.rilContext.iccInfo = new GsmIccInfo(); |
|
3218 } |
|
3219 } |
|
3220 |
|
3221 if (!this.isInfoChanged(message, this.rilContext.iccInfo)) { |
|
3222 return; |
|
3223 } |
|
3224 |
|
3225 this.updateInfo(message, this.rilContext.iccInfo); |
|
3226 } |
|
3227 |
|
3228 // RIL:IccInfoChanged corresponds to a DOM event that gets fired only |
|
3229 // when iccInfo has changed. |
|
3230 gMessageManager.sendIccMessage("RIL:IccInfoChanged", |
|
3231 this.clientId, |
|
3232 message.iccType ? message : null); |
|
3233 |
|
3234 // Update lastKnownSimMcc. |
|
3235 if (message.mcc) { |
|
3236 try { |
|
3237 Services.prefs.setCharPref("ril.lastKnownSimMcc", |
|
3238 message.mcc.toString()); |
|
3239 } catch (e) {} |
|
3240 } |
|
3241 |
|
3242 // Update lastKnownHomeNetwork. |
|
3243 if (message.mcc && message.mnc) { |
|
3244 this._lastKnownHomeNetwork = message.mcc + "-" + message.mnc; |
|
3245 } |
|
3246 |
|
3247 // If spn becomes available, we should check roaming again. |
|
3248 if (!oldSpn && message.spn) { |
|
3249 let voice = this.rilContext.voice; |
|
3250 let data = this.rilContext.data; |
|
3251 let voiceRoaming = voice.roaming; |
|
3252 let dataRoaming = data.roaming; |
|
3253 this.checkRoamingBetweenOperators(voice); |
|
3254 this.checkRoamingBetweenOperators(data); |
|
3255 if (voiceRoaming != voice.roaming) { |
|
3256 gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", |
|
3257 this.clientId, voice); |
|
3258 } |
|
3259 if (dataRoaming != data.roaming) { |
|
3260 gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", |
|
3261 this.clientId, data); |
|
3262 } |
|
3263 } |
|
3264 }, |
|
3265 |
|
3266 handleUSSDReceived: function(ussd) { |
|
3267 if (DEBUG) this.debug("handleUSSDReceived " + JSON.stringify(ussd)); |
|
3268 gSystemMessenger.broadcastMessage("ussd-received", ussd); |
|
3269 gMessageManager.sendMobileConnectionMessage("RIL:USSDReceived", |
|
3270 this.clientId, ussd); |
|
3271 }, |
|
3272 |
|
3273 handleStkProactiveCommand: function(message) { |
|
3274 if (DEBUG) this.debug("handleStkProactiveCommand " + JSON.stringify(message)); |
|
3275 let iccId = this.rilContext.iccInfo && this.rilContext.iccInfo.iccid; |
|
3276 if (iccId) { |
|
3277 gSystemMessenger.broadcastMessage("icc-stkcommand", |
|
3278 {iccId: iccId, |
|
3279 command: message}); |
|
3280 } |
|
3281 gMessageManager.sendIccMessage("RIL:StkCommand", this.clientId, message); |
|
3282 }, |
|
3283 |
|
3284 handleExitEmergencyCbMode: function(message) { |
|
3285 if (DEBUG) this.debug("handleExitEmergencyCbMode: " + JSON.stringify(message)); |
|
3286 gMessageManager.sendRequestResults("RIL:ExitEmergencyCbMode", message); |
|
3287 }, |
|
3288 |
|
3289 // nsIObserver |
|
3290 |
|
3291 observe: function(subject, topic, data) { |
|
3292 switch (topic) { |
|
3293 case kMozSettingsChangedObserverTopic: |
|
3294 let setting = JSON.parse(data); |
|
3295 this.handleSettingsChange(setting.key, setting.value, setting.message); |
|
3296 break; |
|
3297 case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: |
|
3298 if (data === kPrefCellBroadcastDisabled) { |
|
3299 let value = false; |
|
3300 try { |
|
3301 value = Services.prefs.getBoolPref(kPrefCellBroadcastDisabled); |
|
3302 } catch(e) {} |
|
3303 this.workerMessenger.send("setCellBroadcastDisabled", |
|
3304 { disabled: value }); |
|
3305 } |
|
3306 break; |
|
3307 case kSysClockChangeObserverTopic: |
|
3308 let offset = parseInt(data, 10); |
|
3309 if (this._lastNitzMessage) { |
|
3310 this._lastNitzMessage.receiveTimeInMS += offset; |
|
3311 } |
|
3312 this._sntp.updateOffset(offset); |
|
3313 break; |
|
3314 case kNetworkConnStateChangedTopic: |
|
3315 let network = subject.QueryInterface(Ci.nsINetworkInterface); |
|
3316 if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { |
|
3317 return; |
|
3318 } |
|
3319 |
|
3320 // SNTP can only update when we have mobile or Wifi connections. |
|
3321 if (network.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && |
|
3322 network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { |
|
3323 return; |
|
3324 } |
|
3325 |
|
3326 // If the network comes from RIL, make sure the RIL service is matched. |
|
3327 if (subject instanceof Ci.nsIRilNetworkInterface) { |
|
3328 network = subject.QueryInterface(Ci.nsIRilNetworkInterface); |
|
3329 if (network.serviceId != this.clientId) { |
|
3330 return; |
|
3331 } |
|
3332 } |
|
3333 |
|
3334 // SNTP won't update unless the SNTP is already expired. |
|
3335 if (this._sntp.isExpired()) { |
|
3336 this._sntp.request(); |
|
3337 } |
|
3338 break; |
|
3339 case kNetworkActiveChangedTopic: |
|
3340 let dataInfo = this.rilContext.data; |
|
3341 let connected = false; |
|
3342 if (gNetworkManager.active && |
|
3343 gNetworkManager.active.type === |
|
3344 Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE && |
|
3345 gNetworkManager.active.serviceId === this.clientId) { |
|
3346 connected = true; |
|
3347 } |
|
3348 if (dataInfo.connected !== connected) { |
|
3349 dataInfo.connected = connected; |
|
3350 gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", |
|
3351 this.clientId, dataInfo); |
|
3352 } |
|
3353 break; |
|
3354 case kScreenStateChangedTopic: |
|
3355 this.workerMessenger.send("setScreenState", { on: (data === "on") }); |
|
3356 break; |
|
3357 } |
|
3358 }, |
|
3359 |
|
3360 supportedNetworkTypes: null, |
|
3361 |
|
3362 // Flag to determine whether to update system clock automatically. It |
|
3363 // corresponds to the "time.clock.automatic-update.enabled" setting. |
|
3364 _clockAutoUpdateEnabled: null, |
|
3365 |
|
3366 // Flag to determine whether to update system timezone automatically. It |
|
3367 // corresponds to the "time.clock.automatic-update.enabled" setting. |
|
3368 _timezoneAutoUpdateEnabled: null, |
|
3369 |
|
3370 // Remember the last NITZ message so that we can set the time based on |
|
3371 // the network immediately when users enable network-based time. |
|
3372 _lastNitzMessage: null, |
|
3373 |
|
3374 // Object that handles SNTP. |
|
3375 _sntp: null, |
|
3376 |
|
3377 // Cell Broadcast settings values. |
|
3378 _cellBroadcastSearchList: null, |
|
3379 |
|
3380 // Operator's mcc-mnc. |
|
3381 _lastKnownNetwork: null, |
|
3382 |
|
3383 // ICC's mcc-mnc. |
|
3384 _lastKnownHomeNetwork: null, |
|
3385 |
|
3386 handleSettingsChange: function(aName, aResult, aMessage) { |
|
3387 // Don't allow any content processes to modify the setting |
|
3388 // "time.clock.automatic-update.available" except for the chrome process. |
|
3389 if (aName === kSettingsClockAutoUpdateAvailable && |
|
3390 aMessage !== "fromInternalSetting") { |
|
3391 let isClockAutoUpdateAvailable = this._lastNitzMessage !== null || |
|
3392 this._sntp.isAvailable(); |
|
3393 if (aResult !== isClockAutoUpdateAvailable) { |
|
3394 if (DEBUG) { |
|
3395 debug("Content processes cannot modify 'time.clock.automatic-update.available'. Restore!"); |
|
3396 } |
|
3397 // Restore the setting to the current value. |
|
3398 this.setClockAutoUpdateAvailable(isClockAutoUpdateAvailable); |
|
3399 } |
|
3400 } |
|
3401 |
|
3402 // Don't allow any content processes to modify the setting |
|
3403 // "time.timezone.automatic-update.available" except for the chrome |
|
3404 // process. |
|
3405 if (aName === kSettingsTimezoneAutoUpdateAvailable && |
|
3406 aMessage !== "fromInternalSetting") { |
|
3407 let isTimezoneAutoUpdateAvailable = this._lastNitzMessage !== null; |
|
3408 if (aResult !== isTimezoneAutoUpdateAvailable) { |
|
3409 if (DEBUG) { |
|
3410 this.debug("Content processes cannot modify 'time.timezone.automatic-update.available'. Restore!"); |
|
3411 } |
|
3412 // Restore the setting to the current value. |
|
3413 this.setTimezoneAutoUpdateAvailable(isTimezoneAutoUpdateAvailable); |
|
3414 } |
|
3415 } |
|
3416 |
|
3417 this.handle(aName, aResult); |
|
3418 }, |
|
3419 |
|
3420 // nsISettingsServiceCallback |
|
3421 handle: function(aName, aResult) { |
|
3422 switch(aName) { |
|
3423 case kSettingsClockAutoUpdateEnabled: |
|
3424 this._clockAutoUpdateEnabled = aResult; |
|
3425 if (!this._clockAutoUpdateEnabled) { |
|
3426 break; |
|
3427 } |
|
3428 |
|
3429 // Set the latest cached NITZ time if it's available. |
|
3430 if (this._lastNitzMessage) { |
|
3431 this.setClockByNitz(this._lastNitzMessage); |
|
3432 } else if (gNetworkManager.active && gNetworkManager.active.state == |
|
3433 Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { |
|
3434 // Set the latest cached SNTP time if it's available. |
|
3435 if (!this._sntp.isExpired()) { |
|
3436 this.setClockBySntp(this._sntp.getOffset()); |
|
3437 } else { |
|
3438 // Or refresh the SNTP. |
|
3439 this._sntp.request(); |
|
3440 } |
|
3441 } else { |
|
3442 // Set a sane minimum time. |
|
3443 let buildTime = libcutils.property_get("ro.build.date.utc", "0") * 1000; |
|
3444 let file = FileUtils.File("/system/b2g/b2g"); |
|
3445 if (file.lastModifiedTime > buildTime) { |
|
3446 buildTime = file.lastModifiedTime; |
|
3447 } |
|
3448 if (buildTime > Date.now()) { |
|
3449 gTimeService.set(buildTime); |
|
3450 } |
|
3451 } |
|
3452 break; |
|
3453 case kSettingsTimezoneAutoUpdateEnabled: |
|
3454 this._timezoneAutoUpdateEnabled = aResult; |
|
3455 |
|
3456 if (this._timezoneAutoUpdateEnabled) { |
|
3457 // Apply the latest cached NITZ for timezone if it's available. |
|
3458 if (this._timezoneAutoUpdateEnabled && this._lastNitzMessage) { |
|
3459 this.setTimezoneByNitz(this._lastNitzMessage); |
|
3460 } |
|
3461 } |
|
3462 break; |
|
3463 case kSettingsCellBroadcastSearchList: |
|
3464 if (DEBUG) { |
|
3465 this.debug("'" + kSettingsCellBroadcastSearchList + |
|
3466 "' is now " + JSON.stringify(aResult)); |
|
3467 } |
|
3468 // TODO: Set searchlist for Multi-SIM. See Bug 921326. |
|
3469 let result = Array.isArray(aResult) ? aResult[0] : aResult; |
|
3470 this.setCellBroadcastSearchList(result); |
|
3471 break; |
|
3472 } |
|
3473 }, |
|
3474 |
|
3475 handleError: function(aErrorMessage) { |
|
3476 if (DEBUG) { |
|
3477 this.debug("There was an error while reading RIL settings."); |
|
3478 } |
|
3479 }, |
|
3480 |
|
3481 // nsIRadioInterface |
|
3482 |
|
3483 rilContext: null, |
|
3484 |
|
3485 // Handle phone functions of nsIRILContentHelper |
|
3486 |
|
3487 _sendCfStateChanged: function(message) { |
|
3488 gMessageManager.sendMobileConnectionMessage("RIL:CfStateChanged", |
|
3489 this.clientId, message); |
|
3490 }, |
|
3491 |
|
3492 _updateCallingLineIdRestrictionPref: function(mode) { |
|
3493 try { |
|
3494 Services.prefs.setIntPref(kPrefClirModePreference, mode); |
|
3495 Services.prefs.savePrefFile(null); |
|
3496 if (DEBUG) { |
|
3497 this.debug(kPrefClirModePreference + " pref is now " + mode); |
|
3498 } |
|
3499 } catch (e) {} |
|
3500 }, |
|
3501 |
|
3502 sendMMI: function(target, message) { |
|
3503 if (DEBUG) this.debug("SendMMI " + JSON.stringify(message)); |
|
3504 this.workerMessenger.send("sendMMI", message, (function(response) { |
|
3505 if (response.isSetCallForward) { |
|
3506 this._sendCfStateChanged(response); |
|
3507 } else if (response.isSetCLIR && response.success) { |
|
3508 this._updateCallingLineIdRestrictionPref(response.clirMode); |
|
3509 } |
|
3510 |
|
3511 target.sendAsyncMessage("RIL:SendMMI", { |
|
3512 clientId: this.clientId, |
|
3513 data: response |
|
3514 }); |
|
3515 return false; |
|
3516 }).bind(this)); |
|
3517 }, |
|
3518 |
|
3519 setCallForwardingOptions: function(target, message) { |
|
3520 if (DEBUG) this.debug("setCallForwardingOptions: " + JSON.stringify(message)); |
|
3521 message.serviceClass = RIL.ICC_SERVICE_CLASS_VOICE; |
|
3522 this.workerMessenger.send("setCallForward", message, (function(response) { |
|
3523 this._sendCfStateChanged(response); |
|
3524 target.sendAsyncMessage("RIL:SetCallForwardingOptions", { |
|
3525 clientId: this.clientId, |
|
3526 data: response |
|
3527 }); |
|
3528 return false; |
|
3529 }).bind(this)); |
|
3530 }, |
|
3531 |
|
3532 setCallingLineIdRestriction: function(target, message) { |
|
3533 if (DEBUG) { |
|
3534 this.debug("setCallingLineIdRestriction: " + JSON.stringify(message)); |
|
3535 } |
|
3536 this.workerMessenger.send("setCLIR", message, (function(response) { |
|
3537 if (response.success) { |
|
3538 this._updateCallingLineIdRestrictionPref(response.clirMode); |
|
3539 } |
|
3540 target.sendAsyncMessage("RIL:SetCallingLineIdRestriction", { |
|
3541 clientId: this.clientId, |
|
3542 data: response |
|
3543 }); |
|
3544 return false; |
|
3545 }).bind(this)); |
|
3546 }, |
|
3547 |
|
3548 isValidStateForSetRadioEnabled: function() { |
|
3549 let state = this.rilContext.detailedRadioState; |
|
3550 return state == RIL.GECKO_DETAILED_RADIOSTATE_ENABLED || |
|
3551 state == RIL.GECKO_DETAILED_RADIOSTATE_DISABLED; |
|
3552 }, |
|
3553 |
|
3554 isDummyForSetRadioEnabled: function(message) { |
|
3555 let state = this.rilContext.detailedRadioState; |
|
3556 return (state == RIL.GECKO_DETAILED_RADIOSTATE_ENABLED && message.enabled) || |
|
3557 (state == RIL.GECKO_DETAILED_RADIOSTATE_DISABLED && !message.enabled); |
|
3558 }, |
|
3559 |
|
3560 setRadioEnabledResponse: function(target, message, errorMsg) { |
|
3561 if (errorMsg) { |
|
3562 message.errorMsg = errorMsg; |
|
3563 } |
|
3564 |
|
3565 target.sendAsyncMessage("RIL:SetRadioEnabled", { |
|
3566 clientId: this.clientId, |
|
3567 data: message |
|
3568 }); |
|
3569 }, |
|
3570 |
|
3571 setRadioEnabled: function(target, message) { |
|
3572 if (DEBUG) { |
|
3573 this.debug("setRadioEnabled: " + JSON.stringify(message)); |
|
3574 } |
|
3575 |
|
3576 if (!this.isValidStateForSetRadioEnabled()) { |
|
3577 this.setRadioEnabledResponse(target, message, "InvalidStateError"); |
|
3578 return; |
|
3579 } |
|
3580 |
|
3581 if (this.isDummyForSetRadioEnabled(message)) { |
|
3582 this.setRadioEnabledResponse(target, message); |
|
3583 return; |
|
3584 } |
|
3585 |
|
3586 let callback = (function(response) { |
|
3587 if (response.errorMsg) { |
|
3588 // Request fails. Rollback to the original radiostate. |
|
3589 let state = message.enabled ? RIL.GECKO_DETAILED_RADIOSTATE_DISABLED |
|
3590 : RIL.GECKO_DETAILED_RADIOSTATE_ENABLED; |
|
3591 this.handleDetailedRadioStateChanged(state); |
|
3592 } |
|
3593 this.setRadioEnabledResponse(target, response); |
|
3594 return false; |
|
3595 }).bind(this); |
|
3596 |
|
3597 this.setRadioEnabledInternal(message, callback); |
|
3598 }, |
|
3599 |
|
3600 setRadioEnabledInternal: function(message, callback) { |
|
3601 let state = message.enabled ? RIL.GECKO_DETAILED_RADIOSTATE_ENABLING |
|
3602 : RIL.GECKO_DETAILED_RADIOSTATE_DISABLING; |
|
3603 this.handleDetailedRadioStateChanged(state); |
|
3604 this.workerMessenger.send("setRadioEnabled", message, callback); |
|
3605 }, |
|
3606 |
|
3607 /** |
|
3608 * List of tuples of national language identifier pairs. |
|
3609 * |
|
3610 * TODO: Support static/runtime settings, see bug 733331. |
|
3611 */ |
|
3612 enabledGsmTableTuples: [ |
|
3613 [RIL.PDU_NL_IDENTIFIER_DEFAULT, RIL.PDU_NL_IDENTIFIER_DEFAULT], |
|
3614 ], |
|
3615 |
|
3616 /** |
|
3617 * Use 16-bit reference number for concatenated outgoint messages. |
|
3618 * |
|
3619 * TODO: Support static/runtime settings, see bug 733331. |
|
3620 */ |
|
3621 segmentRef16Bit: false, |
|
3622 |
|
3623 /** |
|
3624 * Get valid SMS concatenation reference number. |
|
3625 */ |
|
3626 _segmentRef: 0, |
|
3627 get nextSegmentRef() { |
|
3628 let ref = this._segmentRef++; |
|
3629 |
|
3630 this._segmentRef %= (this.segmentRef16Bit ? 65535 : 255); |
|
3631 |
|
3632 // 0 is not a valid SMS concatenation reference number. |
|
3633 return ref + 1; |
|
3634 }, |
|
3635 |
|
3636 /** |
|
3637 * Calculate encoded length using specified locking/single shift table |
|
3638 * |
|
3639 * @param message |
|
3640 * message string to be encoded. |
|
3641 * @param langTable |
|
3642 * locking shift table string. |
|
3643 * @param langShiftTable |
|
3644 * single shift table string. |
|
3645 * @param strict7BitEncoding |
|
3646 * Optional. Enable Latin characters replacement with corresponding |
|
3647 * ones in GSM SMS 7-bit default alphabet. |
|
3648 * |
|
3649 * @return encoded length in septets. |
|
3650 * |
|
3651 * @note that the algorithm used in this function must match exactly with |
|
3652 * GsmPDUHelper#writeStringAsSeptets. |
|
3653 */ |
|
3654 _countGsm7BitSeptets: function(message, langTable, langShiftTable, strict7BitEncoding) { |
|
3655 let length = 0; |
|
3656 for (let msgIndex = 0; msgIndex < message.length; msgIndex++) { |
|
3657 let c = message.charAt(msgIndex); |
|
3658 if (strict7BitEncoding) { |
|
3659 c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c; |
|
3660 } |
|
3661 |
|
3662 let septet = langTable.indexOf(c); |
|
3663 |
|
3664 // According to 3GPP TS 23.038, section 6.1.1 General notes, "The |
|
3665 // characters marked '1)' are not used but are displayed as a space." |
|
3666 if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) { |
|
3667 continue; |
|
3668 } |
|
3669 |
|
3670 if (septet >= 0) { |
|
3671 length++; |
|
3672 continue; |
|
3673 } |
|
3674 |
|
3675 septet = langShiftTable.indexOf(c); |
|
3676 if (septet < 0) { |
|
3677 if (!strict7BitEncoding) { |
|
3678 return -1; |
|
3679 } |
|
3680 |
|
3681 // Bug 816082, when strict7BitEncoding is enabled, we should replace |
|
3682 // characters that can't be encoded with GSM 7-Bit alphabets with '*'. |
|
3683 c = "*"; |
|
3684 if (langTable.indexOf(c) >= 0) { |
|
3685 length++; |
|
3686 } else if (langShiftTable.indexOf(c) >= 0) { |
|
3687 length += 2; |
|
3688 } else { |
|
3689 // We can't even encode a '*' character with current configuration. |
|
3690 return -1; |
|
3691 } |
|
3692 |
|
3693 continue; |
|
3694 } |
|
3695 |
|
3696 // According to 3GPP TS 23.038 B.2, "This code represents a control |
|
3697 // character and therefore must not be used for language specific |
|
3698 // characters." |
|
3699 if (septet == RIL.PDU_NL_RESERVED_CONTROL) { |
|
3700 continue; |
|
3701 } |
|
3702 |
|
3703 // The character is not found in locking shfit table, but could be |
|
3704 // encoded as <escape><char> with single shift table. Note that it's |
|
3705 // still possible for septet to has the value of PDU_NL_EXTENDED_ESCAPE, |
|
3706 // but we can display it as a space in this case as said in previous |
|
3707 // comment. |
|
3708 length += 2; |
|
3709 } |
|
3710 |
|
3711 return length; |
|
3712 }, |
|
3713 |
|
3714 /** |
|
3715 * Calculate user data length of specified message string encoded in GSM 7Bit |
|
3716 * alphabets. |
|
3717 * |
|
3718 * @param message |
|
3719 * a message string to be encoded. |
|
3720 * @param strict7BitEncoding |
|
3721 * Optional. Enable Latin characters replacement with corresponding |
|
3722 * ones in GSM SMS 7-bit default alphabet. |
|
3723 * |
|
3724 * @return null or an options object with attributes `dcs`, |
|
3725 * `userDataHeaderLength`, `encodedFullBodyLength`, `langIndex`, |
|
3726 * `langShiftIndex`, `segmentMaxSeq` set. |
|
3727 * |
|
3728 * @see #_calculateUserDataLength(). |
|
3729 */ |
|
3730 _calculateUserDataLength7Bit: function(message, strict7BitEncoding) { |
|
3731 let options = null; |
|
3732 let minUserDataSeptets = Number.MAX_VALUE; |
|
3733 for (let i = 0; i < this.enabledGsmTableTuples.length; i++) { |
|
3734 let [langIndex, langShiftIndex] = this.enabledGsmTableTuples[i]; |
|
3735 |
|
3736 const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; |
|
3737 const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; |
|
3738 |
|
3739 let bodySeptets = this._countGsm7BitSeptets(message, |
|
3740 langTable, |
|
3741 langShiftTable, |
|
3742 strict7BitEncoding); |
|
3743 if (bodySeptets < 0) { |
|
3744 continue; |
|
3745 } |
|
3746 |
|
3747 let headerLen = 0; |
|
3748 if (langIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) { |
|
3749 headerLen += 3; // IEI + len + langIndex |
|
3750 } |
|
3751 if (langShiftIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) { |
|
3752 headerLen += 3; // IEI + len + langShiftIndex |
|
3753 } |
|
3754 |
|
3755 // Calculate full user data length, note the extra byte is for header len |
|
3756 let headerSeptets = Math.ceil((headerLen ? headerLen + 1 : 0) * 8 / 7); |
|
3757 let segmentSeptets = RIL.PDU_MAX_USER_DATA_7BIT; |
|
3758 if ((bodySeptets + headerSeptets) > segmentSeptets) { |
|
3759 headerLen += this.segmentRef16Bit ? 6 : 5; |
|
3760 headerSeptets = Math.ceil((headerLen + 1) * 8 / 7); |
|
3761 segmentSeptets -= headerSeptets; |
|
3762 } |
|
3763 |
|
3764 let segments = Math.ceil(bodySeptets / segmentSeptets); |
|
3765 let userDataSeptets = bodySeptets + headerSeptets * segments; |
|
3766 if (userDataSeptets >= minUserDataSeptets) { |
|
3767 continue; |
|
3768 } |
|
3769 |
|
3770 minUserDataSeptets = userDataSeptets; |
|
3771 |
|
3772 options = { |
|
3773 dcs: RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET, |
|
3774 encodedFullBodyLength: bodySeptets, |
|
3775 userDataHeaderLength: headerLen, |
|
3776 langIndex: langIndex, |
|
3777 langShiftIndex: langShiftIndex, |
|
3778 segmentMaxSeq: segments, |
|
3779 segmentChars: segmentSeptets, |
|
3780 }; |
|
3781 } |
|
3782 |
|
3783 return options; |
|
3784 }, |
|
3785 |
|
3786 /** |
|
3787 * Calculate user data length of specified message string encoded in UCS2. |
|
3788 * |
|
3789 * @param message |
|
3790 * a message string to be encoded. |
|
3791 * |
|
3792 * @return an options object with attributes `dcs`, `userDataHeaderLength`, |
|
3793 * `encodedFullBodyLength`, `segmentMaxSeq` set. |
|
3794 * |
|
3795 * @see #_calculateUserDataLength(). |
|
3796 */ |
|
3797 _calculateUserDataLengthUCS2: function(message) { |
|
3798 let bodyChars = message.length; |
|
3799 let headerLen = 0; |
|
3800 let headerChars = Math.ceil((headerLen ? headerLen + 1 : 0) / 2); |
|
3801 let segmentChars = RIL.PDU_MAX_USER_DATA_UCS2; |
|
3802 if ((bodyChars + headerChars) > segmentChars) { |
|
3803 headerLen += this.segmentRef16Bit ? 6 : 5; |
|
3804 headerChars = Math.ceil((headerLen + 1) / 2); |
|
3805 segmentChars -= headerChars; |
|
3806 } |
|
3807 |
|
3808 let segments = Math.ceil(bodyChars / segmentChars); |
|
3809 |
|
3810 return { |
|
3811 dcs: RIL.PDU_DCS_MSG_CODING_16BITS_ALPHABET, |
|
3812 encodedFullBodyLength: bodyChars * 2, |
|
3813 userDataHeaderLength: headerLen, |
|
3814 segmentMaxSeq: segments, |
|
3815 segmentChars: segmentChars, |
|
3816 }; |
|
3817 }, |
|
3818 |
|
3819 /** |
|
3820 * Calculate user data length and its encoding. |
|
3821 * |
|
3822 * @param message |
|
3823 * a message string to be encoded. |
|
3824 * @param strict7BitEncoding |
|
3825 * Optional. Enable Latin characters replacement with corresponding |
|
3826 * ones in GSM SMS 7-bit default alphabet. |
|
3827 * |
|
3828 * @return an options object with some or all of following attributes set: |
|
3829 * |
|
3830 * @param dcs |
|
3831 * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET |
|
3832 * constants. |
|
3833 * @param userDataHeaderLength |
|
3834 * Length of embedded user data header, in bytes. The whole header |
|
3835 * size will be userDataHeaderLength + 1; 0 for no header. |
|
3836 * @param encodedFullBodyLength |
|
3837 * Length of the message body when encoded with the given DCS. For |
|
3838 * UCS2, in bytes; for 7-bit, in septets. |
|
3839 * @param langIndex |
|
3840 * Table index used for normal 7-bit encoded character lookup. |
|
3841 * @param langShiftIndex |
|
3842 * Table index used for escaped 7-bit encoded character lookup. |
|
3843 * @param segmentMaxSeq |
|
3844 * Max sequence number of a multi-part messages, or 1 for single one. |
|
3845 * This number might not be accurate for a multi-part message until |
|
3846 * it's processed by #_fragmentText() again. |
|
3847 */ |
|
3848 _calculateUserDataLength: function(message, strict7BitEncoding) { |
|
3849 let options = this._calculateUserDataLength7Bit(message, strict7BitEncoding); |
|
3850 if (!options) { |
|
3851 options = this._calculateUserDataLengthUCS2(message); |
|
3852 } |
|
3853 |
|
3854 if (DEBUG) this.debug("_calculateUserDataLength: " + JSON.stringify(options)); |
|
3855 return options; |
|
3856 }, |
|
3857 |
|
3858 /** |
|
3859 * Fragment GSM 7-Bit encodable string for transmission. |
|
3860 * |
|
3861 * @param text |
|
3862 * text string to be fragmented. |
|
3863 * @param langTable |
|
3864 * locking shift table string. |
|
3865 * @param langShiftTable |
|
3866 * single shift table string. |
|
3867 * @param segmentSeptets |
|
3868 * Number of available spetets per segment. |
|
3869 * @param strict7BitEncoding |
|
3870 * Optional. Enable Latin characters replacement with corresponding |
|
3871 * ones in GSM SMS 7-bit default alphabet. |
|
3872 * |
|
3873 * @return an array of objects. See #_fragmentText() for detailed definition. |
|
3874 */ |
|
3875 _fragmentText7Bit: function(text, langTable, langShiftTable, segmentSeptets, strict7BitEncoding) { |
|
3876 let ret = []; |
|
3877 let body = "", len = 0; |
|
3878 // If the message is empty, we only push the empty message to ret. |
|
3879 if (text.length === 0) { |
|
3880 ret.push({ |
|
3881 body: text, |
|
3882 encodedBodyLength: text.length, |
|
3883 }); |
|
3884 return ret; |
|
3885 } |
|
3886 |
|
3887 for (let i = 0, inc = 0; i < text.length; i++) { |
|
3888 let c = text.charAt(i); |
|
3889 if (strict7BitEncoding) { |
|
3890 c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c; |
|
3891 } |
|
3892 |
|
3893 let septet = langTable.indexOf(c); |
|
3894 if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) { |
|
3895 continue; |
|
3896 } |
|
3897 |
|
3898 if (septet >= 0) { |
|
3899 inc = 1; |
|
3900 } else { |
|
3901 septet = langShiftTable.indexOf(c); |
|
3902 if (septet == RIL.PDU_NL_RESERVED_CONTROL) { |
|
3903 continue; |
|
3904 } |
|
3905 |
|
3906 inc = 2; |
|
3907 if (septet < 0) { |
|
3908 if (!strict7BitEncoding) { |
|
3909 throw new Error("Given text cannot be encoded with GSM 7-bit Alphabet!"); |
|
3910 } |
|
3911 |
|
3912 // Bug 816082, when strict7BitEncoding is enabled, we should replace |
|
3913 // characters that can't be encoded with GSM 7-Bit alphabets with '*'. |
|
3914 c = "*"; |
|
3915 if (langTable.indexOf(c) >= 0) { |
|
3916 inc = 1; |
|
3917 } |
|
3918 } |
|
3919 } |
|
3920 |
|
3921 if ((len + inc) > segmentSeptets) { |
|
3922 ret.push({ |
|
3923 body: body, |
|
3924 encodedBodyLength: len, |
|
3925 }); |
|
3926 body = c; |
|
3927 len = inc; |
|
3928 } else { |
|
3929 body += c; |
|
3930 len += inc; |
|
3931 } |
|
3932 } |
|
3933 |
|
3934 if (len) { |
|
3935 ret.push({ |
|
3936 body: body, |
|
3937 encodedBodyLength: len, |
|
3938 }); |
|
3939 } |
|
3940 |
|
3941 return ret; |
|
3942 }, |
|
3943 |
|
3944 /** |
|
3945 * Fragment UCS2 encodable string for transmission. |
|
3946 * |
|
3947 * @param text |
|
3948 * text string to be fragmented. |
|
3949 * @param segmentChars |
|
3950 * Number of available characters per segment. |
|
3951 * |
|
3952 * @return an array of objects. See #_fragmentText() for detailed definition. |
|
3953 */ |
|
3954 _fragmentTextUCS2: function(text, segmentChars) { |
|
3955 let ret = []; |
|
3956 // If the message is empty, we only push the empty message to ret. |
|
3957 if (text.length === 0) { |
|
3958 ret.push({ |
|
3959 body: text, |
|
3960 encodedBodyLength: text.length, |
|
3961 }); |
|
3962 return ret; |
|
3963 } |
|
3964 |
|
3965 for (let offset = 0; offset < text.length; offset += segmentChars) { |
|
3966 let str = text.substr(offset, segmentChars); |
|
3967 ret.push({ |
|
3968 body: str, |
|
3969 encodedBodyLength: str.length * 2, |
|
3970 }); |
|
3971 } |
|
3972 |
|
3973 return ret; |
|
3974 }, |
|
3975 |
|
3976 /** |
|
3977 * Fragment string for transmission. |
|
3978 * |
|
3979 * Fragment input text string into an array of objects that contains |
|
3980 * attributes `body`, substring for this segment, `encodedBodyLength`, |
|
3981 * length of the encoded segment body in septets. |
|
3982 * |
|
3983 * @param text |
|
3984 * Text string to be fragmented. |
|
3985 * @param options |
|
3986 * Optional pre-calculated option object. The output array will be |
|
3987 * stored at options.segments if there are multiple segments. |
|
3988 * @param strict7BitEncoding |
|
3989 * Optional. Enable Latin characters replacement with corresponding |
|
3990 * ones in GSM SMS 7-bit default alphabet. |
|
3991 * |
|
3992 * @return Populated options object. |
|
3993 */ |
|
3994 _fragmentText: function(text, options, strict7BitEncoding) { |
|
3995 if (!options) { |
|
3996 options = this._calculateUserDataLength(text, strict7BitEncoding); |
|
3997 } |
|
3998 |
|
3999 if (options.dcs == RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
|
4000 const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[options.langIndex]; |
|
4001 const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[options.langShiftIndex]; |
|
4002 options.segments = this._fragmentText7Bit(text, |
|
4003 langTable, langShiftTable, |
|
4004 options.segmentChars, |
|
4005 strict7BitEncoding); |
|
4006 } else { |
|
4007 options.segments = this._fragmentTextUCS2(text, |
|
4008 options.segmentChars); |
|
4009 } |
|
4010 |
|
4011 // Re-sync options.segmentMaxSeq with actual length of returning array. |
|
4012 options.segmentMaxSeq = options.segments.length; |
|
4013 |
|
4014 return options; |
|
4015 }, |
|
4016 |
|
4017 getSegmentInfoForText: function(text, request) { |
|
4018 let strict7BitEncoding; |
|
4019 try { |
|
4020 strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding"); |
|
4021 } catch (e) { |
|
4022 strict7BitEncoding = false; |
|
4023 } |
|
4024 |
|
4025 let options = this._fragmentText(text, null, strict7BitEncoding); |
|
4026 let charsInLastSegment; |
|
4027 if (options.segmentMaxSeq) { |
|
4028 let lastSegment = options.segments[options.segmentMaxSeq - 1]; |
|
4029 charsInLastSegment = lastSegment.encodedBodyLength; |
|
4030 if (options.dcs == RIL.PDU_DCS_MSG_CODING_16BITS_ALPHABET) { |
|
4031 // In UCS2 encoding, encodedBodyLength is in octets. |
|
4032 charsInLastSegment /= 2; |
|
4033 } |
|
4034 } else { |
|
4035 charsInLastSegment = 0; |
|
4036 } |
|
4037 |
|
4038 let result = gMobileMessageService |
|
4039 .createSmsSegmentInfo(options.segmentMaxSeq, |
|
4040 options.segmentChars, |
|
4041 options.segmentChars - charsInLastSegment); |
|
4042 request.notifySegmentInfoForTextGot(result); |
|
4043 }, |
|
4044 |
|
4045 getSmscAddress: function(request) { |
|
4046 this.workerMessenger.send("getSmscAddress", |
|
4047 null, |
|
4048 (function(response) { |
|
4049 if (!response.errorMsg) { |
|
4050 request.notifyGetSmscAddress(response.smscAddress); |
|
4051 } else { |
|
4052 request.notifyGetSmscAddressFailed(response.errorMsg); |
|
4053 } |
|
4054 }).bind(this)); |
|
4055 }, |
|
4056 |
|
4057 sendSMS: function(number, message, silent, request) { |
|
4058 let strict7BitEncoding; |
|
4059 try { |
|
4060 strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding"); |
|
4061 } catch (e) { |
|
4062 strict7BitEncoding = false; |
|
4063 } |
|
4064 |
|
4065 let options = this._fragmentText(message, null, strict7BitEncoding); |
|
4066 options.number = PhoneNumberUtils.normalize(number); |
|
4067 let requestStatusReport; |
|
4068 try { |
|
4069 requestStatusReport = |
|
4070 Services.prefs.getBoolPref("dom.sms.requestStatusReport"); |
|
4071 } catch (e) { |
|
4072 requestStatusReport = true; |
|
4073 } |
|
4074 options.requestStatusReport = requestStatusReport && !silent; |
|
4075 if (options.segmentMaxSeq > 1) { |
|
4076 options.segmentRef16Bit = this.segmentRef16Bit; |
|
4077 options.segmentRef = this.nextSegmentRef; |
|
4078 } |
|
4079 |
|
4080 let notifyResult = (function notifyResult(rv, domMessage) { |
|
4081 if (!Components.isSuccessCode(rv)) { |
|
4082 if (DEBUG) this.debug("Error! Fail to save sending message! rv = " + rv); |
|
4083 request.notifySendMessageFailed( |
|
4084 gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv)); |
|
4085 Services.obs.notifyObservers(domMessage, kSmsFailedObserverTopic, null); |
|
4086 return; |
|
4087 } |
|
4088 |
|
4089 if (!silent) { |
|
4090 Services.obs.notifyObservers(domMessage, kSmsSendingObserverTopic, null); |
|
4091 } |
|
4092 |
|
4093 // If the radio is disabled or the SIM card is not ready, just directly |
|
4094 // return with the corresponding error code. |
|
4095 let errorCode; |
|
4096 if (!PhoneNumberUtils.isPlainPhoneNumber(options.number)) { |
|
4097 if (DEBUG) this.debug("Error! Address is invalid when sending SMS: " + |
|
4098 options.number); |
|
4099 errorCode = Ci.nsIMobileMessageCallback.INVALID_ADDRESS_ERROR; |
|
4100 } else if (this.rilContext.detailedRadioState == |
|
4101 RIL.GECKO_DETAILED_RADIOSTATE_DISABLED) { |
|
4102 if (DEBUG) this.debug("Error! Radio is disabled when sending SMS."); |
|
4103 errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR; |
|
4104 } else if (this.rilContext.cardState != "ready") { |
|
4105 if (DEBUG) this.debug("Error! SIM card is not ready when sending SMS."); |
|
4106 errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR; |
|
4107 } |
|
4108 if (errorCode) { |
|
4109 if (silent) { |
|
4110 request.notifySendMessageFailed(errorCode); |
|
4111 return; |
|
4112 } |
|
4113 |
|
4114 gMobileMessageDatabaseService |
|
4115 .setMessageDeliveryByMessageId(domMessage.id, |
|
4116 null, |
|
4117 DOM_MOBILE_MESSAGE_DELIVERY_ERROR, |
|
4118 RIL.GECKO_SMS_DELIVERY_STATUS_ERROR, |
|
4119 null, |
|
4120 function notifyResult(rv, domMessage) { |
|
4121 // TODO bug 832140 handle !Components.isSuccessCode(rv) |
|
4122 request.notifySendMessageFailed(errorCode); |
|
4123 Services.obs.notifyObservers(domMessage, kSmsFailedObserverTopic, null); |
|
4124 }); |
|
4125 return; |
|
4126 } |
|
4127 |
|
4128 // Keep current SMS message info for sent/delivered notifications |
|
4129 let context = { |
|
4130 request: request, |
|
4131 sms: domMessage, |
|
4132 requestStatusReport: options.requestStatusReport, |
|
4133 silent: silent |
|
4134 }; |
|
4135 |
|
4136 // This is the entry point starting to send SMS. |
|
4137 this.workerMessenger.send("sendSMS", options, |
|
4138 (function(context, response) { |
|
4139 if (response.errorMsg) { |
|
4140 // Failed to send SMS out. |
|
4141 let error = Ci.nsIMobileMessageCallback.UNKNOWN_ERROR; |
|
4142 switch (response.errorMsg) { |
|
4143 case RIL.ERROR_RADIO_NOT_AVAILABLE: |
|
4144 error = Ci.nsIMobileMessageCallback.NO_SIGNAL_ERROR; |
|
4145 break; |
|
4146 case RIL.ERROR_FDN_CHECK_FAILURE: |
|
4147 error = Ci.nsIMobileMessageCallback.FDN_CHECK_ERROR; |
|
4148 break; |
|
4149 } |
|
4150 |
|
4151 if (context.silent) { |
|
4152 context.request.notifySendMessageFailed(error); |
|
4153 return false; |
|
4154 } |
|
4155 |
|
4156 gMobileMessageDatabaseService |
|
4157 .setMessageDeliveryByMessageId(context.sms.id, |
|
4158 null, |
|
4159 DOM_MOBILE_MESSAGE_DELIVERY_ERROR, |
|
4160 RIL.GECKO_SMS_DELIVERY_STATUS_ERROR, |
|
4161 null, |
|
4162 function notifyResult(rv, domMessage) { |
|
4163 // TODO bug 832140 handle !Components.isSuccessCode(rv) |
|
4164 context.request.notifySendMessageFailed(error); |
|
4165 Services.obs.notifyObservers(domMessage, kSmsFailedObserverTopic, null); |
|
4166 }); |
|
4167 return false; |
|
4168 } // End of send failure. |
|
4169 |
|
4170 if (response.deliveryStatus) { |
|
4171 // Message delivery. |
|
4172 gMobileMessageDatabaseService |
|
4173 .setMessageDeliveryByMessageId(context.sms.id, |
|
4174 null, |
|
4175 context.sms.delivery, |
|
4176 response.deliveryStatus, |
|
4177 null, |
|
4178 (function notifyResult(rv, domMessage) { |
|
4179 // TODO bug 832140 handle !Components.isSuccessCode(rv) |
|
4180 |
|
4181 let topic = (response.deliveryStatus == |
|
4182 RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS) |
|
4183 ? kSmsDeliverySuccessObserverTopic |
|
4184 : kSmsDeliveryErrorObserverTopic; |
|
4185 |
|
4186 // Broadcasting a "sms-delivery-success" system message to open apps. |
|
4187 if (topic == kSmsDeliverySuccessObserverTopic) { |
|
4188 this.broadcastSmsSystemMessage(topic, domMessage); |
|
4189 } |
|
4190 |
|
4191 // Notifying observers the delivery status is updated. |
|
4192 Services.obs.notifyObservers(domMessage, topic, null); |
|
4193 }).bind(this)); |
|
4194 |
|
4195 // Send transaction has ended completely. |
|
4196 return false; |
|
4197 } // End of message delivery. |
|
4198 |
|
4199 // Message sent. |
|
4200 if (context.silent) { |
|
4201 // There is no way to modify nsIDOMMozSmsMessage attributes as they |
|
4202 // are read only so we just create a new sms instance to send along |
|
4203 // with the notification. |
|
4204 let sms = context.sms; |
|
4205 context.request.notifyMessageSent( |
|
4206 gMobileMessageService.createSmsMessage(sms.id, |
|
4207 sms.threadId, |
|
4208 sms.iccId, |
|
4209 DOM_MOBILE_MESSAGE_DELIVERY_SENT, |
|
4210 sms.deliveryStatus, |
|
4211 sms.sender, |
|
4212 sms.receiver, |
|
4213 sms.body, |
|
4214 sms.messageClass, |
|
4215 sms.timestamp, |
|
4216 Date.now(), |
|
4217 0, |
|
4218 sms.read)); |
|
4219 // We don't wait for SMS-DELIVER-REPORT for silent one. |
|
4220 return false; |
|
4221 } |
|
4222 |
|
4223 gMobileMessageDatabaseService |
|
4224 .setMessageDeliveryByMessageId(context.sms.id, |
|
4225 null, |
|
4226 DOM_MOBILE_MESSAGE_DELIVERY_SENT, |
|
4227 context.sms.deliveryStatus, |
|
4228 null, |
|
4229 (function notifyResult(rv, domMessage) { |
|
4230 // TODO bug 832140 handle !Components.isSuccessCode(rv) |
|
4231 |
|
4232 if (context.requestStatusReport) { |
|
4233 context.sms = domMessage; |
|
4234 } |
|
4235 |
|
4236 this.broadcastSmsSystemMessage(kSmsSentObserverTopic, domMessage); |
|
4237 context.request.notifyMessageSent(domMessage); |
|
4238 Services.obs.notifyObservers(domMessage, kSmsSentObserverTopic, null); |
|
4239 }).bind(this)); |
|
4240 |
|
4241 // Only keep current context if we have requested for delivery report. |
|
4242 return context.requestStatusReport; |
|
4243 }).bind(this, context)); // End of |workerMessenger.send| callback. |
|
4244 }).bind(this); // End of DB saveSendingMessage callback. |
|
4245 |
|
4246 let sendingMessage = { |
|
4247 type: "sms", |
|
4248 sender: this.getPhoneNumber(), |
|
4249 receiver: number, |
|
4250 body: message, |
|
4251 deliveryStatusRequested: options.requestStatusReport, |
|
4252 timestamp: Date.now(), |
|
4253 iccId: this.getIccId() |
|
4254 }; |
|
4255 |
|
4256 if (silent) { |
|
4257 let delivery = DOM_MOBILE_MESSAGE_DELIVERY_SENDING; |
|
4258 let deliveryStatus = RIL.GECKO_SMS_DELIVERY_STATUS_PENDING; |
|
4259 let domMessage = |
|
4260 gMobileMessageService.createSmsMessage(-1, // id |
|
4261 0, // threadId |
|
4262 sendingMessage.iccId, |
|
4263 delivery, |
|
4264 deliveryStatus, |
|
4265 sendingMessage.sender, |
|
4266 sendingMessage.receiver, |
|
4267 sendingMessage.body, |
|
4268 "normal", // message class |
|
4269 sendingMessage.timestamp, |
|
4270 0, |
|
4271 0, |
|
4272 false); |
|
4273 notifyResult(Cr.NS_OK, domMessage); |
|
4274 return; |
|
4275 } |
|
4276 |
|
4277 let id = gMobileMessageDatabaseService.saveSendingMessage( |
|
4278 sendingMessage, notifyResult); |
|
4279 }, |
|
4280 |
|
4281 // TODO: Bug 928861 - B2G NetworkManager: Provide a more generic function |
|
4282 // for connecting |
|
4283 setupDataCallByType: function(apntype) { |
|
4284 let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); |
|
4285 connHandler.setupDataCallByType(apntype); |
|
4286 }, |
|
4287 |
|
4288 // TODO: Bug 928861 - B2G NetworkManager: Provide a more generic function |
|
4289 // for connecting |
|
4290 deactivateDataCallByType: function(apntype) { |
|
4291 let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); |
|
4292 connHandler.deactivateDataCallByType(apntype); |
|
4293 }, |
|
4294 |
|
4295 // TODO: Bug 904514 - [meta] NetworkManager enhancement |
|
4296 getDataCallStateByType: function(apntype) { |
|
4297 let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); |
|
4298 return connHandler.getDataCallStateByType(apntype); |
|
4299 }, |
|
4300 |
|
4301 setupDataCall: function(radioTech, apn, user, passwd, chappap, pdptype) { |
|
4302 this.workerMessenger.send("setupDataCall", { radioTech: radioTech, |
|
4303 apn: apn, |
|
4304 user: user, |
|
4305 passwd: passwd, |
|
4306 chappap: chappap, |
|
4307 pdptype: pdptype }); |
|
4308 }, |
|
4309 |
|
4310 deactivateDataCall: function(cid, reason) { |
|
4311 this.workerMessenger.send("deactivateDataCall", { cid: cid, |
|
4312 reason: reason }); |
|
4313 }, |
|
4314 |
|
4315 sendWorkerMessage: function(rilMessageType, message, callback) { |
|
4316 if (callback) { |
|
4317 this.workerMessenger.send(rilMessageType, message, function(response) { |
|
4318 return callback.handleResponse(response); |
|
4319 }); |
|
4320 } else { |
|
4321 this.workerMessenger.send(rilMessageType, message); |
|
4322 } |
|
4323 } |
|
4324 }; |
|
4325 |
|
4326 function RILNetworkInterface(dataConnectionHandler, apnSetting) { |
|
4327 this.dataConnectionHandler = dataConnectionHandler; |
|
4328 this.apnSetting = apnSetting; |
|
4329 this.connectedTypes = []; |
|
4330 |
|
4331 this.ips = []; |
|
4332 this.prefixLengths = []; |
|
4333 this.dnses = []; |
|
4334 this.gateways = []; |
|
4335 } |
|
4336 |
|
4337 RILNetworkInterface.prototype = { |
|
4338 classID: RILNETWORKINTERFACE_CID, |
|
4339 classInfo: XPCOMUtils.generateCI({classID: RILNETWORKINTERFACE_CID, |
|
4340 classDescription: "RILNetworkInterface", |
|
4341 interfaces: [Ci.nsINetworkInterface, |
|
4342 Ci.nsIRilNetworkInterface]}), |
|
4343 QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface, |
|
4344 Ci.nsIRilNetworkInterface]), |
|
4345 |
|
4346 // nsINetworkInterface |
|
4347 |
|
4348 NETWORK_STATE_UNKNOWN: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN, |
|
4349 NETWORK_STATE_CONNECTING: Ci.nsINetworkInterface.CONNECTING, |
|
4350 NETWORK_STATE_CONNECTED: Ci.nsINetworkInterface.CONNECTED, |
|
4351 NETWORK_STATE_DISCONNECTING: Ci.nsINetworkInterface.DISCONNECTING, |
|
4352 NETWORK_STATE_DISCONNECTED: Ci.nsINetworkInterface.DISCONNECTED, |
|
4353 |
|
4354 NETWORK_TYPE_WIFI: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, |
|
4355 NETWORK_TYPE_MOBILE: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, |
|
4356 NETWORK_TYPE_MOBILE_MMS: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS, |
|
4357 NETWORK_TYPE_MOBILE_SUPL: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL, |
|
4358 NETWORK_TYPE_MOBILE_IMS: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_IMS, |
|
4359 NETWORK_TYPE_MOBILE_DUN: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN, |
|
4360 // The network manager should only need to add the host route for "other" |
|
4361 // types, which is the same handling method as the supl type. So let the |
|
4362 // definition of other types to be the same as the one of supl type. |
|
4363 NETWORK_TYPE_MOBILE_OTHERS: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL, |
|
4364 |
|
4365 /** |
|
4366 * Standard values for the APN connection retry process |
|
4367 * Retry funcion: time(secs) = A * numer_of_retries^2 + B |
|
4368 */ |
|
4369 NETWORK_APNRETRY_FACTOR: 8, |
|
4370 NETWORK_APNRETRY_ORIGIN: 3, |
|
4371 NETWORK_APNRETRY_MAXRETRIES: 10, |
|
4372 |
|
4373 // Event timer for connection retries |
|
4374 timer: null, |
|
4375 |
|
4376 /** |
|
4377 * nsINetworkInterface Implementation |
|
4378 */ |
|
4379 |
|
4380 state: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN, |
|
4381 |
|
4382 get type() { |
|
4383 if (this.connectedTypes.indexOf("default") != -1) { |
|
4384 return this.NETWORK_TYPE_MOBILE; |
|
4385 } |
|
4386 if (this.connectedTypes.indexOf("mms") != -1) { |
|
4387 return this.NETWORK_TYPE_MOBILE_MMS; |
|
4388 } |
|
4389 if (this.connectedTypes.indexOf("supl") != -1) { |
|
4390 return this.NETWORK_TYPE_MOBILE_SUPL; |
|
4391 } |
|
4392 if (this.connectedTypes.indexOf("ims") != -1) { |
|
4393 return this.NETWORK_TYPE_MOBILE_IMS; |
|
4394 } |
|
4395 if (this.connectedTypes.indexOf("dun") != -1) { |
|
4396 return this.NETWORK_TYPE_MOBILE_DUN; |
|
4397 } |
|
4398 |
|
4399 return this.NETWORK_TYPE_MOBILE_OTHERS; |
|
4400 }, |
|
4401 |
|
4402 name: null, |
|
4403 |
|
4404 ips: null, |
|
4405 |
|
4406 prefixLengths: null, |
|
4407 |
|
4408 gateways: null, |
|
4409 |
|
4410 dnses: null, |
|
4411 |
|
4412 get httpProxyHost() { |
|
4413 return this.apnSetting.proxy || ""; |
|
4414 }, |
|
4415 |
|
4416 get httpProxyPort() { |
|
4417 return this.apnSetting.port || ""; |
|
4418 }, |
|
4419 |
|
4420 /** |
|
4421 * nsIRilNetworkInterface Implementation |
|
4422 */ |
|
4423 |
|
4424 get serviceId() { |
|
4425 return this.dataConnectionHandler.clientId; |
|
4426 }, |
|
4427 |
|
4428 get iccId() { |
|
4429 let iccInfo = this.dataConnectionHandler.radioInterface.rilContext.iccInfo; |
|
4430 return iccInfo && iccInfo.iccid; |
|
4431 }, |
|
4432 |
|
4433 get mmsc() { |
|
4434 if (!this.inConnectedTypes("mms")) { |
|
4435 if (DEBUG) this.debug("Error! Only MMS network can get MMSC."); |
|
4436 throw Cr.NS_ERROR_UNEXPECTED; |
|
4437 } |
|
4438 |
|
4439 let mmsc = this.apnSetting.mmsc; |
|
4440 if (!mmsc) { |
|
4441 try { |
|
4442 mmsc = Services.prefs.getCharPref("ril.mms.mmsc"); |
|
4443 } catch (e) { |
|
4444 mmsc = ""; |
|
4445 } |
|
4446 } |
|
4447 |
|
4448 return mmsc; |
|
4449 }, |
|
4450 |
|
4451 get mmsProxy() { |
|
4452 if (!this.inConnectedTypes("mms")) { |
|
4453 if (DEBUG) this.debug("Error! Only MMS network can get MMS proxy."); |
|
4454 throw Cr.NS_ERROR_UNEXPECTED; |
|
4455 } |
|
4456 |
|
4457 let proxy = this.apnSetting.mmsproxy; |
|
4458 if (!proxy) { |
|
4459 try { |
|
4460 proxy = Services.prefs.getCharPref("ril.mms.mmsproxy"); |
|
4461 } catch (e) { |
|
4462 proxy = ""; |
|
4463 } |
|
4464 } |
|
4465 |
|
4466 return proxy; |
|
4467 }, |
|
4468 |
|
4469 get mmsPort() { |
|
4470 if (!this.inConnectedTypes("mms")) { |
|
4471 if (DEBUG) this.debug("Error! Only MMS network can get MMS port."); |
|
4472 throw Cr.NS_ERROR_UNEXPECTED; |
|
4473 } |
|
4474 |
|
4475 let port = this.apnSetting.mmsport; |
|
4476 if (!port) { |
|
4477 try { |
|
4478 port = Services.prefs.getIntPref("ril.mms.mmsport"); |
|
4479 } catch (e) { |
|
4480 port = -1; |
|
4481 } |
|
4482 } |
|
4483 |
|
4484 return port; |
|
4485 }, |
|
4486 |
|
4487 getAddresses: function (ips, prefixLengths) { |
|
4488 ips.value = this.ips.slice(); |
|
4489 prefixLengths.value = this.prefixLengths.slice(); |
|
4490 |
|
4491 return this.ips.length; |
|
4492 }, |
|
4493 |
|
4494 getGateways: function (count) { |
|
4495 if (count) { |
|
4496 count.value = this.gateways.length; |
|
4497 } |
|
4498 return this.gateways.slice(); |
|
4499 }, |
|
4500 |
|
4501 getDnses: function (count) { |
|
4502 if (count) { |
|
4503 count.value = this.dnses.length; |
|
4504 } |
|
4505 return this.dnses.slice(); |
|
4506 }, |
|
4507 |
|
4508 debug: function(s) { |
|
4509 dump("-*- RILNetworkInterface[" + this.dataConnectionHandler.clientId + ":" + |
|
4510 this.type + "]: " + s + "\n"); |
|
4511 }, |
|
4512 |
|
4513 dataCallError: function(message) { |
|
4514 if (message.apn != this.apnSetting.apn) { |
|
4515 return; |
|
4516 } |
|
4517 if (DEBUG) this.debug("Data call error on APN: " + message.apn); |
|
4518 this.reset(); |
|
4519 }, |
|
4520 |
|
4521 dataCallStateChanged: function(datacall) { |
|
4522 if (this.cid && this.cid != datacall.cid) { |
|
4523 // If data call for this connection existed but cid mismatched, |
|
4524 // it means this datacall state change is not for us. |
|
4525 return; |
|
4526 } |
|
4527 // If data call for this connection does not exist, it could be state |
|
4528 // change for new data call. We only update data call state change |
|
4529 // if APN name matched. |
|
4530 if (!this.cid && datacall.apn != this.apnSetting.apn) { |
|
4531 return; |
|
4532 } |
|
4533 if (DEBUG) { |
|
4534 this.debug("Data call ID: " + datacall.cid + ", interface name: " + |
|
4535 datacall.ifname + ", APN name: " + datacall.apn); |
|
4536 } |
|
4537 if (this.connecting && |
|
4538 (datacall.state == RIL.GECKO_NETWORK_STATE_CONNECTING || |
|
4539 datacall.state == RIL.GECKO_NETWORK_STATE_CONNECTED)) { |
|
4540 this.connecting = false; |
|
4541 this.cid = datacall.cid; |
|
4542 this.name = datacall.ifname; |
|
4543 for (let entry of datacall.addresses) { |
|
4544 this.ips.push(entry.address); |
|
4545 this.prefixLengths.push(entry.prefixLength); |
|
4546 } |
|
4547 this.gateways = datacall.gateways.slice(); |
|
4548 this.dnses = datacall.dnses.slice(); |
|
4549 if (!this.registeredAsNetworkInterface) { |
|
4550 gNetworkManager.registerNetworkInterface(this); |
|
4551 this.registeredAsNetworkInterface = true; |
|
4552 } |
|
4553 } |
|
4554 // In current design, we don't update status of secondary APN if it shares |
|
4555 // same APN name with the default APN. In this condition, this.cid will |
|
4556 // not be set and we don't want to update its status. |
|
4557 if (this.cid == null) { |
|
4558 return; |
|
4559 } |
|
4560 |
|
4561 if (this.state == datacall.state) { |
|
4562 if (datacall.state != RIL.GECKO_NETWORK_STATE_CONNECTED) { |
|
4563 return; |
|
4564 } |
|
4565 // State remains connected, check for minor changes. |
|
4566 let changed = false; |
|
4567 if (this.ips.length != datacall.addresses.length) { |
|
4568 changed = true; |
|
4569 this.ips = []; |
|
4570 this.prefixLengths = []; |
|
4571 for (let entry of datacall.addresses) { |
|
4572 this.ips.push(entry.address); |
|
4573 this.prefixLengths.push(entry.prefixLength); |
|
4574 } |
|
4575 } |
|
4576 |
|
4577 let reduceFunc = function(aRhs, aChanged, aElement, aIndex) { |
|
4578 return aChanged || (aElement != aRhs[aIndex]); |
|
4579 }; |
|
4580 for (let field of ["gateways", "dnses"]) { |
|
4581 let lhs = this[field], rhs = datacall[field]; |
|
4582 if (lhs.length != rhs.length || |
|
4583 lhs.reduce(reduceFunc.bind(null, rhs), false)) { |
|
4584 changed = true; |
|
4585 this[field] = rhs.slice(); |
|
4586 } |
|
4587 } |
|
4588 |
|
4589 if (changed) { |
|
4590 if (DEBUG) this.debug("Notify for data call minor changes."); |
|
4591 Services.obs.notifyObservers(this, |
|
4592 kNetworkInterfaceStateChangedTopic, |
|
4593 null); |
|
4594 } |
|
4595 return; |
|
4596 } |
|
4597 |
|
4598 this.state = datacall.state; |
|
4599 |
|
4600 Services.obs.notifyObservers(this, |
|
4601 kNetworkInterfaceStateChangedTopic, |
|
4602 null); |
|
4603 |
|
4604 if ((this.state == RIL.GECKO_NETWORK_STATE_UNKNOWN || |
|
4605 this.state == RIL.GECKO_NETWORK_STATE_DISCONNECTED) && |
|
4606 this.registeredAsNetworkInterface) { |
|
4607 gNetworkManager.unregisterNetworkInterface(this); |
|
4608 this.registeredAsNetworkInterface = false; |
|
4609 this.cid = null; |
|
4610 this.connectedTypes = []; |
|
4611 |
|
4612 this.ips = []; |
|
4613 this.prefixLengths = []; |
|
4614 this.dnses = []; |
|
4615 this.gateways = []; |
|
4616 } |
|
4617 |
|
4618 // In case the data setting changed while the datacall was being started or |
|
4619 // ended, let's re-check the setting and potentially adjust the datacall |
|
4620 // state again. |
|
4621 let apnSettings = this.dataConnectionHandler.apnSettings; |
|
4622 if (apnSettings.byType.default && |
|
4623 (apnSettings.byType.default.apn == this.apnSetting.apn)) { |
|
4624 this.dataConnectionHandler.updateRILNetworkInterface(); |
|
4625 } |
|
4626 }, |
|
4627 |
|
4628 // Helpers |
|
4629 |
|
4630 cid: null, |
|
4631 registeredAsDataCallCallback: false, |
|
4632 registeredAsNetworkInterface: false, |
|
4633 connecting: false, |
|
4634 apnSetting: null, |
|
4635 |
|
4636 // APN failed connections. Retry counter |
|
4637 apnRetryCounter: 0, |
|
4638 |
|
4639 connectedTypes: null, |
|
4640 |
|
4641 inConnectedTypes: function(type) { |
|
4642 return this.connectedTypes.indexOf(type) != -1; |
|
4643 }, |
|
4644 |
|
4645 get connected() { |
|
4646 return this.state == RIL.GECKO_NETWORK_STATE_CONNECTED; |
|
4647 }, |
|
4648 |
|
4649 connect: function(apntype) { |
|
4650 if (apntype && !this.inConnectedTypes(apntype)) { |
|
4651 this.connectedTypes.push(apntype); |
|
4652 } |
|
4653 |
|
4654 if (this.connecting || this.connected) { |
|
4655 return; |
|
4656 } |
|
4657 |
|
4658 // When the retry mechanism is running in background and someone calls |
|
4659 // disconnect(), this.connectedTypes.length has chances to become 0. |
|
4660 if (!this.connectedTypes.length) { |
|
4661 return; |
|
4662 } |
|
4663 |
|
4664 if (!this.registeredAsDataCallCallback) { |
|
4665 this.dataConnectionHandler.registerDataCallCallback(this); |
|
4666 this.registeredAsDataCallCallback = true; |
|
4667 } |
|
4668 |
|
4669 if (!this.apnSetting.apn) { |
|
4670 if (DEBUG) this.debug("APN name is empty, nothing to do."); |
|
4671 return; |
|
4672 } |
|
4673 |
|
4674 if (DEBUG) { |
|
4675 this.debug("Going to set up data connection with APN " + |
|
4676 this.apnSetting.apn); |
|
4677 } |
|
4678 let radioInterface = this.dataConnectionHandler.radioInterface; |
|
4679 let radioTechType = radioInterface.rilContext.data.type; |
|
4680 let radioTechnology = RIL.GECKO_RADIO_TECH.indexOf(radioTechType); |
|
4681 let authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(this.apnSetting.authtype); |
|
4682 // Use the default authType if the value in database is invalid. |
|
4683 // For the case that user might not select the authentication type. |
|
4684 if (authType == -1) { |
|
4685 if (DEBUG) { |
|
4686 this.debug("Invalid authType " + this.apnSetting.authtype); |
|
4687 } |
|
4688 authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(RIL.GECKO_DATACALL_AUTH_DEFAULT); |
|
4689 } |
|
4690 let pdpType = RIL.GECKO_DATACALL_PDP_TYPE_IP; |
|
4691 if (RILQUIRKS_HAVE_IPV6) { |
|
4692 pdpType = !radioInterface.rilContext.data.roaming |
|
4693 ? this.apnSetting.protocol |
|
4694 : this.apnSetting.roaming_protocol; |
|
4695 if (RIL.RIL_DATACALL_PDP_TYPES.indexOf(pdpType) < 0) { |
|
4696 if (DEBUG) { |
|
4697 this.debug("Invalid pdpType '" + pdpType + "', using '" + |
|
4698 RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT + "'"); |
|
4699 } |
|
4700 pdpType = RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT; |
|
4701 } |
|
4702 } |
|
4703 radioInterface.setupDataCall(radioTechnology, |
|
4704 this.apnSetting.apn, |
|
4705 this.apnSetting.user, |
|
4706 this.apnSetting.password, |
|
4707 authType, |
|
4708 pdpType); |
|
4709 this.connecting = true; |
|
4710 }, |
|
4711 |
|
4712 reset: function() { |
|
4713 let apnRetryTimer; |
|
4714 this.connecting = false; |
|
4715 // We will retry the connection in increasing times |
|
4716 // based on the function: time = A * numer_of_retries^2 + B |
|
4717 if (this.apnRetryCounter >= this.NETWORK_APNRETRY_MAXRETRIES) { |
|
4718 this.apnRetryCounter = 0; |
|
4719 this.timer = null; |
|
4720 this.connectedTypes = []; |
|
4721 if (DEBUG) this.debug("Too many APN Connection retries - STOP retrying"); |
|
4722 return; |
|
4723 } |
|
4724 |
|
4725 apnRetryTimer = this.NETWORK_APNRETRY_FACTOR * |
|
4726 (this.apnRetryCounter * this.apnRetryCounter) + |
|
4727 this.NETWORK_APNRETRY_ORIGIN; |
|
4728 this.apnRetryCounter++; |
|
4729 if (DEBUG) { |
|
4730 this.debug("Data call - APN Connection Retry Timer (secs-counter): " + |
|
4731 apnRetryTimer + "-" + this.apnRetryCounter); |
|
4732 } |
|
4733 |
|
4734 if (this.timer == null) { |
|
4735 // Event timer for connection retries |
|
4736 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
|
4737 } |
|
4738 this.timer.initWithCallback(this, apnRetryTimer * 1000, |
|
4739 Ci.nsITimer.TYPE_ONE_SHOT); |
|
4740 }, |
|
4741 |
|
4742 disconnect: function(apntype) { |
|
4743 let index = this.connectedTypes.indexOf(apntype); |
|
4744 if (index != -1) { |
|
4745 this.connectedTypes.splice(index, 1); |
|
4746 } |
|
4747 |
|
4748 if (this.connectedTypes.length) { |
|
4749 return; |
|
4750 } |
|
4751 |
|
4752 if (this.state == RIL.GECKO_NETWORK_STATE_DISCONNECTING || |
|
4753 this.state == RIL.GECKO_NETWORK_STATE_DISCONNECTED || |
|
4754 this.state == RIL.GECKO_NETWORK_STATE_UNKNOWN) { |
|
4755 return; |
|
4756 } |
|
4757 let reason = RIL.DATACALL_DEACTIVATE_NO_REASON; |
|
4758 if (DEBUG) this.debug("Going to disconnet data connection " + this.cid); |
|
4759 this.dataConnectionHandler.radioInterface.deactivateDataCall(this.cid, |
|
4760 reason); |
|
4761 }, |
|
4762 |
|
4763 // Entry method for timer events. Used to reconnect to a failed APN |
|
4764 notify: function(timer) { |
|
4765 this.connect(); |
|
4766 }, |
|
4767 |
|
4768 shutdown: function() { |
|
4769 this.timer = null; |
|
4770 } |
|
4771 |
|
4772 }; |
|
4773 |
|
4774 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RadioInterfaceLayer]); |