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