michael@0: /* Copyright 2012 Mozilla Foundation and Mozilla contributors michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/Sntp.jsm"); michael@0: Cu.import("resource://gre/modules/systemlibs.js"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: michael@0: var RIL = {}; michael@0: Cu.import("resource://gre/modules/ril_consts.js", RIL); michael@0: michael@0: // set to true in ril_consts.js to see debug messages michael@0: var DEBUG = RIL.DEBUG_RIL; michael@0: michael@0: // Read debug setting from pref michael@0: let debugPref = false; michael@0: try { michael@0: debugPref = Services.prefs.getBoolPref("ril.debugging.enabled"); michael@0: } catch(e) { michael@0: debugPref = false; michael@0: } michael@0: DEBUG = RIL.DEBUG_RIL || debugPref; michael@0: michael@0: function debug(s) { michael@0: dump("-*- RadioInterfaceLayer: " + s + "\n"); michael@0: } michael@0: michael@0: // Ril quirk to attach data registration on demand. michael@0: let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = michael@0: libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true"; michael@0: michael@0: // Ril quirk to always turn the radio off for the client without SIM card michael@0: // except hw default client. michael@0: let RILQUIRKS_RADIO_OFF_WO_CARD = michael@0: libcutils.property_get("ro.moz.ril.radio_off_wo_card", "false") == "true"; michael@0: michael@0: // Ril quirk to enable IPv6 protocol/roaming protocol in APN settings. michael@0: let RILQUIRKS_HAVE_IPV6 = michael@0: libcutils.property_get("ro.moz.ril.ipv6", "false") == "true"; michael@0: michael@0: const RADIOINTERFACELAYER_CID = michael@0: Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}"); michael@0: const RADIOINTERFACE_CID = michael@0: Components.ID("{6a7c91f0-a2b3-4193-8562-8969296c0b54}"); michael@0: const RILNETWORKINTERFACE_CID = michael@0: Components.ID("{3bdd52a9-3965-4130-b569-0ac5afed045e}"); michael@0: const GSMICCINFO_CID = michael@0: Components.ID("{d90c4261-a99d-47bc-8b05-b057bb7e8f8a}"); michael@0: const CDMAICCINFO_CID = michael@0: Components.ID("{39ba3c08-aacc-46d0-8c04-9b619c387061}"); michael@0: michael@0: const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown"; michael@0: const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed"; michael@0: const kNetworkConnStateChangedTopic = "network-connection-state-changed"; michael@0: const kNetworkActiveChangedTopic = "network-active-changed"; michael@0: const kSmsReceivedObserverTopic = "sms-received"; michael@0: const kSilentSmsReceivedObserverTopic = "silent-sms-received"; michael@0: const kSmsSendingObserverTopic = "sms-sending"; michael@0: const kSmsSentObserverTopic = "sms-sent"; michael@0: const kSmsFailedObserverTopic = "sms-failed"; michael@0: const kSmsDeliverySuccessObserverTopic = "sms-delivery-success"; michael@0: const kSmsDeliveryErrorObserverTopic = "sms-delivery-error"; michael@0: const kMozSettingsChangedObserverTopic = "mozsettings-changed"; michael@0: const kSysMsgListenerReadyObserverTopic = "system-message-listener-ready"; michael@0: const kSysClockChangeObserverTopic = "system-clock-change"; michael@0: const kScreenStateChangedTopic = "screen-state-changed"; michael@0: michael@0: const kSettingsCellBroadcastSearchList = "ril.cellbroadcast.searchlist"; michael@0: const kSettingsClockAutoUpdateEnabled = "time.clock.automatic-update.enabled"; michael@0: const kSettingsClockAutoUpdateAvailable = "time.clock.automatic-update.available"; michael@0: const kSettingsTimezoneAutoUpdateEnabled = "time.timezone.automatic-update.enabled"; michael@0: const kSettingsTimezoneAutoUpdateAvailable = "time.timezone.automatic-update.available"; michael@0: michael@0: const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; michael@0: michael@0: const kPrefCellBroadcastDisabled = "ril.cellbroadcast.disabled"; michael@0: const kPrefClirModePreference = "ril.clirMode"; michael@0: const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces"; michael@0: michael@0: const DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED = "received"; michael@0: const DOM_MOBILE_MESSAGE_DELIVERY_SENDING = "sending"; michael@0: const DOM_MOBILE_MESSAGE_DELIVERY_SENT = "sent"; michael@0: const DOM_MOBILE_MESSAGE_DELIVERY_ERROR = "error"; michael@0: michael@0: const RADIO_POWER_OFF_TIMEOUT = 30000; michael@0: const SMS_HANDLED_WAKELOCK_TIMEOUT = 5000; michael@0: const HW_DEFAULT_CLIENT_ID = 0; michael@0: michael@0: const RIL_IPC_MOBILECONNECTION_MSG_NAMES = [ michael@0: "RIL:GetRilContext", michael@0: "RIL:GetAvailableNetworks", michael@0: "RIL:SelectNetwork", michael@0: "RIL:SelectNetworkAuto", michael@0: "RIL:SetPreferredNetworkType", michael@0: "RIL:GetPreferredNetworkType", michael@0: "RIL:SendMMI", michael@0: "RIL:CancelMMI", michael@0: "RIL:RegisterMobileConnectionMsg", michael@0: "RIL:SetCallForwardingOptions", michael@0: "RIL:GetCallForwardingOptions", michael@0: "RIL:SetCallBarringOptions", michael@0: "RIL:GetCallBarringOptions", michael@0: "RIL:ChangeCallBarringPassword", michael@0: "RIL:SetCallWaitingOptions", michael@0: "RIL:GetCallWaitingOptions", michael@0: "RIL:SetCallingLineIdRestriction", michael@0: "RIL:GetCallingLineIdRestriction", michael@0: "RIL:SetRoamingPreference", michael@0: "RIL:GetRoamingPreference", michael@0: "RIL:ExitEmergencyCbMode", michael@0: "RIL:SetRadioEnabled", michael@0: "RIL:SetVoicePrivacyMode", michael@0: "RIL:GetVoicePrivacyMode", michael@0: "RIL:GetSupportedNetworkTypes" michael@0: ]; michael@0: michael@0: const RIL_IPC_MOBILENETWORK_MSG_NAMES = [ michael@0: "RIL:GetLastKnownNetwork", michael@0: "RIL:GetLastKnownHomeNetwork" michael@0: ]; michael@0: michael@0: const RIL_IPC_ICCMANAGER_MSG_NAMES = [ michael@0: "RIL:SendStkResponse", michael@0: "RIL:SendStkMenuSelection", michael@0: "RIL:SendStkTimerExpiration", michael@0: "RIL:SendStkEventDownload", michael@0: "RIL:GetCardLockState", michael@0: "RIL:UnlockCardLock", michael@0: "RIL:SetCardLock", michael@0: "RIL:GetCardLockRetryCount", michael@0: "RIL:IccOpenChannel", michael@0: "RIL:IccExchangeAPDU", michael@0: "RIL:IccCloseChannel", michael@0: "RIL:ReadIccContacts", michael@0: "RIL:UpdateIccContact", michael@0: "RIL:RegisterIccMsg", michael@0: "RIL:MatchMvno" michael@0: ]; michael@0: michael@0: const RIL_IPC_VOICEMAIL_MSG_NAMES = [ michael@0: "RIL:RegisterVoicemailMsg", michael@0: "RIL:GetVoicemailInfo" michael@0: ]; michael@0: michael@0: const RIL_IPC_CELLBROADCAST_MSG_NAMES = [ michael@0: "RIL:RegisterCellBroadcastMsg" michael@0: ]; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService", michael@0: "@mozilla.org/power/powermanagerservice;1", michael@0: "nsIPowerManagerService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService", michael@0: "@mozilla.org/mobilemessage/mobilemessageservice;1", michael@0: "nsIMobileMessageService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gSmsService", michael@0: "@mozilla.org/sms/smsservice;1", michael@0: "nsISmsService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageDatabaseService", michael@0: "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1", michael@0: "nsIRilMobileMessageDatabaseService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "ppmm", michael@0: "@mozilla.org/parentprocessmessagemanager;1", michael@0: "nsIMessageBroadcaster"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService", michael@0: "@mozilla.org/settingsService;1", michael@0: "nsISettingsService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger", michael@0: "@mozilla.org/system-message-internal;1", michael@0: "nsISystemMessagesInternal"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager", michael@0: "@mozilla.org/network/manager;1", michael@0: "nsINetworkManager"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gTimeService", michael@0: "@mozilla.org/time/timeservice;1", michael@0: "nsITimeService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager", michael@0: "@mozilla.org/telephony/system-worker-manager;1", michael@0: "nsISystemWorkerManager"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gTelephonyProvider", michael@0: "@mozilla.org/telephony/telephonyprovider;1", michael@0: "nsIGonkTelephonyProvider"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "WAP", function() { michael@0: let wap = {}; michael@0: Cu.import("resource://gre/modules/WapPushManager.js", wap); michael@0: return wap; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "PhoneNumberUtils", function() { michael@0: let ns = {}; michael@0: Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns); michael@0: return ns.PhoneNumberUtils; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gMessageManager", function() { michael@0: return { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener, michael@0: Ci.nsIObserver]), michael@0: michael@0: ril: null, michael@0: michael@0: // Manage message targets in terms of topic. Only the authorized and michael@0: // registered contents can receive related messages. michael@0: targetsByTopic: {}, michael@0: topics: [], michael@0: michael@0: targetMessageQueue: [], michael@0: ready: false, michael@0: michael@0: init: function(ril) { michael@0: this.ril = ril; michael@0: michael@0: Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: Services.obs.addObserver(this, kSysMsgListenerReadyObserverTopic, false); michael@0: this._registerMessageListeners(); michael@0: }, michael@0: michael@0: _shutdown: function() { michael@0: this.ril = null; michael@0: michael@0: Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: this._unregisterMessageListeners(); michael@0: }, michael@0: michael@0: _registerMessageListeners: function() { michael@0: ppmm.addMessageListener("child-process-shutdown", this); michael@0: for (let msgname of RIL_IPC_MOBILECONNECTION_MSG_NAMES) { michael@0: ppmm.addMessageListener(msgname, this); michael@0: } michael@0: for (let msgname of RIL_IPC_MOBILENETWORK_MSG_NAMES) { michael@0: ppmm.addMessageListener(msgname, this); michael@0: } michael@0: for (let msgName of RIL_IPC_ICCMANAGER_MSG_NAMES) { michael@0: ppmm.addMessageListener(msgName, this); michael@0: } michael@0: for (let msgname of RIL_IPC_VOICEMAIL_MSG_NAMES) { michael@0: ppmm.addMessageListener(msgname, this); michael@0: } michael@0: for (let msgname of RIL_IPC_CELLBROADCAST_MSG_NAMES) { michael@0: ppmm.addMessageListener(msgname, this); michael@0: } michael@0: }, michael@0: michael@0: _unregisterMessageListeners: function() { michael@0: ppmm.removeMessageListener("child-process-shutdown", this); michael@0: for (let msgname of RIL_IPC_MOBILECONNECTION_MSG_NAMES) { michael@0: ppmm.removeMessageListener(msgname, this); michael@0: } michael@0: for (let msgname of RIL_IPC_MOBILENETWORK_MSG_NAMES) { michael@0: ppmm.removeMessageListener(msgname, this); michael@0: } michael@0: for (let msgName of RIL_IPC_ICCMANAGER_MSG_NAMES) { michael@0: ppmm.removeMessageListener(msgName, this); michael@0: } michael@0: for (let msgname of RIL_IPC_VOICEMAIL_MSG_NAMES) { michael@0: ppmm.removeMessageListener(msgname, this); michael@0: } michael@0: for (let msgname of RIL_IPC_CELLBROADCAST_MSG_NAMES) { michael@0: ppmm.removeMessageListener(msgname, this); michael@0: } michael@0: ppmm = null; michael@0: }, michael@0: michael@0: _registerMessageTarget: function(topic, target) { michael@0: let targets = this.targetsByTopic[topic]; michael@0: if (!targets) { michael@0: targets = this.targetsByTopic[topic] = []; michael@0: let list = this.topics; michael@0: if (list.indexOf(topic) == -1) { michael@0: list.push(topic); michael@0: } michael@0: } michael@0: michael@0: if (targets.indexOf(target) != -1) { michael@0: if (DEBUG) debug("Already registered this target!"); michael@0: return; michael@0: } michael@0: michael@0: targets.push(target); michael@0: if (DEBUG) debug("Registered " + topic + " target: " + target); michael@0: }, michael@0: michael@0: _unregisterMessageTarget: function(topic, target) { michael@0: if (topic == null) { michael@0: // Unregister the target for every topic when no topic is specified. michael@0: for (let type of this.topics) { michael@0: this._unregisterMessageTarget(type, target); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Unregister the target for a specified topic. michael@0: let targets = this.targetsByTopic[topic]; michael@0: if (!targets) { michael@0: return; michael@0: } michael@0: michael@0: let index = targets.indexOf(target); michael@0: if (index != -1) { michael@0: targets.splice(index, 1); michael@0: if (DEBUG) debug("Unregistered " + topic + " target: " + target); michael@0: } michael@0: }, michael@0: michael@0: _enqueueTargetMessage: function(topic, message, options) { michael@0: let msg = { topic : topic, michael@0: message : message, michael@0: options : options }; michael@0: // Remove previous queued message with the same message type and client Id michael@0: // , only one message per (message type + client Id) is allowed in queue. michael@0: let messageQueue = this.targetMessageQueue; michael@0: for(let i = 0; i < messageQueue.length; i++) { michael@0: if (messageQueue[i].message === message && michael@0: messageQueue[i].options.clientId === options.clientId) { michael@0: messageQueue.splice(i, 1); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: messageQueue.push(msg); michael@0: }, michael@0: michael@0: _sendTargetMessage: function(topic, message, options) { michael@0: if (!this.ready) { michael@0: this._enqueueTargetMessage(topic, message, options); michael@0: return; michael@0: } michael@0: michael@0: let targets = this.targetsByTopic[topic]; michael@0: if (!targets) { michael@0: return; michael@0: } michael@0: michael@0: for (let target of targets) { michael@0: target.sendAsyncMessage(message, options); michael@0: } michael@0: }, michael@0: michael@0: _resendQueuedTargetMessage: function() { michael@0: this.ready = true; michael@0: michael@0: // Here uses this._sendTargetMessage() to resend message, which will michael@0: // enqueue message if listener is not ready. michael@0: // So only resend after listener is ready, or it will cause infinate loop and michael@0: // hang the system. michael@0: michael@0: // Dequeue and resend messages. michael@0: for each (let msg in this.targetMessageQueue) { michael@0: this._sendTargetMessage(msg.topic, msg.message, msg.options); michael@0: } michael@0: this.targetMessageQueue = null; michael@0: }, michael@0: michael@0: /** michael@0: * nsIMessageListener interface methods. michael@0: */ michael@0: michael@0: receiveMessage: function(msg) { michael@0: if (DEBUG) debug("Received '" + msg.name + "' message from content process"); michael@0: if (msg.name == "child-process-shutdown") { michael@0: // By the time we receive child-process-shutdown, the child process has michael@0: // already forgotten its permissions so we need to unregister the target michael@0: // for every permission. michael@0: this._unregisterMessageTarget(null, msg.target); michael@0: return null; michael@0: } michael@0: michael@0: if (RIL_IPC_MOBILECONNECTION_MSG_NAMES.indexOf(msg.name) != -1) { michael@0: if (!msg.target.assertPermission("mobileconnection")) { michael@0: if (DEBUG) { michael@0: debug("MobileConnection message " + msg.name + michael@0: " from a content process with no 'mobileconnection' privileges."); michael@0: } michael@0: return null; michael@0: } michael@0: } else if (RIL_IPC_MOBILENETWORK_MSG_NAMES.indexOf(msg.name) != -1) { michael@0: if (!msg.target.assertPermission("mobilenetwork")) { michael@0: if (DEBUG) { michael@0: debug("MobileNetwork message " + msg.name + michael@0: " from a content process with no 'mobilenetwork' privileges."); michael@0: } michael@0: return null; michael@0: } michael@0: } else if (RIL_IPC_ICCMANAGER_MSG_NAMES.indexOf(msg.name) != -1) { michael@0: if (!msg.target.assertPermission("mobileconnection")) { michael@0: if (DEBUG) { michael@0: debug("IccManager message " + msg.name + michael@0: " from a content process with no 'mobileconnection' privileges."); michael@0: } michael@0: return null; michael@0: } michael@0: } else if (RIL_IPC_VOICEMAIL_MSG_NAMES.indexOf(msg.name) != -1) { michael@0: if (!msg.target.assertPermission("voicemail")) { michael@0: if (DEBUG) { michael@0: debug("Voicemail message " + msg.name + michael@0: " from a content process with no 'voicemail' privileges."); michael@0: } michael@0: return null; michael@0: } michael@0: } else if (RIL_IPC_CELLBROADCAST_MSG_NAMES.indexOf(msg.name) != -1) { michael@0: if (!msg.target.assertPermission("cellbroadcast")) { michael@0: if (DEBUG) { michael@0: debug("Cell Broadcast message " + msg.name + michael@0: " from a content process with no 'cellbroadcast' privileges."); michael@0: } michael@0: return null; michael@0: } michael@0: } else { michael@0: if (DEBUG) debug("Ignoring unknown message type: " + msg.name); michael@0: return null; michael@0: } michael@0: michael@0: switch (msg.name) { michael@0: case "RIL:RegisterMobileConnectionMsg": michael@0: this._registerMessageTarget("mobileconnection", msg.target); michael@0: return null; michael@0: case "RIL:RegisterIccMsg": michael@0: this._registerMessageTarget("icc", msg.target); michael@0: return null; michael@0: case "RIL:RegisterVoicemailMsg": michael@0: this._registerMessageTarget("voicemail", msg.target); michael@0: return null; michael@0: case "RIL:RegisterCellBroadcastMsg": michael@0: this._registerMessageTarget("cellbroadcast", msg.target); michael@0: return null; michael@0: } michael@0: michael@0: let clientId = msg.json.clientId || 0; michael@0: let radioInterface = this.ril.getRadioInterface(clientId); michael@0: if (!radioInterface) { michael@0: if (DEBUG) debug("No such radio interface: " + clientId); michael@0: return null; michael@0: } michael@0: michael@0: if (msg.name === "RIL:SetRadioEnabled") { michael@0: // Special handler for SetRadioEnabled. michael@0: return gRadioEnabledController.receiveMessage(msg); michael@0: } michael@0: michael@0: return radioInterface.receiveMessage(msg); michael@0: }, michael@0: michael@0: /** michael@0: * nsIObserver interface methods. michael@0: */ michael@0: michael@0: observe: function(subject, topic, data) { michael@0: switch (topic) { michael@0: case kSysMsgListenerReadyObserverTopic: michael@0: Services.obs.removeObserver(this, kSysMsgListenerReadyObserverTopic); michael@0: this._resendQueuedTargetMessage(); michael@0: break; michael@0: case NS_XPCOM_SHUTDOWN_OBSERVER_ID: michael@0: this._shutdown(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: sendMobileConnectionMessage: function(message, clientId, data) { michael@0: this._sendTargetMessage("mobileconnection", message, { michael@0: clientId: clientId, michael@0: data: data michael@0: }); michael@0: }, michael@0: michael@0: sendVoicemailMessage: function(message, clientId, data) { michael@0: this._sendTargetMessage("voicemail", message, { michael@0: clientId: clientId, michael@0: data: data michael@0: }); michael@0: }, michael@0: michael@0: sendCellBroadcastMessage: function(message, clientId, data) { michael@0: this._sendTargetMessage("cellbroadcast", message, { michael@0: clientId: clientId, michael@0: data: data michael@0: }); michael@0: }, michael@0: michael@0: sendIccMessage: function(message, clientId, data) { michael@0: this._sendTargetMessage("icc", message, { michael@0: clientId: clientId, michael@0: data: data michael@0: }); michael@0: } michael@0: }; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function() { michael@0: let _ril = null; michael@0: let _pendingMessages = []; // For queueing "RIL =SetRadioEnabled" messages. michael@0: let _isProcessingPending = false; michael@0: let _timer = null; michael@0: let _request = null; michael@0: let _deactivatingDeferred = {}; michael@0: let _initializedCardState = {}; michael@0: let _allCardStateInitialized = !RILQUIRKS_RADIO_OFF_WO_CARD; michael@0: michael@0: return { michael@0: init: function(ril) { michael@0: _ril = ril; michael@0: }, michael@0: michael@0: receiveCardState: function(clientId) { michael@0: if (_allCardStateInitialized) { michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) debug("RadioControl: receive cardState from " + clientId); michael@0: _initializedCardState[clientId] = true; michael@0: if (Object.keys(_initializedCardState).length == _ril.numRadioInterfaces) { michael@0: _allCardStateInitialized = true; michael@0: this._startProcessingPending(); michael@0: } michael@0: }, michael@0: michael@0: receiveMessage: function(msg) { michael@0: if (DEBUG) debug("RadioControl: receiveMessage: " + JSON.stringify(msg)); michael@0: _pendingMessages.push(msg); michael@0: this._startProcessingPending(); michael@0: }, michael@0: michael@0: isDeactivatingDataCalls: function() { michael@0: return _request !== null; michael@0: }, michael@0: michael@0: finishDeactivatingDataCalls: function(clientId) { michael@0: if (DEBUG) debug("RadioControl: finishDeactivatingDataCalls: " + clientId); michael@0: let deferred = _deactivatingDeferred[clientId]; michael@0: if (deferred) { michael@0: deferred.resolve(); michael@0: } michael@0: }, michael@0: michael@0: _startProcessingPending: function() { michael@0: if (!_isProcessingPending) { michael@0: if (DEBUG) debug("RadioControl: start dequeue"); michael@0: _isProcessingPending = true; michael@0: this._processNextMessage(); michael@0: } michael@0: }, michael@0: michael@0: _processNextMessage: function() { michael@0: if (_pendingMessages.length === 0 || !_allCardStateInitialized) { michael@0: if (DEBUG) debug("RadioControl: stop dequeue"); michael@0: _isProcessingPending = false; michael@0: return; michael@0: } michael@0: michael@0: let msg = _pendingMessages.shift(); michael@0: this._handleMessage(msg); michael@0: }, michael@0: michael@0: _getNumCards: function() { michael@0: let numCards = 0; michael@0: for (let i = 0, N = _ril.numRadioInterfaces; i < N; ++i) { michael@0: if (this._isCardPresentAtClient(i)) { michael@0: numCards++; michael@0: } michael@0: } michael@0: return numCards; michael@0: }, michael@0: michael@0: _isCardPresentAtClient: function(clientId) { michael@0: let cardState = _ril.getRadioInterface(clientId).rilContext.cardState; michael@0: return cardState !== RIL.GECKO_CARDSTATE_UNDETECTED && michael@0: cardState !== RIL.GECKO_CARDSTATE_UNKNOWN; michael@0: }, michael@0: michael@0: _isRadioAbleToEnableAtClient: function(clientId, numCards) { michael@0: if (!RILQUIRKS_RADIO_OFF_WO_CARD) { michael@0: return true; michael@0: } michael@0: michael@0: // We could only turn on the radio for clientId if michael@0: // 1. a SIM card is presented or michael@0: // 2. it is the default clientId and there is no any SIM card at any client. michael@0: michael@0: if (this._isCardPresentAtClient(clientId)) { michael@0: return true; michael@0: } michael@0: michael@0: numCards = numCards == null ? this._getNumCards() : numCards; michael@0: if (clientId === HW_DEFAULT_CLIENT_ID && numCards === 0) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: _handleMessage: function(msg) { michael@0: if (DEBUG) debug("RadioControl: handleMessage: " + JSON.stringify(msg)); michael@0: let clientId = msg.json.clientId || 0; michael@0: let radioInterface = _ril.getRadioInterface(clientId); michael@0: michael@0: if (!radioInterface.isValidStateForSetRadioEnabled()) { michael@0: radioInterface.setRadioEnabledResponse(msg.target, msg.json.data, michael@0: "InvalidStateError"); michael@0: this._processNextMessage(); michael@0: return; michael@0: } michael@0: michael@0: if (radioInterface.isDummyForSetRadioEnabled(msg.json.data)) { michael@0: radioInterface.setRadioEnabledResponse(msg.target, msg.json.data); michael@0: this._processNextMessage(); michael@0: return; michael@0: } michael@0: michael@0: if (msg.json.data.enabled) { michael@0: if (this._isRadioAbleToEnableAtClient(clientId)) { michael@0: radioInterface.receiveMessage(msg); michael@0: } else { michael@0: // Not really do it but respond success. michael@0: radioInterface.setRadioEnabledResponse(msg.target, msg.json.data); michael@0: } michael@0: michael@0: this._processNextMessage(); michael@0: } else { michael@0: _request = function() { michael@0: radioInterface.receiveMessage(msg); michael@0: }; michael@0: michael@0: // In 2G network, modem takes 35+ seconds to process deactivate data michael@0: // call request if device has active voice call (please see bug 964974 michael@0: // for more details). Therefore we should hangup all active voice calls michael@0: // first. And considering some DSDS architecture, toggling one radio may michael@0: // toggle both, so we send hangUpAll to all clients. michael@0: for (let i = 0, N = _ril.numRadioInterfaces; i < N; ++i) { michael@0: let iface = _ril.getRadioInterface(i); michael@0: iface.workerMessenger.send("hangUpAll"); michael@0: } michael@0: michael@0: // In some DSDS architecture with only one modem, toggling one radio may michael@0: // toggle both. Therefore, for safely turning off, we should first michael@0: // explicitly deactivate all data calls from all clients. michael@0: this._deactivateDataCalls().then(() => { michael@0: if (DEBUG) debug("RadioControl: deactivation done"); michael@0: this._executeRequest(); michael@0: }); michael@0: michael@0: this._createTimer(); michael@0: } michael@0: }, michael@0: michael@0: _deactivateDataCalls: function() { michael@0: if (DEBUG) debug("RadioControl: deactivating data calls..."); michael@0: _deactivatingDeferred = {}; michael@0: michael@0: let promise = Promise.resolve(); michael@0: for (let i = 0, N = _ril.numRadioInterfaces; i < N; ++i) { michael@0: promise = promise.then(this._deactivateDataCallsForClient(i)); michael@0: } michael@0: michael@0: return promise; michael@0: }, michael@0: michael@0: _deactivateDataCallsForClient: function(clientId) { michael@0: return function() { michael@0: let deferred = _deactivatingDeferred[clientId] = Promise.defer(); michael@0: let dataConnectionHandler = gDataConnectionManager.getConnectionHandler(clientId); michael@0: dataConnectionHandler.deactivateDataCalls(); michael@0: return deferred.promise; michael@0: }; michael@0: }, michael@0: michael@0: _createTimer: function() { michael@0: if (!_timer) { michael@0: _timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: } michael@0: _timer.initWithCallback(this._executeRequest.bind(this), michael@0: RADIO_POWER_OFF_TIMEOUT, michael@0: Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }, michael@0: michael@0: _cancelTimer: function() { michael@0: if (_timer) { michael@0: _timer.cancel(); michael@0: } michael@0: }, michael@0: michael@0: _executeRequest: function() { michael@0: if (typeof _request === "function") { michael@0: if (DEBUG) debug("RadioControl: executeRequest"); michael@0: this._cancelTimer(); michael@0: _request(); michael@0: _request = null; michael@0: } michael@0: this._processNextMessage(); michael@0: }, michael@0: }; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gDataConnectionManager", function () { michael@0: return { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, michael@0: Ci.nsISettingsServiceCallback]), michael@0: michael@0: _connectionHandlers: null, michael@0: michael@0: // Flag to determine the data state to start with when we boot up. It michael@0: // corresponds to the 'ril.data.enabled' setting from the UI. michael@0: _dataEnabled: false, michael@0: michael@0: // Flag to record the default client id for data call. It corresponds to michael@0: // the 'ril.data.defaultServiceId' setting from the UI. michael@0: _dataDefaultClientId: -1, michael@0: michael@0: // Flag to record the current default client id for data call. michael@0: // It differs from _dataDefaultClientId in that it is set only when michael@0: // the switch of client id process is done. michael@0: _currentDataClientId: -1, michael@0: michael@0: // Pending function to execute when we are notified that another data call has michael@0: // been disconnected. michael@0: _pendingDataCallRequest: null, michael@0: michael@0: debug: function(s) { michael@0: dump("-*- DataConnectionManager: " + s + "\n"); michael@0: }, michael@0: michael@0: init: function(ril) { michael@0: if (!ril) { michael@0: return; michael@0: } michael@0: michael@0: this._connectionHandlers = []; michael@0: for (let clientId = 0; clientId < ril.numRadioInterfaces; clientId++) { michael@0: let radioInterface = ril.getRadioInterface(clientId); michael@0: this._connectionHandlers.push( michael@0: new DataConnectionHandler(clientId, radioInterface)); michael@0: } michael@0: michael@0: let lock = gSettingsService.createLock(); michael@0: // Read the APN data from the settings DB. michael@0: lock.get("ril.data.apnSettings", this); michael@0: // Read the data enabled setting from DB. michael@0: lock.get("ril.data.enabled", this); michael@0: lock.get("ril.data.roaming_enabled", this); michael@0: // Read the default client id for data call. michael@0: lock.get("ril.data.defaultServiceId", this); michael@0: michael@0: Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false); michael@0: Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic, false); michael@0: }, michael@0: michael@0: getConnectionHandler: function(clientId) { michael@0: return this._connectionHandlers[clientId]; michael@0: }, michael@0: michael@0: _handleDataClientIdChange: function(newDefault) { michael@0: if (this._dataDefaultClientId === newDefault) { michael@0: return; michael@0: } michael@0: this._dataDefaultClientId = newDefault; michael@0: michael@0: if (this._currentDataClientId == -1) { michael@0: // This is to handle boot up stage. michael@0: this._currentDataClientId = this._dataDefaultClientId; michael@0: let connHandler = this._connectionHandlers[this._currentDataClientId]; michael@0: let radioInterface = connHandler.radioInterface; michael@0: if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { michael@0: radioInterface.setDataRegistration(true); michael@0: } michael@0: if (this._dataEnabled) { michael@0: let settings = connHandler.dataCallSettings; michael@0: settings.oldEnabled = settings.enabled; michael@0: settings.enabled = true; michael@0: connHandler.updateRILNetworkInterface(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let oldConnHandler = this._connectionHandlers[this._currentDataClientId]; michael@0: let oldIface = oldConnHandler.radioInterface; michael@0: let oldSettings = oldConnHandler.dataCallSettings; michael@0: let newConnHandler = this._connectionHandlers[this._dataDefaultClientId]; michael@0: let newIface = newConnHandler.radioInterface; michael@0: let newSettings = newConnHandler.dataCallSettings; michael@0: michael@0: if (!this._dataEnabled) { michael@0: if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { michael@0: oldIface.setDataRegistration(false); michael@0: newIface.setDataRegistration(true); michael@0: } michael@0: this._currentDataClientId = this._dataDefaultClientId; michael@0: return; michael@0: } michael@0: michael@0: oldSettings.oldEnabled = oldSettings.enabled; michael@0: oldSettings.enabled = false; michael@0: michael@0: if (oldConnHandler.anyDataConnected()) { michael@0: this._pendingDataCallRequest = function () { michael@0: if (DEBUG) { michael@0: this.debug("Executing pending data call request."); michael@0: } michael@0: if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { michael@0: newIface.setDataRegistration(true); michael@0: } michael@0: newSettings.oldEnabled = newSettings.enabled; michael@0: newSettings.enabled = this._dataEnabled; michael@0: michael@0: this._currentDataClientId = this._dataDefaultClientId; michael@0: newConnHandler.updateRILNetworkInterface(); michael@0: }; michael@0: michael@0: if (DEBUG) { michael@0: this.debug("_handleDataClientIdChange: existing data call(s) active" + michael@0: ", wait for them to get disconnected."); michael@0: } michael@0: oldConnHandler.deactivateDataCalls(); michael@0: return; michael@0: } michael@0: michael@0: newSettings.oldEnabled = newSettings.enabled; michael@0: newSettings.enabled = true; michael@0: michael@0: this._currentDataClientId = this._dataDefaultClientId; michael@0: if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { michael@0: oldIface.setDataRegistration(false); michael@0: newIface.setDataRegistration(true); michael@0: } michael@0: newConnHandler.updateRILNetworkInterface(); michael@0: }, michael@0: michael@0: _shutdown: function() { michael@0: for (let handler of this._connectionHandlers) { michael@0: handler.shutdown(); michael@0: } michael@0: this._connectionHandlers = null; michael@0: Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic); michael@0: Services.obs.removeObserver(this, kNetworkInterfaceStateChangedTopic); michael@0: }, michael@0: michael@0: /** michael@0: * nsISettingsServiceCallback michael@0: */ michael@0: handle: function(name, result) { michael@0: switch(name) { michael@0: case "ril.data.apnSettings": michael@0: if (DEBUG) { michael@0: this.debug("'ril.data.apnSettings' is now " + michael@0: JSON.stringify(result)); michael@0: } michael@0: if (!result) { michael@0: break; michael@0: } michael@0: for (let clientId in this._connectionHandlers) { michael@0: let handler = this._connectionHandlers[clientId]; michael@0: let apnSetting = result[clientId]; michael@0: if (handler && apnSetting) { michael@0: handler.updateApnSettings(apnSetting); michael@0: handler.updateRILNetworkInterface(); michael@0: } michael@0: } michael@0: break; michael@0: case "ril.data.enabled": michael@0: if (DEBUG) { michael@0: this.debug("'ril.data.enabled' is now " + result); michael@0: } michael@0: if (this._dataEnabled === result) { michael@0: break; michael@0: } michael@0: this._dataEnabled = result; michael@0: michael@0: if (DEBUG) { michael@0: this.debug("Default id for data call: " + this._dataDefaultClientId); michael@0: } michael@0: if (this._dataDefaultClientId === -1) { michael@0: // We haven't got the default id for data from db. michael@0: break; michael@0: } michael@0: michael@0: let connHandler = this._connectionHandlers[this._dataDefaultClientId]; michael@0: let settings = connHandler.dataCallSettings; michael@0: settings.oldEnabled = settings.enabled; michael@0: settings.enabled = result; michael@0: connHandler.updateRILNetworkInterface(); michael@0: break; michael@0: case "ril.data.roaming_enabled": michael@0: if (DEBUG) { michael@0: this.debug("'ril.data.roaming_enabled' is now " + result); michael@0: this.debug("Default id for data call: " + this._dataDefaultClientId); michael@0: } michael@0: for (let clientId = 0; clientId < this._connectionHandlers.length; clientId++) { michael@0: let connHandler = this._connectionHandlers[clientId]; michael@0: let settings = connHandler.dataCallSettings; michael@0: settings.roamingEnabled = Array.isArray(result) ? result[clientId] : result; michael@0: } michael@0: if (this._dataDefaultClientId === -1) { michael@0: // We haven't got the default id for data from db. michael@0: break; michael@0: } michael@0: this._connectionHandlers[this._dataDefaultClientId].updateRILNetworkInterface(); michael@0: break; michael@0: case "ril.data.defaultServiceId": michael@0: result = result || 0; michael@0: if (DEBUG) { michael@0: this.debug("'ril.data.defaultServiceId' is now " + result); michael@0: } michael@0: this._handleDataClientIdChange(result); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: handleError: function(errorMessage) { michael@0: if (DEBUG) { michael@0: this.debug("There was an error while reading RIL settings."); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * nsIObserver interface methods. michael@0: */ michael@0: observe: function(subject, topic, data) { michael@0: switch (topic) { michael@0: case kMozSettingsChangedObserverTopic: michael@0: let setting = JSON.parse(data); michael@0: this.handle(setting.key, setting.value); michael@0: break; michael@0: case kNetworkInterfaceStateChangedTopic: michael@0: let network = subject.QueryInterface(Ci.nsINetworkInterface); michael@0: // DSDS: setup pending data connection when switching the default id michael@0: // for data call. We can not use network.type to tell if it's michael@0: // NETWORK_TYPE_MOBILE, since the type is removed from michael@0: // RILNetworkInterface.connectedTypes on disconnect(). michael@0: if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN) { michael@0: let connHandler = this._connectionHandlers[this._currentDataClientId]; michael@0: let radioInterface = connHandler.radioInterface; michael@0: if (connHandler.allDataDisconnected() && michael@0: typeof this._pendingDataCallRequest === "function") { michael@0: if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) { michael@0: radioInterface.setDataRegistration(false); michael@0: } michael@0: if (DEBUG) { michael@0: this.debug("All data calls disconnected, setup pending data call."); michael@0: } michael@0: this._pendingDataCallRequest(); michael@0: this._pendingDataCallRequest = null; michael@0: } michael@0: } michael@0: break; michael@0: case NS_XPCOM_SHUTDOWN_OBSERVER_ID: michael@0: this._shutdown(); michael@0: break; michael@0: } michael@0: }, michael@0: }; michael@0: }); michael@0: michael@0: // Initialize shared preference "ril.numRadioInterfaces" according to system michael@0: // property. michael@0: try { michael@0: Services.prefs.setIntPref(kPrefRilNumRadioInterfaces, (function() { michael@0: // When Gonk property "ro.moz.ril.numclients" is not set, return 1; if michael@0: // explicitly set to any number larger-equal than 0, return num; else, return michael@0: // 1 for compatibility. michael@0: try { michael@0: let numString = libcutils.property_get("ro.moz.ril.numclients", "1"); michael@0: let num = parseInt(numString, 10); michael@0: if (num >= 0) { michael@0: return num; michael@0: } michael@0: } catch (e) {} michael@0: michael@0: return 1; michael@0: })()); michael@0: } catch (e) {} michael@0: michael@0: function IccInfo() {} michael@0: IccInfo.prototype = { michael@0: iccType: null, michael@0: iccid: null, michael@0: mcc: null, michael@0: mnc: null, michael@0: spn: null, michael@0: isDisplayNetworkNameRequired: null, michael@0: isDisplaySpnRequired: null michael@0: }; michael@0: michael@0: function GsmIccInfo() {} michael@0: GsmIccInfo.prototype = { michael@0: __proto__: IccInfo.prototype, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozGsmIccInfo]), michael@0: classID: GSMICCINFO_CID, michael@0: classInfo: XPCOMUtils.generateCI({ michael@0: classID: GSMICCINFO_CID, michael@0: classDescription: "MozGsmIccInfo", michael@0: flags: Ci.nsIClassInfo.DOM_OBJECT, michael@0: interfaces: [Ci.nsIDOMMozGsmIccInfo] michael@0: }), michael@0: michael@0: // nsIDOMMozGsmIccInfo michael@0: michael@0: msisdn: null michael@0: }; michael@0: michael@0: function CdmaIccInfo() {} michael@0: CdmaIccInfo.prototype = { michael@0: __proto__: IccInfo.prototype, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozCdmaIccInfo]), michael@0: classID: CDMAICCINFO_CID, michael@0: classInfo: XPCOMUtils.generateCI({ michael@0: classID: CDMAICCINFO_CID, michael@0: classDescription: "MozCdmaIccInfo", michael@0: flags: Ci.nsIClassInfo.DOM_OBJECT, michael@0: interfaces: [Ci.nsIDOMMozCdmaIccInfo] michael@0: }), michael@0: michael@0: // nsIDOMMozCdmaIccInfo michael@0: michael@0: mdn: null, michael@0: prlVersion: 0 michael@0: }; michael@0: michael@0: function DataConnectionHandler(clientId, radioInterface) { michael@0: // Initial owning attributes. michael@0: this.clientId = clientId; michael@0: this.radioInterface = radioInterface; michael@0: this.dataCallSettings = { michael@0: oldEnabled: false, michael@0: enabled: false, michael@0: roamingEnabled: false michael@0: }; michael@0: this._dataCallbacks = []; michael@0: michael@0: // This matrix is used to keep all the APN settings. michael@0: // - |byApn| object makes it easier to get the corresponding APN setting michael@0: // via a given set of APN, user name and password. michael@0: // - |byType| object makes it easier to get the corresponding APN setting michael@0: // via a given APN type. michael@0: this.apnSettings = { michael@0: byType: {}, michael@0: byApn: {} michael@0: }; michael@0: } michael@0: DataConnectionHandler.prototype = { michael@0: clientId: 0, michael@0: radioInterface: null, michael@0: // Data calls setting. michael@0: dataCallSettings: null, michael@0: apnSettings: null, michael@0: michael@0: // Apn settings to be setup after data call are cleared. michael@0: _pendingApnSettings: null, michael@0: michael@0: debug: function(s) { michael@0: dump("-*- DataConnectionHandler[" + this.clientId + "]: " + s + "\n"); michael@0: }, michael@0: michael@0: shutdown: function() { michael@0: // Shutdown all RIL network interfaces michael@0: for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { michael@0: if (apnSetting.iface) { michael@0: apnSetting.iface.shutdown(); michael@0: } michael@0: } michael@0: this.clientId = null; michael@0: this.radioInterface = null; michael@0: }, michael@0: michael@0: /** michael@0: * Check if we get all necessary APN data. michael@0: */ michael@0: _validateApnSetting: function(apnSetting) { michael@0: return (apnSetting && michael@0: apnSetting.apn && michael@0: apnSetting.types && michael@0: apnSetting.types.length); michael@0: }, michael@0: michael@0: _deliverDataCallCallback: function(name, args) { michael@0: // We need to worry about callback registration state mutations during the michael@0: // callback firing. The behaviour we want is to *not* call any callbacks michael@0: // that are added during the firing and to *not* call any callbacks that are michael@0: // removed during the firing. To address this, we make a copy of the michael@0: // callback list before dispatching and then double-check that each callback michael@0: // is still registered before calling it. michael@0: let callbacks = this._dataCallbacks.slice(); michael@0: for (let callback of callbacks) { michael@0: if (this._dataCallbacks.indexOf(callback) == -1) { michael@0: continue; michael@0: } michael@0: try { michael@0: let handler = callback[name]; michael@0: if (typeof handler !== "function") { michael@0: throw new Error("No handler for " + name); michael@0: } michael@0: handler.apply(callback, args); michael@0: } catch (e) { michael@0: if (DEBUG) { michael@0: this.debug("callback handler for " + name + " threw an exception: " + e); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * This function will do the following steps: michael@0: * 1. Clear the cached APN settings in the RIL. michael@0: * 2. Combine APN, user name, and password as the key of |byApn| object to michael@0: * refer to the corresponding APN setting. michael@0: * 3. Use APN type as the index of |byType| object to refer to the michael@0: * corresponding APN setting. michael@0: * 4. Create RilNetworkInterface for each APN setting created at step 2. michael@0: */ michael@0: _setupApnSettings: function(newApnSettings) { michael@0: if (!newApnSettings) { michael@0: return; michael@0: } michael@0: if (DEBUG) this.debug("setupApnSettings: " + JSON.stringify(newApnSettings)); michael@0: michael@0: // Unregister anything from iface and delete it. michael@0: for (let [, apnSetting] in Iterator(this.apnSettings.byApn)) { michael@0: if (apnSetting.iface.name in gNetworkManager.networkInterfaces) { michael@0: gNetworkManager.unregisterNetworkInterface(apnSetting.iface); michael@0: } michael@0: this.unregisterDataCallCallback(apnSetting.iface); michael@0: delete apnSetting.iface; michael@0: } michael@0: this.apnSettings.byApn = {}; michael@0: this.apnSettings.byType = {}; michael@0: michael@0: // Cache the APN settings by APNs and by types in the RIL. michael@0: for (let inputApnSetting of newApnSettings) { michael@0: if (!this._validateApnSetting(inputApnSetting)) { michael@0: continue; michael@0: } michael@0: michael@0: // Combine APN, user name, and password as the key of |byApn| object to michael@0: // refer to the corresponding APN setting. michael@0: let apnKey = inputApnSetting.apn + michael@0: (inputApnSetting.user || "") + michael@0: (inputApnSetting.password || ""); michael@0: michael@0: if (!this.apnSettings.byApn[apnKey]) { michael@0: this.apnSettings.byApn[apnKey] = inputApnSetting; michael@0: } else { michael@0: this.apnSettings.byApn[apnKey].types = michael@0: this.apnSettings.byApn[apnKey].types.concat(inputApnSetting.types); michael@0: } michael@0: michael@0: // Use APN type as the index of |byType| object to refer to the michael@0: // corresponding APN setting. michael@0: for (let type of inputApnSetting.types) { michael@0: this.apnSettings.byType[type] = this.apnSettings.byApn[apnKey]; michael@0: } michael@0: } michael@0: michael@0: // Create RilNetworkInterface for each APN setting that just cached. michael@0: for (let [, apnSetting] in Iterator(this.apnSettings.byApn)) { michael@0: apnSetting.iface = new RILNetworkInterface(this, apnSetting); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Check if all data is disconnected. michael@0: */ michael@0: allDataDisconnected: function() { michael@0: for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { michael@0: let iface = apnSetting.iface; michael@0: if (iface && iface.state != RIL.GECKO_NETWORK_STATE_UNKNOWN && michael@0: iface.state != RIL.GECKO_NETWORK_STATE_DISCONNECTED) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Check if there is any activated data connection. michael@0: */ michael@0: anyDataConnected: function() { michael@0: for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { michael@0: let iface = apnSetting.iface; michael@0: if (iface && iface.state == RIL.GECKO_NETWORK_STATE_CONNECTED) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: updateApnSettings: function(newApnSettings) { michael@0: if (!newApnSettings) { michael@0: return; michael@0: } michael@0: if (this._pendingApnSettings) { michael@0: // Change of apn settings in process, just update to the newest. michael@0: this._pengingApnSettings = newApnSettings; michael@0: return; michael@0: } michael@0: michael@0: let isDeactivatingDataCalls = false; michael@0: // Clear the cached APN settings in the RIL. michael@0: for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { michael@0: // Clear all existing connections based on APN types. michael@0: for (let type of apnSetting.types) { michael@0: if (this.getDataCallStateByType(type) == michael@0: RIL.GECKO_NETWORK_STATE_CONNECTED) { michael@0: this.deactivateDataCallByType(type); michael@0: isDeactivatingDataCalls = true; michael@0: } michael@0: } michael@0: } michael@0: if (isDeactivatingDataCalls) { michael@0: // Defer apn settings setup until all data calls are cleared. michael@0: this._pendingApnSettings = newApnSettings; michael@0: return; michael@0: } michael@0: this._setupApnSettings(newApnSettings); michael@0: }, michael@0: michael@0: updateRILNetworkInterface: function() { michael@0: let apnSetting = this.apnSettings.byType.default; michael@0: if (!this._validateApnSetting(apnSetting)) { michael@0: if (DEBUG) { michael@0: this.debug("We haven't gotten completely the APN data."); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // This check avoids data call connection if the radio is not ready michael@0: // yet after toggling off airplane mode. michael@0: let rilContext = this.radioInterface.rilContext; michael@0: if (rilContext.radioState != RIL.GECKO_RADIOSTATE_READY) { michael@0: if (DEBUG) { michael@0: this.debug("RIL is not ready for data connection: radio's not ready"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // We only watch at "ril.data.enabled" flag changes for connecting or michael@0: // disconnecting the data call. If the value of "ril.data.enabled" is michael@0: // true and any of the remaining flags change the setting application michael@0: // should turn this flag to false and then to true in order to reload michael@0: // the new values and reconnect the data call. michael@0: if (this.dataCallSettings.oldEnabled === this.dataCallSettings.enabled) { michael@0: if (DEBUG) { michael@0: this.debug("No changes for ril.data.enabled flag. Nothing to do."); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let defaultDataCallState = this.getDataCallStateByType("default"); michael@0: if (defaultDataCallState == RIL.GECKO_NETWORK_STATE_CONNECTING || michael@0: defaultDataCallState == RIL.GECKO_NETWORK_STATE_DISCONNECTING) { michael@0: if (DEBUG) { michael@0: this.debug("Nothing to do during connecting/disconnecting in progress."); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let dataInfo = rilContext.data; michael@0: let isRegistered = michael@0: dataInfo.state == RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED; michael@0: let haveDataConnection = michael@0: dataInfo.type != RIL.GECKO_MOBILE_CONNECTION_STATE_UNKNOWN; michael@0: if (!isRegistered || !haveDataConnection) { michael@0: if (DEBUG) { michael@0: this.debug("RIL is not ready for data connection: Phone's not " + michael@0: "registered or doesn't have data connection."); michael@0: } michael@0: return; michael@0: } michael@0: let wifi_active = false; michael@0: if (gNetworkManager.active && michael@0: gNetworkManager.active.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) { michael@0: wifi_active = true; michael@0: } michael@0: michael@0: let defaultDataCallConnected = defaultDataCallState == michael@0: RIL.GECKO_NETWORK_STATE_CONNECTED; michael@0: if (defaultDataCallConnected && michael@0: (!this.dataCallSettings.enabled || michael@0: (dataInfo.roaming && !this.dataCallSettings.roamingEnabled))) { michael@0: if (DEBUG) { michael@0: this.debug("Data call settings: disconnect data call."); michael@0: } michael@0: this.deactivateDataCallByType("default"); michael@0: return; michael@0: } michael@0: michael@0: if (defaultDataCallConnected && wifi_active) { michael@0: if (DEBUG) { michael@0: this.debug("Disconnect data call when Wifi is connected."); michael@0: } michael@0: this.deactivateDataCallByType("default"); michael@0: return; michael@0: } michael@0: michael@0: if (!this.dataCallSettings.enabled || defaultDataCallConnected) { michael@0: if (DEBUG) { michael@0: this.debug("Data call settings: nothing to do."); michael@0: } michael@0: return; michael@0: } michael@0: if (dataInfo.roaming && !this.dataCallSettings.roamingEnabled) { michael@0: if (DEBUG) { michael@0: this.debug("We're roaming, but data roaming is disabled."); michael@0: } michael@0: return; michael@0: } michael@0: if (wifi_active) { michael@0: if (DEBUG) { michael@0: this.debug("Don't connect data call when Wifi is connected."); michael@0: } michael@0: return; michael@0: } michael@0: if (this._pendingApnSettings) { michael@0: if (DEBUG) this.debug("We're changing apn settings, ignore any changes."); michael@0: return; michael@0: } michael@0: michael@0: let detailedRadioState = rilContext.detailedRadioState; michael@0: if (gRadioEnabledController.isDeactivatingDataCalls() || michael@0: detailedRadioState == RIL.GECKO_DETAILED_RADIOSTATE_ENABLING || michael@0: detailedRadioState == RIL.GECKO_DETAILED_RADIOSTATE_DISABLING) { michael@0: // We're changing the radio power currently, ignore any changes. michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.debug("Data call settings: connect data call."); michael@0: } michael@0: this.setupDataCallByType("default"); michael@0: }, michael@0: michael@0: getDataCallStateByType: function(apnType) { michael@0: let apnSetting = this.apnSettings.byType[apnType]; michael@0: if (!apnSetting) { michael@0: return RIL.GECKO_NETWORK_STATE_UNKNOWN; michael@0: } michael@0: if (!apnSetting.iface.inConnectedTypes(apnType)) { michael@0: return RIL.GECKO_NETWORK_STATE_DISCONNECTED; michael@0: } michael@0: return apnSetting.iface.state; michael@0: }, michael@0: michael@0: setupDataCallByType: function(apnType) { michael@0: if (DEBUG) { michael@0: this.debug("setupDataCallByType: " + apnType); michael@0: } michael@0: let apnSetting = this.apnSettings.byType[apnType]; michael@0: if (!apnSetting) { michael@0: if (DEBUG) { michael@0: this.debug("No apn setting for type: " + apnType); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let dataInfo = this.radioInterface.rilContext.data; michael@0: if (dataInfo.state != RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED || michael@0: dataInfo.type == RIL.GECKO_MOBILE_CONNECTION_STATE_UNKNOWN) { michael@0: return; michael@0: } michael@0: michael@0: apnSetting.iface.connect(apnType); michael@0: // We just call connect() function, so this interface should be in michael@0: // connecting state. If this interface is already in connected state, we michael@0: // are sure that this interface have successfully established connection michael@0: // for other data call types before we call connect() function for current michael@0: // data call type. In this circumstance, we have to directly update the michael@0: // necessary data call and interface information to RILContentHelper michael@0: // and network manager for current data call type. michael@0: if (apnSetting.iface.connected) { michael@0: // Update the interface status via-registration if the interface has michael@0: // already been registered in the network manager. michael@0: if (apnSetting.iface.name in gNetworkManager.networkInterfaces) { michael@0: gNetworkManager.unregisterNetworkInterface(apnSetting.iface); michael@0: } michael@0: gNetworkManager.registerNetworkInterface(apnSetting.iface); michael@0: michael@0: Services.obs.notifyObservers(apnSetting.iface, michael@0: kNetworkInterfaceStateChangedTopic, michael@0: null); michael@0: } michael@0: }, michael@0: michael@0: deactivateDataCallByType: function(apnType) { michael@0: if (DEBUG) { michael@0: this.debug("deactivateDataCallByType: " + apnType); michael@0: } michael@0: let apnSetting = this.apnSettings.byType[apnType]; michael@0: if (!apnSetting) { michael@0: if (DEBUG) { michael@0: this.debug("No apn setting for type: " + apnType); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: apnSetting.iface.disconnect(apnType); michael@0: // We just call disconnect() function, so this interface should be in michael@0: // disconnecting state. If this interface is still in connected state, we michael@0: // are sure that other data call types still need this connection of this michael@0: // interface. In this circumstance, we have to directly update the michael@0: // necessary data call and interface information to RILContentHelper michael@0: // and network manager for current data call type. michael@0: if (apnSetting.iface.connectedTypes.length && apnSetting.iface.connected) { michael@0: // Update the interface status via-registration if the interface has michael@0: // already been registered in the network manager. michael@0: if (apnSetting.iface.name in gNetworkManager.networkInterfaces) { michael@0: gNetworkManager.unregisterNetworkInterface(apnSetting.iface); michael@0: } michael@0: gNetworkManager.registerNetworkInterface(apnSetting.iface); michael@0: michael@0: Services.obs.notifyObservers(apnSetting.iface, michael@0: kNetworkInterfaceStateChangedTopic, michael@0: null); michael@0: } michael@0: }, michael@0: michael@0: deactivateDataCalls: function() { michael@0: let dataDisconnecting = false; michael@0: for (let [, apnSetting] of Iterator(this.apnSettings.byApn)) { michael@0: for (let type of apnSetting.types) { michael@0: if (this.getDataCallStateByType(type) == michael@0: RIL.GECKO_NETWORK_STATE_CONNECTED) { michael@0: this.deactivateDataCallByType(type); michael@0: dataDisconnecting = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // No data calls exist. It's safe to proceed the pending radio power off michael@0: // request. michael@0: if (gRadioEnabledController.isDeactivatingDataCalls() && !dataDisconnecting) { michael@0: gRadioEnabledController.finishDeactivatingDataCalls(this.clientId); michael@0: } michael@0: }, michael@0: michael@0: registerDataCallCallback: function(callback) { michael@0: if (this._dataCallbacks.indexOf(callback) != -1) { michael@0: throw new Error("Already registered this callback: " + callback); michael@0: } michael@0: this._dataCallbacks.push(callback); michael@0: if (DEBUG) { michael@0: this.debug("Registering callback: " + callback); michael@0: } michael@0: }, michael@0: michael@0: unregisterDataCallCallback: function(callback) { michael@0: let index = this._dataCallbacks.indexOf(callback); michael@0: if (index != -1) { michael@0: this._dataCallbacks.splice(index, 1); michael@0: if (DEBUG) { michael@0: this.debug("Unregistering callback: " + callback); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handle data errors. michael@0: */ michael@0: handleDataCallError: function(message) { michael@0: // Notify data call error only for data APN michael@0: let apnSetting = this.apnSettings && this.apnSettings.byType.default; michael@0: if (apnSetting) { michael@0: if (message.apn == apnSetting.apn && michael@0: apnSetting.iface.inConnectedTypes("default")) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:DataError", michael@0: this.clientId, message); michael@0: } michael@0: } michael@0: michael@0: this._deliverDataCallCallback("dataCallError", [message]); michael@0: }, michael@0: michael@0: /** michael@0: * Handle data call state changes. michael@0: */ michael@0: handleDataCallState: function(datacall) { michael@0: this._deliverDataCallCallback("dataCallStateChanged", [datacall]); michael@0: michael@0: // Process pending radio power off request after all data calls michael@0: // are disconnected. michael@0: if (datacall.state == RIL.GECKO_NETWORK_STATE_UNKNOWN && michael@0: this.allDataDisconnected()) { michael@0: if (gRadioEnabledController.isDeactivatingDataCalls()) { michael@0: if (DEBUG) { michael@0: this.debug("All data connections are disconnected."); michael@0: } michael@0: gRadioEnabledController.finishDeactivatingDataCalls(this.clientId); michael@0: } michael@0: michael@0: if (this._pendingApnSettings) { michael@0: if (DEBUG) { michael@0: this.debug("Setup pending apn settings."); michael@0: } michael@0: this._setupApnSettings(this._pendingApnSettings); michael@0: this._pendingApnSettings = null; michael@0: this.updateRILNetworkInterface(); michael@0: } michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: function RadioInterfaceLayer() { michael@0: let workerMessenger = new WorkerMessenger(); michael@0: workerMessenger.init(); michael@0: michael@0: let numIfaces = this.numRadioInterfaces; michael@0: if (DEBUG) debug(numIfaces + " interfaces"); michael@0: this.radioInterfaces = []; michael@0: for (let clientId = 0; clientId < numIfaces; clientId++) { michael@0: this.radioInterfaces.push(new RadioInterface(clientId, workerMessenger)); michael@0: } michael@0: michael@0: Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); michael@0: michael@0: gMessageManager.init(this); michael@0: gRadioEnabledController.init(this); michael@0: gDataConnectionManager.init(this); michael@0: } michael@0: RadioInterfaceLayer.prototype = { michael@0: michael@0: classID: RADIOINTERFACELAYER_CID, michael@0: classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACELAYER_CID, michael@0: classDescription: "RadioInterfaceLayer", michael@0: interfaces: [Ci.nsIRadioInterfaceLayer]}), michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIRadioInterfaceLayer, michael@0: Ci.nsIObserver]), michael@0: michael@0: /** michael@0: * nsIObserver interface methods. michael@0: */ michael@0: michael@0: observe: function(subject, topic, data) { michael@0: switch (topic) { michael@0: case NS_XPCOM_SHUTDOWN_OBSERVER_ID: michael@0: for (let radioInterface of this.radioInterfaces) { michael@0: radioInterface.shutdown(); michael@0: } michael@0: this.radioInterfaces = null; michael@0: Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * nsIRadioInterfaceLayer interface methods. michael@0: */ michael@0: michael@0: getRadioInterface: function(clientId) { michael@0: return this.radioInterfaces[clientId]; michael@0: }, michael@0: michael@0: setMicrophoneMuted: function(muted) { michael@0: for (let clientId = 0; clientId < this.numRadioInterfaces; clientId++) { michael@0: let radioInterface = this.radioInterfaces[clientId]; michael@0: radioInterface.workerMessenger.send("setMute", { muted: muted }); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyGetter(RadioInterfaceLayer.prototype, michael@0: "numRadioInterfaces", function() { michael@0: try { michael@0: return Services.prefs.getIntPref(kPrefRilNumRadioInterfaces); michael@0: } catch(e) {} michael@0: michael@0: return 1; michael@0: }); michael@0: michael@0: function WorkerMessenger() { michael@0: // Initial owning attributes. michael@0: this.radioInterfaces = []; michael@0: this.tokenCallbackMap = {}; michael@0: michael@0: this.worker = new ChromeWorker("resource://gre/modules/ril_worker.js"); michael@0: this.worker.onerror = this.onerror.bind(this); michael@0: this.worker.onmessage = this.onmessage.bind(this); michael@0: } michael@0: WorkerMessenger.prototype = { michael@0: radioInterfaces: null, michael@0: worker: null, michael@0: michael@0: // This gets incremented each time we send out a message. michael@0: token: 1, michael@0: michael@0: // Maps tokens we send out with messages to the message callback. michael@0: tokenCallbackMap: null, michael@0: michael@0: init: function() { michael@0: let options = { michael@0: debug: DEBUG, michael@0: cellBroadcastDisabled: false, michael@0: clirMode: RIL.CLIR_DEFAULT, michael@0: quirks: { michael@0: callstateExtraUint32: michael@0: libcutils.property_get("ro.moz.ril.callstate_extra_int", "false") === "true", michael@0: v5Legacy: michael@0: libcutils.property_get("ro.moz.ril.v5_legacy", "true") === "true", michael@0: requestUseDialEmergencyCall: michael@0: libcutils.property_get("ro.moz.ril.dial_emergency_call", "false") === "true", michael@0: simAppStateExtraFields: michael@0: libcutils.property_get("ro.moz.ril.simstate_extra_field", "false") === "true", michael@0: extraUint2ndCall: michael@0: libcutils.property_get("ro.moz.ril.extra_int_2nd_call", "false") == "true", michael@0: haveQueryIccLockRetryCount: michael@0: libcutils.property_get("ro.moz.ril.query_icc_count", "false") == "true", michael@0: sendStkProfileDownload: michael@0: libcutils.property_get("ro.moz.ril.send_stk_profile_dl", "false") == "true", michael@0: dataRegistrationOnDemand: michael@0: libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true" michael@0: }, michael@0: rilEmergencyNumbers: libcutils.property_get("ril.ecclist") || michael@0: libcutils.property_get("ro.ril.ecclist") michael@0: }; michael@0: michael@0: try { michael@0: options.cellBroadcastDisabled = michael@0: Services.prefs.getBoolPref(kPrefCellBroadcastDisabled); michael@0: } catch(e) {} michael@0: michael@0: try { michael@0: options.clirMode = Services.prefs.getIntPref(kPrefClirModePreference); michael@0: } catch(e) {} michael@0: michael@0: this.send(null, "setInitialOptions", options); michael@0: }, michael@0: michael@0: debug: function(aClientId, aMessage) { michael@0: // We use the same debug subject with RadioInterface's here. michael@0: dump("-*- RadioInterface[" + aClientId + "]: " + aMessage + "\n"); michael@0: }, michael@0: michael@0: onerror: function(event) { michael@0: if (DEBUG) { michael@0: this.debug("X", "Got an error: " + event.filename + ":" + michael@0: event.lineno + ": " + event.message + "\n"); michael@0: } michael@0: event.preventDefault(); michael@0: }, michael@0: michael@0: /** michael@0: * Process the incoming message from the RIL worker. michael@0: */ michael@0: onmessage: function(event) { michael@0: let message = event.data; michael@0: let clientId = message.rilMessageClientId; michael@0: if (clientId === null) { michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.debug(clientId, "Received message from worker: " + JSON.stringify(message)); michael@0: } michael@0: michael@0: let token = message.rilMessageToken; michael@0: if (token == null) { michael@0: // That's an unsolicited message. Pass to RadioInterface directly. michael@0: let radioInterface = this.radioInterfaces[clientId]; michael@0: radioInterface.handleUnsolicitedWorkerMessage(message); michael@0: return; michael@0: } michael@0: michael@0: let callback = this.tokenCallbackMap[message.rilMessageToken]; michael@0: if (!callback) { michael@0: if (DEBUG) this.debug(clientId, "Ignore orphan token: " + message.rilMessageToken); michael@0: return; michael@0: } michael@0: michael@0: let keep = false; michael@0: try { michael@0: keep = callback(message); michael@0: } catch(e) { michael@0: if (DEBUG) this.debug(clientId, "callback throws an exception: " + e); michael@0: } michael@0: michael@0: if (!keep) { michael@0: delete this.tokenCallbackMap[message.rilMessageToken]; michael@0: } michael@0: }, michael@0: michael@0: registerClient: function(aClientId, aRadioInterface) { michael@0: if (DEBUG) this.debug(aClientId, "Starting RIL Worker"); michael@0: michael@0: // Keep a reference so that we can dispatch unsolicited messages to it. michael@0: this.radioInterfaces[aClientId] = aRadioInterface; michael@0: michael@0: this.send(null, "registerClient", { clientId: aClientId }); michael@0: gSystemWorkerManager.registerRilWorker(aClientId, this.worker); michael@0: }, michael@0: michael@0: /** michael@0: * Send arbitrary message to worker. michael@0: * michael@0: * @param rilMessageType michael@0: * A text message type. michael@0: * @param message [optional] michael@0: * An optional message object to send. michael@0: * @param callback [optional] michael@0: * An optional callback function which is called when worker replies michael@0: * with an message containing a "rilMessageToken" attribute of the michael@0: * same value we passed. This callback function accepts only one michael@0: * parameter -- the reply from worker. It also returns a boolean michael@0: * value true to keep current token-callback mapping and wait for michael@0: * another worker reply, or false to remove the mapping. michael@0: */ michael@0: send: function(clientId, rilMessageType, message, callback) { michael@0: message = message || {}; michael@0: michael@0: message.rilMessageClientId = clientId; michael@0: message.rilMessageToken = this.token; michael@0: this.token++; michael@0: michael@0: if (callback) { michael@0: // Only create the map if callback is provided. For sending a request michael@0: // and intentionally leaving the callback undefined, that reply will michael@0: // be dropped in |this.onmessage| because of that orphan token. michael@0: // michael@0: // For sending a request that never replied at all, we're fine with this michael@0: // because no callback shall be passed and we leave nothing to be cleaned michael@0: // up later. michael@0: this.tokenCallbackMap[message.rilMessageToken] = callback; michael@0: } michael@0: michael@0: message.rilMessageType = rilMessageType; michael@0: this.worker.postMessage(message); michael@0: }, michael@0: michael@0: /** michael@0: * Send message to worker and return worker reply to RILContentHelper. michael@0: * michael@0: * @param msg michael@0: * A message object from ppmm. michael@0: * @param rilMessageType michael@0: * A text string for worker message type. michael@0: * @param ipcType [optinal] michael@0: * A text string for ipc message type. "msg.name" if omitted. michael@0: * michael@0: * @TODO: Bug 815526 - deprecate RILContentHelper. michael@0: */ michael@0: sendWithIPCMessage: function(clientId, msg, rilMessageType, ipcType) { michael@0: this.send(clientId, rilMessageType, msg.json.data, (function(reply) { michael@0: ipcType = ipcType || msg.name; michael@0: msg.target.sendAsyncMessage(ipcType, { michael@0: clientId: clientId, michael@0: data: reply michael@0: }); michael@0: return false; michael@0: }).bind(this)); michael@0: } michael@0: }; michael@0: michael@0: function RadioInterface(aClientId, aWorkerMessenger) { michael@0: this.clientId = aClientId; michael@0: this.workerMessenger = { michael@0: send: aWorkerMessenger.send.bind(aWorkerMessenger, aClientId), michael@0: sendWithIPCMessage: michael@0: aWorkerMessenger.sendWithIPCMessage.bind(aWorkerMessenger, aClientId), michael@0: }; michael@0: aWorkerMessenger.registerClient(aClientId, this); michael@0: michael@0: this.supportedNetworkTypes = this.getSupportedNetworkTypes(); michael@0: michael@0: this.rilContext = { michael@0: radioState: RIL.GECKO_RADIOSTATE_UNAVAILABLE, michael@0: detailedRadioState: null, michael@0: cardState: RIL.GECKO_CARDSTATE_UNKNOWN, michael@0: networkSelectionMode: RIL.GECKO_NETWORK_SELECTION_UNKNOWN, michael@0: iccInfo: null, michael@0: imsi: null, michael@0: michael@0: // These objects implement the nsIDOMMozMobileConnectionInfo interface, michael@0: // although the actual implementation lives in the content process. So are michael@0: // the child attributes `network` and `cell`, which implement michael@0: // nsIDOMMozMobileNetworkInfo and nsIDOMMozMobileCellInfo respectively. michael@0: voice: {connected: false, michael@0: emergencyCallsOnly: false, michael@0: roaming: false, michael@0: network: null, michael@0: cell: null, michael@0: type: null, michael@0: signalStrength: null, michael@0: relSignalStrength: null}, michael@0: data: {connected: false, michael@0: emergencyCallsOnly: false, michael@0: roaming: false, michael@0: network: null, michael@0: cell: null, michael@0: type: null, michael@0: signalStrength: null, michael@0: relSignalStrength: null}, michael@0: }; michael@0: michael@0: this.voicemailInfo = { michael@0: number: null, michael@0: displayName: null michael@0: }; michael@0: michael@0: this.operatorInfo = {}; michael@0: michael@0: let lock = gSettingsService.createLock(); michael@0: michael@0: // Read the "time.clock.automatic-update.enabled" setting to see if michael@0: // we need to adjust the system clock time by NITZ or SNTP. michael@0: lock.get(kSettingsClockAutoUpdateEnabled, this); michael@0: michael@0: // Read the "time.timezone.automatic-update.enabled" setting to see if michael@0: // we need to adjust the system timezone by NITZ. michael@0: lock.get(kSettingsTimezoneAutoUpdateEnabled, this); michael@0: michael@0: // Set "time.clock.automatic-update.available" to false when starting up. michael@0: this.setClockAutoUpdateAvailable(false); michael@0: michael@0: // Set "time.timezone.automatic-update.available" to false when starting up. michael@0: this.setTimezoneAutoUpdateAvailable(false); michael@0: michael@0: // Read the Cell Broadcast Search List setting, string of integers or integer michael@0: // ranges separated by comma, to set listening channels. michael@0: lock.get(kSettingsCellBroadcastSearchList, this); michael@0: michael@0: Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false); michael@0: Services.obs.addObserver(this, kSysClockChangeObserverTopic, false); michael@0: Services.obs.addObserver(this, kScreenStateChangedTopic, false); michael@0: michael@0: Services.obs.addObserver(this, kNetworkConnStateChangedTopic, false); michael@0: Services.obs.addObserver(this, kNetworkActiveChangedTopic, false); michael@0: Services.prefs.addObserver(kPrefCellBroadcastDisabled, this, false); michael@0: michael@0: this.portAddressedSmsApps = {}; michael@0: this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this); michael@0: michael@0: this._receivedSmsSegmentsMap = {}; michael@0: michael@0: this._sntp = new Sntp(this.setClockBySntp.bind(this), michael@0: Services.prefs.getIntPref("network.sntp.maxRetryCount"), michael@0: Services.prefs.getIntPref("network.sntp.refreshPeriod"), michael@0: Services.prefs.getIntPref("network.sntp.timeout"), michael@0: Services.prefs.getCharPref("network.sntp.pools").split(";"), michael@0: Services.prefs.getIntPref("network.sntp.port")); michael@0: } michael@0: michael@0: RadioInterface.prototype = { michael@0: michael@0: classID: RADIOINTERFACE_CID, michael@0: classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACE_CID, michael@0: classDescription: "RadioInterface", michael@0: interfaces: [Ci.nsIRadioInterface]}), michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIRadioInterface, michael@0: Ci.nsIObserver, michael@0: Ci.nsISettingsServiceCallback]), michael@0: michael@0: // A private wrapped WorkerMessenger instance. michael@0: workerMessenger: null, michael@0: michael@0: debug: function(s) { michael@0: dump("-*- RadioInterface[" + this.clientId + "]: " + s + "\n"); michael@0: }, michael@0: michael@0: shutdown: function() { michael@0: // Release the CPU wake lock for handling the received SMS. michael@0: this._releaseSmsHandledWakeLock(); michael@0: michael@0: Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic); michael@0: Services.obs.removeObserver(this, kSysClockChangeObserverTopic); michael@0: Services.obs.removeObserver(this, kScreenStateChangedTopic); michael@0: Services.obs.removeObserver(this, kNetworkConnStateChangedTopic); michael@0: Services.obs.removeObserver(this, kNetworkActiveChangedTopic); michael@0: }, michael@0: michael@0: /** michael@0: * A utility function to copy objects. The srcInfo may contain michael@0: * "rilMessageType", should ignore it. michael@0: */ michael@0: updateInfo: function(srcInfo, destInfo) { michael@0: for (let key in srcInfo) { michael@0: if (key === "rilMessageType") { michael@0: continue; michael@0: } michael@0: destInfo[key] = srcInfo[key]; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * A utility function to compare objects. The srcInfo may contain michael@0: * "rilMessageType", should ignore it. michael@0: */ michael@0: isInfoChanged: function(srcInfo, destInfo) { michael@0: if (!destInfo) { michael@0: return true; michael@0: } michael@0: michael@0: for (let key in srcInfo) { michael@0: if (key === "rilMessageType") { michael@0: continue; michael@0: } michael@0: if (srcInfo[key] !== destInfo[key]) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * A utility function to get supportedNetworkTypes from system property michael@0: */ michael@0: getSupportedNetworkTypes: function() { michael@0: let key = "ro.moz.ril." + this.clientId + ".network_types"; michael@0: let supportedNetworkTypes = libcutils.property_get(key, "").split(","); michael@0: for (let type of supportedNetworkTypes) { michael@0: // If the value in system property is not valid, use the default one which michael@0: // is defined in ril_consts.js. michael@0: if (RIL.GECKO_SUPPORTED_NETWORK_TYPES.indexOf(type) < 0) { michael@0: if (DEBUG) this.debug("Unknown network type: " + type); michael@0: supportedNetworkTypes = michael@0: RIL.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT.split(","); michael@0: break; michael@0: } michael@0: } michael@0: if (DEBUG) this.debug("Supported Network Types: " + supportedNetworkTypes); michael@0: return supportedNetworkTypes; michael@0: }, michael@0: michael@0: /** michael@0: * Process a message from the content process. michael@0: */ michael@0: receiveMessage: function(msg) { michael@0: switch (msg.name) { michael@0: case "RIL:GetRilContext": michael@0: // This message is sync. michael@0: return this.rilContext; michael@0: case "RIL:GetLastKnownNetwork": michael@0: // This message is sync. michael@0: return this._lastKnownNetwork; michael@0: case "RIL:GetLastKnownHomeNetwork": michael@0: // This message is sync. michael@0: return this._lastKnownHomeNetwork; michael@0: case "RIL:GetAvailableNetworks": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "getAvailableNetworks"); michael@0: break; michael@0: case "RIL:SelectNetwork": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "selectNetwork"); michael@0: break; michael@0: case "RIL:SelectNetworkAuto": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "selectNetworkAuto"); michael@0: break; michael@0: case "RIL:SetPreferredNetworkType": michael@0: this.setPreferredNetworkType(msg.target, msg.json.data); michael@0: break; michael@0: case "RIL:GetPreferredNetworkType": michael@0: this.getPreferredNetworkType(msg.target, msg.json.data); michael@0: break; michael@0: case "RIL:GetCardLockState": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "iccGetCardLockState", michael@0: "RIL:CardLockResult"); michael@0: break; michael@0: case "RIL:UnlockCardLock": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "iccUnlockCardLock", michael@0: "RIL:CardLockResult"); michael@0: break; michael@0: case "RIL:SetCardLock": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "iccSetCardLock", michael@0: "RIL:CardLockResult"); michael@0: break; michael@0: case "RIL:GetCardLockRetryCount": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "iccGetCardLockRetryCount", michael@0: "RIL:CardLockRetryCount"); michael@0: break; michael@0: case "RIL:SendMMI": michael@0: this.sendMMI(msg.target, msg.json.data); michael@0: break; michael@0: case "RIL:CancelMMI": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "cancelUSSD"); michael@0: break; michael@0: case "RIL:SendStkResponse": michael@0: this.workerMessenger.send("sendStkTerminalResponse", msg.json.data); michael@0: break; michael@0: case "RIL:SendStkMenuSelection": michael@0: this.workerMessenger.send("sendStkMenuSelection", msg.json.data); michael@0: break; michael@0: case "RIL:SendStkTimerExpiration": michael@0: this.workerMessenger.send("sendStkTimerExpiration", msg.json.data); michael@0: break; michael@0: case "RIL:SendStkEventDownload": michael@0: this.workerMessenger.send("sendStkEventDownload", msg.json.data); michael@0: break; michael@0: case "RIL:IccOpenChannel": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "iccOpenChannel"); michael@0: break; michael@0: case "RIL:IccCloseChannel": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "iccCloseChannel"); michael@0: break; michael@0: case "RIL:IccExchangeAPDU": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "iccExchangeAPDU"); michael@0: break; michael@0: case "RIL:ReadIccContacts": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "readICCContacts"); michael@0: break; michael@0: case "RIL:UpdateIccContact": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "updateICCContact"); michael@0: break; michael@0: case "RIL:MatchMvno": michael@0: this.matchMvno(msg.target, msg.json.data); michael@0: break; michael@0: case "RIL:SetCallForwardingOptions": michael@0: this.setCallForwardingOptions(msg.target, msg.json.data); michael@0: break; michael@0: case "RIL:GetCallForwardingOptions": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "queryCallForwardStatus"); michael@0: break; michael@0: case "RIL:SetCallBarringOptions": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "setCallBarring"); michael@0: break; michael@0: case "RIL:GetCallBarringOptions": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "queryCallBarringStatus"); michael@0: break; michael@0: case "RIL:ChangeCallBarringPassword": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "changeCallBarringPassword"); michael@0: break; michael@0: case "RIL:SetCallWaitingOptions": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "setCallWaiting"); michael@0: break; michael@0: case "RIL:GetCallWaitingOptions": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "queryCallWaiting"); michael@0: break; michael@0: case "RIL:SetCallingLineIdRestriction": michael@0: this.setCallingLineIdRestriction(msg.target, msg.json.data); michael@0: break; michael@0: case "RIL:GetCallingLineIdRestriction": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "getCLIR"); michael@0: break; michael@0: case "RIL:ExitEmergencyCbMode": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "exitEmergencyCbMode"); michael@0: break; michael@0: case "RIL:SetRadioEnabled": michael@0: this.setRadioEnabled(msg.target, msg.json.data); michael@0: break; michael@0: case "RIL:GetVoicemailInfo": michael@0: // This message is sync. michael@0: return this.voicemailInfo; michael@0: case "RIL:SetRoamingPreference": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "setRoamingPreference"); michael@0: break; michael@0: case "RIL:GetRoamingPreference": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "queryRoamingPreference"); michael@0: break; michael@0: case "RIL:SetVoicePrivacyMode": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "setVoicePrivacyMode"); michael@0: break; michael@0: case "RIL:GetVoicePrivacyMode": michael@0: this.workerMessenger.sendWithIPCMessage(msg, "queryVoicePrivacyMode"); michael@0: break; michael@0: case "RIL:GetSupportedNetworkTypes": michael@0: // This message is sync. michael@0: return this.supportedNetworkTypes; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: handleUnsolicitedWorkerMessage: function(message) { michael@0: let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); michael@0: switch (message.rilMessageType) { michael@0: case "callRing": michael@0: gTelephonyProvider.notifyCallRing(); michael@0: break; michael@0: case "callStateChange": michael@0: gTelephonyProvider.notifyCallStateChanged(this.clientId, message.call); michael@0: break; michael@0: case "callDisconnected": michael@0: gTelephonyProvider.notifyCallDisconnected(this.clientId, message.call); michael@0: break; michael@0: case "conferenceCallStateChanged": michael@0: gTelephonyProvider.notifyConferenceCallStateChanged(message.state); michael@0: break; michael@0: case "cdmaCallWaiting": michael@0: gTelephonyProvider.notifyCdmaCallWaiting(this.clientId, message.number); michael@0: break; michael@0: case "suppSvcNotification": michael@0: gTelephonyProvider.notifySupplementaryService(this.clientId, michael@0: message.callIndex, michael@0: message.notification); michael@0: break; michael@0: case "datacallerror": michael@0: connHandler.handleDataCallError(message); michael@0: break; michael@0: case "datacallstatechange": michael@0: let addresses = []; michael@0: for (let i = 0; i < message.addresses.length; i++) { michael@0: let [address, prefixLength] = message.addresses[i].split("/"); michael@0: // From AOSP hardware/ril/include/telephony/ril.h, that address prefix michael@0: // is said to be OPTIONAL, but we never met such case before. michael@0: addresses.push({ michael@0: address: address, michael@0: prefixLength: prefixLength ? parseInt(prefixLength, 10) : 0 michael@0: }); michael@0: } michael@0: message.addresses = addresses; michael@0: connHandler.handleDataCallState(message); michael@0: break; michael@0: case "emergencyCbModeChange": michael@0: this.handleEmergencyCbModeChange(message); michael@0: break; michael@0: case "networkinfochanged": michael@0: this.updateNetworkInfo(message); michael@0: break; michael@0: case "networkselectionmodechange": michael@0: this.updateNetworkSelectionMode(message); michael@0: break; michael@0: case "voiceregistrationstatechange": michael@0: this.updateVoiceConnection(message); michael@0: break; michael@0: case "dataregistrationstatechange": michael@0: this.updateDataConnection(message); michael@0: break; michael@0: case "signalstrengthchange": michael@0: this.handleSignalStrengthChange(message); michael@0: break; michael@0: case "operatorchange": michael@0: this.handleOperatorChange(message); michael@0: break; michael@0: case "otastatuschange": michael@0: this.handleOtaStatus(message); michael@0: break; michael@0: case "radiostatechange": michael@0: this.handleRadioStateChange(message); michael@0: break; michael@0: case "cardstatechange": michael@0: this.rilContext.cardState = message.cardState; michael@0: gRadioEnabledController.receiveCardState(this.clientId); michael@0: gMessageManager.sendIccMessage("RIL:CardStateChanged", michael@0: this.clientId, message); michael@0: break; michael@0: case "sms-received": michael@0: this.handleSmsMultipart(message); michael@0: break; michael@0: case "cellbroadcast-received": michael@0: message.timestamp = Date.now(); michael@0: gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived", michael@0: this.clientId, message); michael@0: break; michael@0: case "nitzTime": michael@0: this.handleNitzTime(message); michael@0: break; michael@0: case "iccinfochange": michael@0: this.handleIccInfoChange(message); michael@0: break; michael@0: case "iccimsi": michael@0: this.rilContext.imsi = message.imsi; michael@0: break; michael@0: case "iccmbdn": michael@0: this.handleIccMbdn(message); michael@0: break; michael@0: case "iccmwis": michael@0: gMessageManager.sendVoicemailMessage("RIL:VoicemailNotification", michael@0: this.clientId, message.mwi); michael@0: break; michael@0: case "USSDReceived": michael@0: if (DEBUG) this.debug("USSDReceived " + JSON.stringify(message)); michael@0: this.handleUSSDReceived(message); michael@0: break; michael@0: case "stkcommand": michael@0: this.handleStkProactiveCommand(message); michael@0: break; michael@0: case "stksessionend": michael@0: gMessageManager.sendIccMessage("RIL:StkSessionEnd", this.clientId, null); michael@0: break; michael@0: case "exitEmergencyCbMode": michael@0: this.handleExitEmergencyCbMode(message); michael@0: break; michael@0: case "cdma-info-rec-received": michael@0: if (DEBUG) this.debug("cdma-info-rec-received: " + JSON.stringify(message)); michael@0: gSystemMessenger.broadcastMessage("cdma-info-rec-received", message); michael@0: break; michael@0: default: michael@0: throw new Error("Don't know about this message type: " + michael@0: message.rilMessageType); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Get phone number from iccInfo. michael@0: * michael@0: * If the icc card is gsm card, the phone number is in msisdn. michael@0: * @see nsIDOMMozGsmIccInfo michael@0: * michael@0: * Otherwise, the phone number is in mdn. michael@0: * @see nsIDOMMozCdmaIccInfo michael@0: */ michael@0: getPhoneNumber: function() { michael@0: let iccInfo = this.rilContext.iccInfo; michael@0: michael@0: if (!iccInfo) { michael@0: return null; michael@0: } michael@0: michael@0: // After moving SMS code out of RadioInterfaceLayer, we could use michael@0: // |iccInfo instanceof Ci.nsIDOMMozGsmIccInfo| here. michael@0: // TODO: Bug 873351 - B2G SMS: move SMS code out of RadioInterfaceLayer to michael@0: // SmsService michael@0: let number = (iccInfo instanceof GsmIccInfo) ? iccInfo.msisdn : iccInfo.mdn; michael@0: michael@0: // Workaround an xpconnect issue with undefined string objects. michael@0: // See bug 808220 michael@0: if (number === undefined || number === "undefined") { michael@0: return null; michael@0: } michael@0: michael@0: return number; michael@0: }, michael@0: michael@0: /** michael@0: * A utility function to get the ICC ID of the SIM card (if installed). michael@0: */ michael@0: getIccId: function() { michael@0: let iccInfo = this.rilContext.iccInfo; michael@0: michael@0: if (!iccInfo) { michael@0: return null; michael@0: } michael@0: michael@0: let iccId = iccInfo.iccid; michael@0: michael@0: // Workaround an xpconnect issue with undefined string objects. michael@0: // See bug 808220 michael@0: if (iccId === undefined || iccId === "undefined") { michael@0: return null; michael@0: } michael@0: michael@0: return iccId; michael@0: }, michael@0: michael@0: // Matches the mvnoData pattern with imsi. Characters 'x' and 'X' are skipped michael@0: // and not compared. E.g., if the mvnoData passed is '310260x10xxxxxx', michael@0: // then the function returns true only if imsi has the same first 6 digits, michael@0: // 8th and 9th digit. michael@0: isImsiMatches: function(mvnoData) { michael@0: let imsi = this.rilContext.imsi; michael@0: michael@0: // This should not be an error, but a mismatch. michael@0: if (mvnoData.length > imsi.length) { michael@0: return false; michael@0: } michael@0: michael@0: for (let i = 0; i < mvnoData.length; i++) { michael@0: let c = mvnoData[i]; michael@0: if ((c !== 'x') && (c !== 'X') && (c !== imsi[i])) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: matchMvno: function(target, message) { michael@0: if (DEBUG) this.debug("matchMvno: " + JSON.stringify(message)); michael@0: michael@0: if (!message || !message.mvnoType || !message.mvnoData) { michael@0: message.errorMsg = RIL.GECKO_ERROR_INVALID_PARAMETER; michael@0: } michael@0: // Currently we only support imsi matching. michael@0: if (message.mvnoType != "imsi") { michael@0: message.errorMsg = RIL.GECKO_ERROR_MODE_NOT_SUPPORTED; michael@0: } michael@0: // Fire error if mvnoType is imsi but imsi is not available. michael@0: if (!this.rilContext.imsi) { michael@0: message.errorMsg = RIL.GECKO_ERROR_GENERIC_FAILURE; michael@0: } michael@0: michael@0: if (!message.errorMsg) { michael@0: message.result = this.isImsiMatches(message.mvnoData); michael@0: } michael@0: michael@0: target.sendAsyncMessage("RIL:MatchMvno", { michael@0: clientId: this.clientId, michael@0: data: message michael@0: }); michael@0: }, michael@0: michael@0: updateNetworkInfo: function(message) { michael@0: let voiceMessage = message[RIL.NETWORK_INFO_VOICE_REGISTRATION_STATE]; michael@0: let dataMessage = message[RIL.NETWORK_INFO_DATA_REGISTRATION_STATE]; michael@0: let operatorMessage = message[RIL.NETWORK_INFO_OPERATOR]; michael@0: let selectionMessage = message[RIL.NETWORK_INFO_NETWORK_SELECTION_MODE]; michael@0: let signalMessage = message[RIL.NETWORK_INFO_SIGNAL]; michael@0: michael@0: // Batch the *InfoChanged messages together michael@0: if (voiceMessage) { michael@0: this.updateVoiceConnection(voiceMessage, true); michael@0: } michael@0: michael@0: if (dataMessage) { michael@0: this.updateDataConnection(dataMessage, true); michael@0: } michael@0: michael@0: if (operatorMessage) { michael@0: this.handleOperatorChange(operatorMessage, true); michael@0: } michael@0: michael@0: if (signalMessage) { michael@0: this.handleSignalStrengthChange(signalMessage, true); michael@0: } michael@0: michael@0: let voice = this.rilContext.voice; michael@0: let data = this.rilContext.data; michael@0: michael@0: this.checkRoamingBetweenOperators(voice); michael@0: this.checkRoamingBetweenOperators(data); michael@0: michael@0: if (voiceMessage || operatorMessage || signalMessage) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", michael@0: this.clientId, voice); michael@0: } michael@0: if (dataMessage || operatorMessage || signalMessage) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", michael@0: this.clientId, data); michael@0: } michael@0: michael@0: if (selectionMessage) { michael@0: this.updateNetworkSelectionMode(selectionMessage); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Fix the roaming. RIL can report roaming in some case it is not michael@0: * really the case. See bug 787967 michael@0: * michael@0: * @param registration The voiceMessage or dataMessage from which the michael@0: * roaming state will be changed (maybe, if needed). michael@0: */ michael@0: checkRoamingBetweenOperators: function(registration) { michael@0: let iccInfo = this.rilContext.iccInfo; michael@0: let operator = registration.network; michael@0: let state = registration.state; michael@0: michael@0: if (!iccInfo || !operator || michael@0: state != RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) { michael@0: return; michael@0: } michael@0: michael@0: let spn = iccInfo.spn && iccInfo.spn.toLowerCase(); michael@0: let longName = operator.longName && operator.longName.toLowerCase(); michael@0: let shortName = operator.shortName && operator.shortName.toLowerCase(); michael@0: michael@0: let equalsLongName = longName && (spn == longName); michael@0: let equalsShortName = shortName && (spn == shortName); michael@0: let equalsMcc = iccInfo.mcc == operator.mcc; michael@0: michael@0: registration.roaming = registration.roaming && michael@0: !(equalsMcc && (equalsLongName || equalsShortName)); michael@0: }, michael@0: michael@0: /** michael@0: * Handle data connection changes. michael@0: * michael@0: * @param newInfo The new voice connection information. michael@0: * @param batch When batch is true, the RIL:VoiceInfoChanged message will michael@0: * not be sent. michael@0: */ michael@0: updateVoiceConnection: function(newInfo, batch) { michael@0: let voiceInfo = this.rilContext.voice; michael@0: voiceInfo.state = newInfo.state; michael@0: voiceInfo.connected = newInfo.connected; michael@0: voiceInfo.roaming = newInfo.roaming; michael@0: voiceInfo.emergencyCallsOnly = newInfo.emergencyCallsOnly; michael@0: voiceInfo.type = newInfo.type; michael@0: michael@0: // Make sure we also reset the operator and signal strength information michael@0: // if we drop off the network. michael@0: if (newInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) { michael@0: voiceInfo.cell = null; michael@0: voiceInfo.network = null; michael@0: voiceInfo.signalStrength = null; michael@0: voiceInfo.relSignalStrength = null; michael@0: } else { michael@0: voiceInfo.cell = newInfo.cell; michael@0: voiceInfo.network = this.operatorInfo; michael@0: } michael@0: michael@0: if (!batch) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", michael@0: this.clientId, voiceInfo); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handle the data connection's state has changed. michael@0: * michael@0: * @param newInfo The new data connection information. michael@0: * @param batch When batch is true, the RIL:DataInfoChanged message will michael@0: * not be sent. michael@0: */ michael@0: updateDataConnection: function(newInfo, batch) { michael@0: let dataInfo = this.rilContext.data; michael@0: dataInfo.state = newInfo.state; michael@0: dataInfo.roaming = newInfo.roaming; michael@0: dataInfo.emergencyCallsOnly = newInfo.emergencyCallsOnly; michael@0: dataInfo.type = newInfo.type; michael@0: // For the data connection, the `connected` flag indicates whether michael@0: // there's an active data call. michael@0: dataInfo.connected = false; michael@0: if (gNetworkManager.active && michael@0: gNetworkManager.active.type === michael@0: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE && michael@0: gNetworkManager.active.serviceId === this.clientId) { michael@0: dataInfo.connected = true; michael@0: } michael@0: michael@0: // Make sure we also reset the operator and signal strength information michael@0: // if we drop off the network. michael@0: if (newInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) { michael@0: dataInfo.cell = null; michael@0: dataInfo.network = null; michael@0: dataInfo.signalStrength = null; michael@0: dataInfo.relSignalStrength = null; michael@0: } else { michael@0: dataInfo.cell = newInfo.cell; michael@0: dataInfo.network = this.operatorInfo; michael@0: } michael@0: michael@0: if (!batch) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", michael@0: this.clientId, dataInfo); michael@0: } michael@0: michael@0: let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); michael@0: connHandler.updateRILNetworkInterface(); michael@0: }, michael@0: michael@0: getPreferredNetworkType: function(target, message) { michael@0: this.workerMessenger.send("getPreferredNetworkType", message, (function(response) { michael@0: if (response.success) { michael@0: response.type = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[response.networkType]; michael@0: } michael@0: michael@0: target.sendAsyncMessage("RIL:GetPreferredNetworkType", { michael@0: clientId: this.clientId, michael@0: data: response michael@0: }); michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: setPreferredNetworkType: function(target, message) { michael@0: let networkType = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.indexOf(message.type); michael@0: if (networkType < 0) { michael@0: message.errorMsg = RIL.GECKO_ERROR_INVALID_PARAMETER; michael@0: target.sendAsyncMessage("RIL:SetPreferredNetworkType", { michael@0: clientId: this.clientId, michael@0: data: message michael@0: }); michael@0: return false; michael@0: } michael@0: message.networkType = networkType; michael@0: michael@0: this.workerMessenger.send("setPreferredNetworkType", message, (function(response) { michael@0: target.sendAsyncMessage("RIL:SetPreferredNetworkType", { michael@0: clientId: this.clientId, michael@0: data: response michael@0: }); michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: setCellBroadcastSearchList: function(newSearchList) { michael@0: if ((newSearchList == this._cellBroadcastSearchList) || michael@0: (newSearchList && this._cellBroadcastSearchList && michael@0: newSearchList.gsm == this._cellBroadcastSearchList.gsm && michael@0: newSearchList.cdma == this._cellBroadcastSearchList.cdma)) { michael@0: return; michael@0: } michael@0: michael@0: this.workerMessenger.send("setCellBroadcastSearchList", michael@0: { searchList: newSearchList }, michael@0: (function callback(response) { michael@0: if (!response.success) { michael@0: let lock = gSettingsService.createLock(); michael@0: lock.set(kSettingsCellBroadcastSearchList, michael@0: this._cellBroadcastSearchList, null); michael@0: } else { michael@0: this._cellBroadcastSearchList = response.searchList; michael@0: } michael@0: michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Handle signal strength changes. michael@0: * michael@0: * @param message The new signal strength. michael@0: * @param batch When batch is true, the RIL:VoiceInfoChanged and michael@0: * RIL:DataInfoChanged message will not be sent. michael@0: */ michael@0: handleSignalStrengthChange: function(message, batch) { michael@0: let voiceInfo = this.rilContext.voice; michael@0: // If the voice is not registered, need not to update signal information. michael@0: if (voiceInfo.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED && michael@0: this.isInfoChanged(message.voice, voiceInfo)) { michael@0: this.updateInfo(message.voice, voiceInfo); michael@0: if (!batch) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", michael@0: this.clientId, voiceInfo); michael@0: } michael@0: } michael@0: michael@0: let dataInfo = this.rilContext.data; michael@0: // If the data is not registered, need not to update signal information. michael@0: if (dataInfo.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED && michael@0: this.isInfoChanged(message.data, dataInfo)) { michael@0: this.updateInfo(message.data, dataInfo); michael@0: if (!batch) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", michael@0: this.clientId, dataInfo); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handle operator information changes. michael@0: * michael@0: * @param message The new operator information. michael@0: * @param batch When batch is true, the RIL:VoiceInfoChanged and michael@0: * RIL:DataInfoChanged message will not be sent. michael@0: */ michael@0: handleOperatorChange: function(message, batch) { michael@0: let operatorInfo = this.operatorInfo; michael@0: let voice = this.rilContext.voice; michael@0: let data = this.rilContext.data; michael@0: michael@0: if (this.isInfoChanged(message, operatorInfo)) { michael@0: this.updateInfo(message, operatorInfo); michael@0: michael@0: // Update lastKnownNetwork michael@0: if (message.mcc && message.mnc) { michael@0: this._lastKnownNetwork = message.mcc + "-" + message.mnc; michael@0: } michael@0: michael@0: // If the voice is unregistered, no need to send RIL:VoiceInfoChanged. michael@0: if (voice.network && !batch) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", michael@0: this.clientId, voice); michael@0: } michael@0: michael@0: // If the data is unregistered, no need to send RIL:DataInfoChanged. michael@0: if (data.network && !batch) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", michael@0: this.clientId, data); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: handleOtaStatus: function(message) { michael@0: if (message.status < 0 || michael@0: RIL.CDMA_OTA_PROVISION_STATUS_TO_GECKO.length <= message.status) { michael@0: return; michael@0: } michael@0: michael@0: let status = RIL.CDMA_OTA_PROVISION_STATUS_TO_GECKO[message.status]; michael@0: michael@0: gMessageManager.sendMobileConnectionMessage("RIL:OtaStatusChanged", michael@0: this.clientId, status); michael@0: }, michael@0: michael@0: _convertRadioState: function(state) { michael@0: switch (state) { michael@0: case RIL.GECKO_RADIOSTATE_OFF: michael@0: return RIL.GECKO_DETAILED_RADIOSTATE_DISABLED; michael@0: case RIL.GECKO_RADIOSTATE_READY: michael@0: return RIL.GECKO_DETAILED_RADIOSTATE_ENABLED; michael@0: default: michael@0: return RIL.GECKO_DETAILED_RADIOSTATE_UNKNOWN; michael@0: } michael@0: }, michael@0: michael@0: handleRadioStateChange: function(message) { michael@0: let newState = message.radioState; michael@0: if (this.rilContext.radioState == newState) { michael@0: return; michael@0: } michael@0: this.rilContext.radioState = newState; michael@0: this.handleDetailedRadioStateChanged(this._convertRadioState(newState)); michael@0: michael@0: //TODO Should we notify this change as a card state change? michael@0: }, michael@0: michael@0: handleDetailedRadioStateChanged: function(state) { michael@0: if (this.rilContext.detailedRadioState == state) { michael@0: return; michael@0: } michael@0: this.rilContext.detailedRadioState = state; michael@0: gMessageManager.sendMobileConnectionMessage("RIL:RadioStateChanged", michael@0: this.clientId, state); michael@0: }, michael@0: michael@0: setDataRegistration: function(attach) { michael@0: this.workerMessenger.send("setDataRegistration", {attach: attach}); michael@0: }, michael@0: michael@0: /** michael@0: * TODO: Bug 911713 - B2G NetworkManager: Move policy control logic to michael@0: * NetworkManager michael@0: */ michael@0: updateRILNetworkInterface: function() { michael@0: let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); michael@0: connHandler.updateRILNetworkInterface(); michael@0: }, michael@0: michael@0: /** michael@0: * Update network selection mode michael@0: */ michael@0: updateNetworkSelectionMode: function(message) { michael@0: if (DEBUG) this.debug("updateNetworkSelectionMode: " + JSON.stringify(message)); michael@0: this.rilContext.networkSelectionMode = message.mode; michael@0: gMessageManager.sendMobileConnectionMessage("RIL:NetworkSelectionModeChanged", michael@0: this.clientId, message); michael@0: }, michael@0: michael@0: /** michael@0: * Handle emergency callback mode change. michael@0: */ michael@0: handleEmergencyCbModeChange: function(message) { michael@0: if (DEBUG) this.debug("handleEmergencyCbModeChange: " + JSON.stringify(message)); michael@0: gMessageManager.sendMobileConnectionMessage("RIL:EmergencyCbModeChanged", michael@0: this.clientId, message); michael@0: }, michael@0: michael@0: /** michael@0: * Handle WDP port push PDU. Constructor WDP bearer information and deliver michael@0: * to WapPushManager. michael@0: * michael@0: * @param message michael@0: * A SMS message. michael@0: */ michael@0: handleSmsWdpPortPush: function(message) { michael@0: if (message.encoding != RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { michael@0: if (DEBUG) { michael@0: this.debug("Got port addressed SMS but not encoded in 8-bit alphabet." + michael@0: " Drop!"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let options = { michael@0: bearer: WAP.WDP_BEARER_GSM_SMS_GSM_MSISDN, michael@0: sourceAddress: message.sender, michael@0: sourcePort: message.originatorPort, michael@0: destinationAddress: this.rilContext.iccInfo.msisdn, michael@0: destinationPort: message.destinationPort, michael@0: serviceId: this.clientId michael@0: }; michael@0: WAP.WapPushManager.receiveWdpPDU(message.fullData, message.fullData.length, michael@0: 0, options); michael@0: }, michael@0: michael@0: /** michael@0: * A helper to broadcast the system message to launch registered apps michael@0: * like Costcontrol, Notification and Message app... etc. michael@0: * michael@0: * @param aName michael@0: * The system message name. michael@0: * @param aDomMessage michael@0: * The nsIDOMMozSmsMessage object. michael@0: */ michael@0: broadcastSmsSystemMessage: function(aName, aDomMessage) { michael@0: if (DEBUG) this.debug("Broadcasting the SMS system message: " + aName); michael@0: michael@0: // Sadly we cannot directly broadcast the aDomMessage object michael@0: // because the system message mechamism will rewrap the object michael@0: // based on the content window, which needs to know the properties. michael@0: gSystemMessenger.broadcastMessage(aName, { michael@0: iccId: aDomMessage.iccId, michael@0: type: aDomMessage.type, michael@0: id: aDomMessage.id, michael@0: threadId: aDomMessage.threadId, michael@0: delivery: aDomMessage.delivery, michael@0: deliveryStatus: aDomMessage.deliveryStatus, michael@0: sender: aDomMessage.sender, michael@0: receiver: aDomMessage.receiver, michael@0: body: aDomMessage.body, michael@0: messageClass: aDomMessage.messageClass, michael@0: timestamp: aDomMessage.timestamp, michael@0: sentTimestamp: aDomMessage.sentTimestamp, michael@0: deliveryTimestamp: aDomMessage.deliveryTimestamp, michael@0: read: aDomMessage.read michael@0: }); michael@0: }, michael@0: michael@0: // The following attributes/functions are used for acquiring/releasing the michael@0: // CPU wake lock when the RIL handles the received SMS. Note that we need michael@0: // a timer to bound the lock's life cycle to avoid exhausting the battery. michael@0: _smsHandledWakeLock: null, michael@0: _smsHandledWakeLockTimer: null, michael@0: michael@0: _acquireSmsHandledWakeLock: function() { michael@0: if (!this._smsHandledWakeLock) { michael@0: if (DEBUG) this.debug("Acquiring a CPU wake lock for handling SMS."); michael@0: this._smsHandledWakeLock = gPowerManagerService.newWakeLock("cpu"); michael@0: } michael@0: if (!this._smsHandledWakeLockTimer) { michael@0: if (DEBUG) this.debug("Creating a timer for releasing the CPU wake lock."); michael@0: this._smsHandledWakeLockTimer = michael@0: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: } michael@0: if (DEBUG) this.debug("Setting the timer for releasing the CPU wake lock."); michael@0: this._smsHandledWakeLockTimer michael@0: .initWithCallback(this._releaseSmsHandledWakeLock.bind(this), michael@0: SMS_HANDLED_WAKELOCK_TIMEOUT, michael@0: Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }, michael@0: michael@0: _releaseSmsHandledWakeLock: function() { michael@0: if (DEBUG) this.debug("Releasing the CPU wake lock for handling SMS."); michael@0: if (this._smsHandledWakeLockTimer) { michael@0: this._smsHandledWakeLockTimer.cancel(); michael@0: } michael@0: if (this._smsHandledWakeLock) { michael@0: this._smsHandledWakeLock.unlock(); michael@0: this._smsHandledWakeLock = null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Hash map for received multipart sms fragments. Messages are hashed with michael@0: * its sender address and concatenation reference number. Three additional michael@0: * attributes `segmentMaxSeq`, `receivedSegments`, `segments` are inserted. michael@0: */ michael@0: _receivedSmsSegmentsMap: null, michael@0: michael@0: /** michael@0: * Helper for processing received multipart SMS. michael@0: * michael@0: * @return null for handled segments, and an object containing full message michael@0: * body/data once all segments are received. michael@0: */ michael@0: _processReceivedSmsSegment: function(aSegment) { michael@0: michael@0: // Directly replace full message body for single SMS. michael@0: if (!(aSegment.segmentMaxSeq && (aSegment.segmentMaxSeq > 1))) { michael@0: if (aSegment.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { michael@0: aSegment.fullData = aSegment.data; michael@0: } else { michael@0: aSegment.fullBody = aSegment.body; michael@0: } michael@0: return aSegment; michael@0: } michael@0: michael@0: // Handle Concatenation for Class 0 SMS michael@0: let hash = aSegment.sender + ":" + michael@0: aSegment.segmentRef + ":" + michael@0: aSegment.segmentMaxSeq; michael@0: let seq = aSegment.segmentSeq; michael@0: michael@0: let options = this._receivedSmsSegmentsMap[hash]; michael@0: if (!options) { michael@0: options = aSegment; michael@0: this._receivedSmsSegmentsMap[hash] = options; michael@0: michael@0: options.receivedSegments = 0; michael@0: options.segments = []; michael@0: } else if (options.segments[seq]) { michael@0: // Duplicated segment? michael@0: if (DEBUG) { michael@0: this.debug("Got duplicated segment no." + seq + michael@0: " of a multipart SMS: " + JSON.stringify(aSegment)); michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: if (options.receivedSegments > 0) { michael@0: // Update received timestamp. michael@0: options.timestamp = aSegment.timestamp; michael@0: } michael@0: michael@0: if (options.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { michael@0: options.segments[seq] = aSegment.data; michael@0: } else { michael@0: options.segments[seq] = aSegment.body; michael@0: } michael@0: options.receivedSegments++; michael@0: michael@0: // The port information is only available in 1st segment for CDMA WAP Push. michael@0: // If the segments of a WAP Push are not received in sequence michael@0: // (e.g., SMS with seq == 1 is not the 1st segment received by the device), michael@0: // we have to retrieve the port information from 1st segment and michael@0: // save it into the cached options. michael@0: if (aSegment.teleservice === RIL.PDU_CDMA_MSG_TELESERIVCIE_ID_WAP michael@0: && seq === 1) { michael@0: if (!options.originatorPort && aSegment.originatorPort) { michael@0: options.originatorPort = aSegment.originatorPort; michael@0: } michael@0: michael@0: if (!options.destinationPort && aSegment.destinationPort) { michael@0: options.destinationPort = aSegment.destinationPort; michael@0: } michael@0: } michael@0: michael@0: if (options.receivedSegments < options.segmentMaxSeq) { michael@0: if (DEBUG) { michael@0: this.debug("Got segment no." + seq + " of a multipart SMS: " + michael@0: JSON.stringify(options)); michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: // Remove from map michael@0: delete this._receivedSmsSegmentsMap[hash]; michael@0: michael@0: // Rebuild full body michael@0: if (options.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { michael@0: // Uint8Array doesn't have `concat`, so we have to merge all segements michael@0: // by hand. michael@0: let fullDataLen = 0; michael@0: for (let i = 1; i <= options.segmentMaxSeq; i++) { michael@0: fullDataLen += options.segments[i].length; michael@0: } michael@0: michael@0: options.fullData = new Uint8Array(fullDataLen); michael@0: for (let d= 0, i = 1; i <= options.segmentMaxSeq; i++) { michael@0: let data = options.segments[i]; michael@0: for (let j = 0; j < data.length; j++) { michael@0: options.fullData[d++] = data[j]; michael@0: } michael@0: } michael@0: } else { michael@0: options.fullBody = options.segments.join(""); michael@0: } michael@0: michael@0: // Remove handy fields after completing the concatenation. michael@0: delete options.receivedSegments; michael@0: delete options.segments; michael@0: michael@0: if (DEBUG) { michael@0: this.debug("Got full multipart SMS: " + JSON.stringify(options)); michael@0: } michael@0: michael@0: return options; michael@0: }, michael@0: michael@0: /** michael@0: * Helper to create Savable SmsSegment. michael@0: */ michael@0: _createSavableSmsSegment: function(aMessage) { michael@0: // We precisely define what data fields to be stored into michael@0: // DB here for better data migration. michael@0: let segment = {}; michael@0: segment.messageType = aMessage.messageType; michael@0: segment.teleservice = aMessage.teleservice; michael@0: segment.SMSC = aMessage.SMSC; michael@0: segment.sentTimestamp = aMessage.sentTimestamp; michael@0: segment.timestamp = Date.now(); michael@0: segment.sender = aMessage.sender; michael@0: segment.pid = aMessage.pid; michael@0: segment.encoding = aMessage.encoding; michael@0: segment.messageClass = aMessage.messageClass; michael@0: segment.iccId = this.getIccId(); michael@0: if (aMessage.header) { michael@0: segment.segmentRef = aMessage.header.segmentRef; michael@0: segment.segmentSeq = aMessage.header.segmentSeq; michael@0: segment.segmentMaxSeq = aMessage.header.segmentMaxSeq; michael@0: segment.originatorPort = aMessage.header.originatorPort; michael@0: segment.destinationPort = aMessage.header.destinationPort; michael@0: } michael@0: segment.mwiPresent = (aMessage.mwi)? true: false; michael@0: segment.mwiDiscard = (segment.mwiPresent)? aMessage.mwi.discard: false; michael@0: segment.mwiMsgCount = (segment.mwiPresent)? aMessage.mwi.msgCount: 0; michael@0: segment.mwiActive = (segment.mwiPresent)? aMessage.mwi.active: false; michael@0: segment.serviceCategory = aMessage.serviceCategory; michael@0: segment.language = aMessage.language; michael@0: segment.data = aMessage.data; michael@0: segment.body = aMessage.body; michael@0: michael@0: return segment; michael@0: }, michael@0: michael@0: /** michael@0: * Helper to purge complete message. michael@0: * michael@0: * We remove unnessary fields defined in _createSavableSmsSegment() after michael@0: * completing the concatenation. michael@0: */ michael@0: _purgeCompleteSmsMessage: function(aMessage) { michael@0: // Purge concatenation info michael@0: delete aMessage.segmentRef; michael@0: delete aMessage.segmentSeq; michael@0: delete aMessage.segmentMaxSeq; michael@0: michael@0: // Purge partial message body michael@0: delete aMessage.data; michael@0: delete aMessage.body; michael@0: }, michael@0: michael@0: /** michael@0: * handle concatenation of received SMS. michael@0: */ michael@0: handleSmsMultipart: function(aMessage) { michael@0: if (DEBUG) this.debug("handleSmsMultipart: " + JSON.stringify(aMessage)); michael@0: michael@0: this._acquireSmsHandledWakeLock(); michael@0: michael@0: let segment = this._createSavableSmsSegment(aMessage); michael@0: michael@0: let isMultipart = (segment.segmentMaxSeq && (segment.segmentMaxSeq > 1)); michael@0: let messageClass = segment.messageClass; michael@0: michael@0: let handleReceivedAndAck = function(aRvOfIncompleteMsg, aCompleteMessage) { michael@0: if (aCompleteMessage) { michael@0: this._purgeCompleteSmsMessage(aCompleteMessage); michael@0: if (this.handleSmsReceived(aCompleteMessage)) { michael@0: this.sendAckSms(Cr.NS_OK, aCompleteMessage); michael@0: } michael@0: // else Ack will be sent after further process in handleSmsReceived. michael@0: } else { michael@0: this.sendAckSms(aRvOfIncompleteMsg, segment); michael@0: } michael@0: }.bind(this); michael@0: michael@0: // No need to access SmsSegmentStore for Class 0 SMS and Single SMS. michael@0: if (!isMultipart || michael@0: (messageClass == RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0])) { michael@0: // `When a mobile terminated message is class 0 and the MS has the michael@0: // capability of displaying short messages, the MS shall display the michael@0: // message immediately and send an acknowledgement to the SC when the michael@0: // message has successfully reached the MS irrespective of whether michael@0: // there is memory available in the (U)SIM or ME. The message shall michael@0: // not be automatically stored in the (U)SIM or ME.` michael@0: // ~ 3GPP 23.038 clause 4 michael@0: michael@0: handleReceivedAndAck(Cr.NS_OK, // ACK OK For Incomplete Class 0 michael@0: this._processReceivedSmsSegment(segment)); michael@0: } else { michael@0: gMobileMessageDatabaseService michael@0: .saveSmsSegment(segment, function notifyResult(aRv, aCompleteMessage) { michael@0: handleReceivedAndAck(aRv, // Ack according to the result after saving michael@0: aCompleteMessage); michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: portAddressedSmsApps: null, michael@0: handleSmsReceived: function(message) { michael@0: if (DEBUG) this.debug("handleSmsReceived: " + JSON.stringify(message)); michael@0: michael@0: if (message.messageType == RIL.PDU_CDMA_MSG_TYPE_BROADCAST) { michael@0: gMessageManager.sendCellBroadcastMessage("RIL:CellBroadcastReceived", michael@0: this.clientId, message); michael@0: return true; michael@0: } michael@0: michael@0: // Dispatch to registered handler if application port addressing is michael@0: // available. Note that the destination port can possibly be zero when michael@0: // representing a UDP/TCP port. michael@0: if (message.destinationPort != null) { michael@0: let handler = this.portAddressedSmsApps[message.destinationPort]; michael@0: if (handler) { michael@0: handler(message); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: if (message.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) { michael@0: // Don't know how to handle binary data yet. michael@0: return true; michael@0: } michael@0: michael@0: message.type = "sms"; michael@0: message.sender = message.sender || null; michael@0: message.receiver = this.getPhoneNumber(); michael@0: message.body = message.fullBody = message.fullBody || null; michael@0: michael@0: if (gSmsService.isSilentNumber(message.sender)) { michael@0: message.id = -1; michael@0: message.threadId = 0; michael@0: message.delivery = DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED; michael@0: message.deliveryStatus = RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS; michael@0: message.read = false; michael@0: michael@0: let domMessage = michael@0: gMobileMessageService.createSmsMessage(message.id, michael@0: message.threadId, michael@0: message.iccId, michael@0: message.delivery, michael@0: message.deliveryStatus, michael@0: message.sender, michael@0: message.receiver, michael@0: message.body, michael@0: message.messageClass, michael@0: message.timestamp, michael@0: message.sentTimestamp, michael@0: 0, michael@0: message.read); michael@0: michael@0: Services.obs.notifyObservers(domMessage, michael@0: kSilentSmsReceivedObserverTopic, michael@0: null); michael@0: return true; michael@0: } michael@0: michael@0: if (message.mwiPresent) { michael@0: let mwi = { michael@0: discard: message.mwiDiscard, michael@0: msgCount: message.mwiMsgCount, michael@0: active: message.mwiActive michael@0: }; michael@0: this.workerMessenger.send("updateMwis", { mwi: mwi }); michael@0: michael@0: mwi.returnNumber = message.sender; michael@0: mwi.returnMessage = message.fullBody; michael@0: gMessageManager.sendVoicemailMessage("RIL:VoicemailNotification", michael@0: this.clientId, mwi); michael@0: michael@0: // Dicarded MWI comes without text body. michael@0: // Hence, we discard it here after notifying the MWI status. michael@0: if (message.mwiDiscard) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: let notifyReceived = function notifyReceived(rv, domMessage) { michael@0: let success = Components.isSuccessCode(rv); michael@0: michael@0: this.sendAckSms(rv, message); michael@0: michael@0: if (!success) { michael@0: // At this point we could send a message to content to notify the user michael@0: // that storing an incoming SMS failed, most likely due to a full disk. michael@0: if (DEBUG) { michael@0: this.debug("Could not store SMS, error code " + rv); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: this.broadcastSmsSystemMessage(kSmsReceivedObserverTopic, domMessage); michael@0: Services.obs.notifyObservers(domMessage, kSmsReceivedObserverTopic, null); michael@0: }.bind(this); michael@0: michael@0: if (message.messageClass != RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0]) { michael@0: gMobileMessageDatabaseService.saveReceivedMessage(message, michael@0: notifyReceived); michael@0: } else { michael@0: message.id = -1; michael@0: message.threadId = 0; michael@0: message.delivery = DOM_MOBILE_MESSAGE_DELIVERY_RECEIVED; michael@0: message.deliveryStatus = RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS; michael@0: message.read = false; michael@0: michael@0: let domMessage = michael@0: gMobileMessageService.createSmsMessage(message.id, michael@0: message.threadId, michael@0: message.iccId, michael@0: message.delivery, michael@0: message.deliveryStatus, michael@0: message.sender, michael@0: message.receiver, michael@0: message.body, michael@0: message.messageClass, michael@0: message.timestamp, michael@0: message.sentTimestamp, michael@0: 0, michael@0: message.read); michael@0: michael@0: notifyReceived(Cr.NS_OK, domMessage); michael@0: } michael@0: michael@0: // SMS ACK will be sent in notifyReceived. Return false here. michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Handle ACK response of received SMS. michael@0: */ michael@0: sendAckSms: function(aRv, aMessage) { michael@0: if (aMessage.messageClass === RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_2]) { michael@0: return; michael@0: } michael@0: michael@0: let result = RIL.PDU_FCS_OK; michael@0: if (!Components.isSuccessCode(aRv)) { michael@0: if (DEBUG) this.debug("Failed to handle received sms: " + aRv); michael@0: result = (aRv === Cr.NS_ERROR_FILE_NO_DEVICE_SPACE) michael@0: ? RIL.PDU_FCS_MEMORY_CAPACITY_EXCEEDED michael@0: : RIL.PDU_FCS_UNSPECIFIED; michael@0: } michael@0: michael@0: this.workerMessenger.send("ackSMS", { result: result }); michael@0: michael@0: }, michael@0: michael@0: /** michael@0: * Set the setting value of "time.clock.automatic-update.available". michael@0: */ michael@0: setClockAutoUpdateAvailable: function(value) { michael@0: gSettingsService.createLock().set(kSettingsClockAutoUpdateAvailable, value, null, michael@0: "fromInternalSetting"); michael@0: }, michael@0: michael@0: /** michael@0: * Set the setting value of "time.timezone.automatic-update.available". michael@0: */ michael@0: setTimezoneAutoUpdateAvailable: function(value) { michael@0: gSettingsService.createLock().set(kSettingsTimezoneAutoUpdateAvailable, value, null, michael@0: "fromInternalSetting"); michael@0: }, michael@0: michael@0: /** michael@0: * Set the system clock by NITZ. michael@0: */ michael@0: setClockByNitz: function(message) { michael@0: // To set the system clock time. Note that there could be a time diff michael@0: // between when the NITZ was received and when the time is actually set. michael@0: gTimeService.set( michael@0: message.networkTimeInMS + (Date.now() - message.receiveTimeInMS)); michael@0: }, michael@0: michael@0: /** michael@0: * Set the system time zone by NITZ. michael@0: */ michael@0: setTimezoneByNitz: function(message) { michael@0: // To set the sytem timezone. Note that we need to convert the time zone michael@0: // value to a UTC repesentation string in the format of "UTC(+/-)hh:mm". michael@0: // Ex, time zone -480 is "UTC+08:00"; time zone 630 is "UTC-10:30". michael@0: // michael@0: // We can unapply the DST correction if we want the raw time zone offset: michael@0: // message.networkTimeZoneInMinutes -= message.networkDSTInMinutes; michael@0: if (message.networkTimeZoneInMinutes != (new Date()).getTimezoneOffset()) { michael@0: let absTimeZoneInMinutes = Math.abs(message.networkTimeZoneInMinutes); michael@0: let timeZoneStr = "UTC"; michael@0: timeZoneStr += (message.networkTimeZoneInMinutes > 0 ? "-" : "+"); michael@0: timeZoneStr += ("0" + Math.floor(absTimeZoneInMinutes / 60)).slice(-2); michael@0: timeZoneStr += ":"; michael@0: timeZoneStr += ("0" + absTimeZoneInMinutes % 60).slice(-2); michael@0: gSettingsService.createLock().set("time.timezone", timeZoneStr, null); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handle the NITZ message. michael@0: */ michael@0: handleNitzTime: function(message) { michael@0: // Got the NITZ info received from the ril_worker. michael@0: this.setClockAutoUpdateAvailable(true); michael@0: this.setTimezoneAutoUpdateAvailable(true); michael@0: michael@0: // Cache the latest NITZ message whenever receiving it. michael@0: this._lastNitzMessage = message; michael@0: michael@0: // Set the received NITZ clock if the setting is enabled. michael@0: if (this._clockAutoUpdateEnabled) { michael@0: this.setClockByNitz(message); michael@0: } michael@0: // Set the received NITZ timezone if the setting is enabled. michael@0: if (this._timezoneAutoUpdateEnabled) { michael@0: this.setTimezoneByNitz(message); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Set the system clock by SNTP. michael@0: */ michael@0: setClockBySntp: function(offset) { michael@0: // Got the SNTP info. michael@0: this.setClockAutoUpdateAvailable(true); michael@0: if (!this._clockAutoUpdateEnabled) { michael@0: return; michael@0: } michael@0: if (this._lastNitzMessage) { michael@0: if (DEBUG) debug("SNTP: NITZ available, discard SNTP"); michael@0: return; michael@0: } michael@0: gTimeService.set(Date.now() + offset); michael@0: }, michael@0: michael@0: handleIccMbdn: function(message) { michael@0: let voicemailInfo = this.voicemailInfo; michael@0: michael@0: voicemailInfo.number = message.number; michael@0: voicemailInfo.displayName = message.alphaId; michael@0: michael@0: gMessageManager.sendVoicemailMessage("RIL:VoicemailInfoChanged", michael@0: this.clientId, voicemailInfo); michael@0: }, michael@0: michael@0: handleIccInfoChange: function(message) { michael@0: let oldSpn = this.rilContext.iccInfo ? this.rilContext.iccInfo.spn : null; michael@0: michael@0: if (!message || !message.iccType) { michael@0: // Card is not detected, clear iccInfo to null. michael@0: this.rilContext.iccInfo = null; michael@0: } else { michael@0: if (!this.rilContext.iccInfo) { michael@0: if (message.iccType === "ruim" || message.iccType === "csim") { michael@0: this.rilContext.iccInfo = new CdmaIccInfo(); michael@0: } else { michael@0: this.rilContext.iccInfo = new GsmIccInfo(); michael@0: } michael@0: } michael@0: michael@0: if (!this.isInfoChanged(message, this.rilContext.iccInfo)) { michael@0: return; michael@0: } michael@0: michael@0: this.updateInfo(message, this.rilContext.iccInfo); michael@0: } michael@0: michael@0: // RIL:IccInfoChanged corresponds to a DOM event that gets fired only michael@0: // when iccInfo has changed. michael@0: gMessageManager.sendIccMessage("RIL:IccInfoChanged", michael@0: this.clientId, michael@0: message.iccType ? message : null); michael@0: michael@0: // Update lastKnownSimMcc. michael@0: if (message.mcc) { michael@0: try { michael@0: Services.prefs.setCharPref("ril.lastKnownSimMcc", michael@0: message.mcc.toString()); michael@0: } catch (e) {} michael@0: } michael@0: michael@0: // Update lastKnownHomeNetwork. michael@0: if (message.mcc && message.mnc) { michael@0: this._lastKnownHomeNetwork = message.mcc + "-" + message.mnc; michael@0: } michael@0: michael@0: // If spn becomes available, we should check roaming again. michael@0: if (!oldSpn && message.spn) { michael@0: let voice = this.rilContext.voice; michael@0: let data = this.rilContext.data; michael@0: let voiceRoaming = voice.roaming; michael@0: let dataRoaming = data.roaming; michael@0: this.checkRoamingBetweenOperators(voice); michael@0: this.checkRoamingBetweenOperators(data); michael@0: if (voiceRoaming != voice.roaming) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:VoiceInfoChanged", michael@0: this.clientId, voice); michael@0: } michael@0: if (dataRoaming != data.roaming) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", michael@0: this.clientId, data); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: handleUSSDReceived: function(ussd) { michael@0: if (DEBUG) this.debug("handleUSSDReceived " + JSON.stringify(ussd)); michael@0: gSystemMessenger.broadcastMessage("ussd-received", ussd); michael@0: gMessageManager.sendMobileConnectionMessage("RIL:USSDReceived", michael@0: this.clientId, ussd); michael@0: }, michael@0: michael@0: handleStkProactiveCommand: function(message) { michael@0: if (DEBUG) this.debug("handleStkProactiveCommand " + JSON.stringify(message)); michael@0: let iccId = this.rilContext.iccInfo && this.rilContext.iccInfo.iccid; michael@0: if (iccId) { michael@0: gSystemMessenger.broadcastMessage("icc-stkcommand", michael@0: {iccId: iccId, michael@0: command: message}); michael@0: } michael@0: gMessageManager.sendIccMessage("RIL:StkCommand", this.clientId, message); michael@0: }, michael@0: michael@0: handleExitEmergencyCbMode: function(message) { michael@0: if (DEBUG) this.debug("handleExitEmergencyCbMode: " + JSON.stringify(message)); michael@0: gMessageManager.sendRequestResults("RIL:ExitEmergencyCbMode", message); michael@0: }, michael@0: michael@0: // nsIObserver michael@0: michael@0: observe: function(subject, topic, data) { michael@0: switch (topic) { michael@0: case kMozSettingsChangedObserverTopic: michael@0: let setting = JSON.parse(data); michael@0: this.handleSettingsChange(setting.key, setting.value, setting.message); michael@0: break; michael@0: case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: michael@0: if (data === kPrefCellBroadcastDisabled) { michael@0: let value = false; michael@0: try { michael@0: value = Services.prefs.getBoolPref(kPrefCellBroadcastDisabled); michael@0: } catch(e) {} michael@0: this.workerMessenger.send("setCellBroadcastDisabled", michael@0: { disabled: value }); michael@0: } michael@0: break; michael@0: case kSysClockChangeObserverTopic: michael@0: let offset = parseInt(data, 10); michael@0: if (this._lastNitzMessage) { michael@0: this._lastNitzMessage.receiveTimeInMS += offset; michael@0: } michael@0: this._sntp.updateOffset(offset); michael@0: break; michael@0: case kNetworkConnStateChangedTopic: michael@0: let network = subject.QueryInterface(Ci.nsINetworkInterface); michael@0: if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { michael@0: return; michael@0: } michael@0: michael@0: // SNTP can only update when we have mobile or Wifi connections. michael@0: if (network.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI && michael@0: network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) { michael@0: return; michael@0: } michael@0: michael@0: // If the network comes from RIL, make sure the RIL service is matched. michael@0: if (subject instanceof Ci.nsIRilNetworkInterface) { michael@0: network = subject.QueryInterface(Ci.nsIRilNetworkInterface); michael@0: if (network.serviceId != this.clientId) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // SNTP won't update unless the SNTP is already expired. michael@0: if (this._sntp.isExpired()) { michael@0: this._sntp.request(); michael@0: } michael@0: break; michael@0: case kNetworkActiveChangedTopic: michael@0: let dataInfo = this.rilContext.data; michael@0: let connected = false; michael@0: if (gNetworkManager.active && michael@0: gNetworkManager.active.type === michael@0: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE && michael@0: gNetworkManager.active.serviceId === this.clientId) { michael@0: connected = true; michael@0: } michael@0: if (dataInfo.connected !== connected) { michael@0: dataInfo.connected = connected; michael@0: gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged", michael@0: this.clientId, dataInfo); michael@0: } michael@0: break; michael@0: case kScreenStateChangedTopic: michael@0: this.workerMessenger.send("setScreenState", { on: (data === "on") }); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: supportedNetworkTypes: null, michael@0: michael@0: // Flag to determine whether to update system clock automatically. It michael@0: // corresponds to the "time.clock.automatic-update.enabled" setting. michael@0: _clockAutoUpdateEnabled: null, michael@0: michael@0: // Flag to determine whether to update system timezone automatically. It michael@0: // corresponds to the "time.clock.automatic-update.enabled" setting. michael@0: _timezoneAutoUpdateEnabled: null, michael@0: michael@0: // Remember the last NITZ message so that we can set the time based on michael@0: // the network immediately when users enable network-based time. michael@0: _lastNitzMessage: null, michael@0: michael@0: // Object that handles SNTP. michael@0: _sntp: null, michael@0: michael@0: // Cell Broadcast settings values. michael@0: _cellBroadcastSearchList: null, michael@0: michael@0: // Operator's mcc-mnc. michael@0: _lastKnownNetwork: null, michael@0: michael@0: // ICC's mcc-mnc. michael@0: _lastKnownHomeNetwork: null, michael@0: michael@0: handleSettingsChange: function(aName, aResult, aMessage) { michael@0: // Don't allow any content processes to modify the setting michael@0: // "time.clock.automatic-update.available" except for the chrome process. michael@0: if (aName === kSettingsClockAutoUpdateAvailable && michael@0: aMessage !== "fromInternalSetting") { michael@0: let isClockAutoUpdateAvailable = this._lastNitzMessage !== null || michael@0: this._sntp.isAvailable(); michael@0: if (aResult !== isClockAutoUpdateAvailable) { michael@0: if (DEBUG) { michael@0: debug("Content processes cannot modify 'time.clock.automatic-update.available'. Restore!"); michael@0: } michael@0: // Restore the setting to the current value. michael@0: this.setClockAutoUpdateAvailable(isClockAutoUpdateAvailable); michael@0: } michael@0: } michael@0: michael@0: // Don't allow any content processes to modify the setting michael@0: // "time.timezone.automatic-update.available" except for the chrome michael@0: // process. michael@0: if (aName === kSettingsTimezoneAutoUpdateAvailable && michael@0: aMessage !== "fromInternalSetting") { michael@0: let isTimezoneAutoUpdateAvailable = this._lastNitzMessage !== null; michael@0: if (aResult !== isTimezoneAutoUpdateAvailable) { michael@0: if (DEBUG) { michael@0: this.debug("Content processes cannot modify 'time.timezone.automatic-update.available'. Restore!"); michael@0: } michael@0: // Restore the setting to the current value. michael@0: this.setTimezoneAutoUpdateAvailable(isTimezoneAutoUpdateAvailable); michael@0: } michael@0: } michael@0: michael@0: this.handle(aName, aResult); michael@0: }, michael@0: michael@0: // nsISettingsServiceCallback michael@0: handle: function(aName, aResult) { michael@0: switch(aName) { michael@0: case kSettingsClockAutoUpdateEnabled: michael@0: this._clockAutoUpdateEnabled = aResult; michael@0: if (!this._clockAutoUpdateEnabled) { michael@0: break; michael@0: } michael@0: michael@0: // Set the latest cached NITZ time if it's available. michael@0: if (this._lastNitzMessage) { michael@0: this.setClockByNitz(this._lastNitzMessage); michael@0: } else if (gNetworkManager.active && gNetworkManager.active.state == michael@0: Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) { michael@0: // Set the latest cached SNTP time if it's available. michael@0: if (!this._sntp.isExpired()) { michael@0: this.setClockBySntp(this._sntp.getOffset()); michael@0: } else { michael@0: // Or refresh the SNTP. michael@0: this._sntp.request(); michael@0: } michael@0: } else { michael@0: // Set a sane minimum time. michael@0: let buildTime = libcutils.property_get("ro.build.date.utc", "0") * 1000; michael@0: let file = FileUtils.File("/system/b2g/b2g"); michael@0: if (file.lastModifiedTime > buildTime) { michael@0: buildTime = file.lastModifiedTime; michael@0: } michael@0: if (buildTime > Date.now()) { michael@0: gTimeService.set(buildTime); michael@0: } michael@0: } michael@0: break; michael@0: case kSettingsTimezoneAutoUpdateEnabled: michael@0: this._timezoneAutoUpdateEnabled = aResult; michael@0: michael@0: if (this._timezoneAutoUpdateEnabled) { michael@0: // Apply the latest cached NITZ for timezone if it's available. michael@0: if (this._timezoneAutoUpdateEnabled && this._lastNitzMessage) { michael@0: this.setTimezoneByNitz(this._lastNitzMessage); michael@0: } michael@0: } michael@0: break; michael@0: case kSettingsCellBroadcastSearchList: michael@0: if (DEBUG) { michael@0: this.debug("'" + kSettingsCellBroadcastSearchList + michael@0: "' is now " + JSON.stringify(aResult)); michael@0: } michael@0: // TODO: Set searchlist for Multi-SIM. See Bug 921326. michael@0: let result = Array.isArray(aResult) ? aResult[0] : aResult; michael@0: this.setCellBroadcastSearchList(result); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: handleError: function(aErrorMessage) { michael@0: if (DEBUG) { michael@0: this.debug("There was an error while reading RIL settings."); michael@0: } michael@0: }, michael@0: michael@0: // nsIRadioInterface michael@0: michael@0: rilContext: null, michael@0: michael@0: // Handle phone functions of nsIRILContentHelper michael@0: michael@0: _sendCfStateChanged: function(message) { michael@0: gMessageManager.sendMobileConnectionMessage("RIL:CfStateChanged", michael@0: this.clientId, message); michael@0: }, michael@0: michael@0: _updateCallingLineIdRestrictionPref: function(mode) { michael@0: try { michael@0: Services.prefs.setIntPref(kPrefClirModePreference, mode); michael@0: Services.prefs.savePrefFile(null); michael@0: if (DEBUG) { michael@0: this.debug(kPrefClirModePreference + " pref is now " + mode); michael@0: } michael@0: } catch (e) {} michael@0: }, michael@0: michael@0: sendMMI: function(target, message) { michael@0: if (DEBUG) this.debug("SendMMI " + JSON.stringify(message)); michael@0: this.workerMessenger.send("sendMMI", message, (function(response) { michael@0: if (response.isSetCallForward) { michael@0: this._sendCfStateChanged(response); michael@0: } else if (response.isSetCLIR && response.success) { michael@0: this._updateCallingLineIdRestrictionPref(response.clirMode); michael@0: } michael@0: michael@0: target.sendAsyncMessage("RIL:SendMMI", { michael@0: clientId: this.clientId, michael@0: data: response michael@0: }); michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: setCallForwardingOptions: function(target, message) { michael@0: if (DEBUG) this.debug("setCallForwardingOptions: " + JSON.stringify(message)); michael@0: message.serviceClass = RIL.ICC_SERVICE_CLASS_VOICE; michael@0: this.workerMessenger.send("setCallForward", message, (function(response) { michael@0: this._sendCfStateChanged(response); michael@0: target.sendAsyncMessage("RIL:SetCallForwardingOptions", { michael@0: clientId: this.clientId, michael@0: data: response michael@0: }); michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: setCallingLineIdRestriction: function(target, message) { michael@0: if (DEBUG) { michael@0: this.debug("setCallingLineIdRestriction: " + JSON.stringify(message)); michael@0: } michael@0: this.workerMessenger.send("setCLIR", message, (function(response) { michael@0: if (response.success) { michael@0: this._updateCallingLineIdRestrictionPref(response.clirMode); michael@0: } michael@0: target.sendAsyncMessage("RIL:SetCallingLineIdRestriction", { michael@0: clientId: this.clientId, michael@0: data: response michael@0: }); michael@0: return false; michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: isValidStateForSetRadioEnabled: function() { michael@0: let state = this.rilContext.detailedRadioState; michael@0: return state == RIL.GECKO_DETAILED_RADIOSTATE_ENABLED || michael@0: state == RIL.GECKO_DETAILED_RADIOSTATE_DISABLED; michael@0: }, michael@0: michael@0: isDummyForSetRadioEnabled: function(message) { michael@0: let state = this.rilContext.detailedRadioState; michael@0: return (state == RIL.GECKO_DETAILED_RADIOSTATE_ENABLED && message.enabled) || michael@0: (state == RIL.GECKO_DETAILED_RADIOSTATE_DISABLED && !message.enabled); michael@0: }, michael@0: michael@0: setRadioEnabledResponse: function(target, message, errorMsg) { michael@0: if (errorMsg) { michael@0: message.errorMsg = errorMsg; michael@0: } michael@0: michael@0: target.sendAsyncMessage("RIL:SetRadioEnabled", { michael@0: clientId: this.clientId, michael@0: data: message michael@0: }); michael@0: }, michael@0: michael@0: setRadioEnabled: function(target, message) { michael@0: if (DEBUG) { michael@0: this.debug("setRadioEnabled: " + JSON.stringify(message)); michael@0: } michael@0: michael@0: if (!this.isValidStateForSetRadioEnabled()) { michael@0: this.setRadioEnabledResponse(target, message, "InvalidStateError"); michael@0: return; michael@0: } michael@0: michael@0: if (this.isDummyForSetRadioEnabled(message)) { michael@0: this.setRadioEnabledResponse(target, message); michael@0: return; michael@0: } michael@0: michael@0: let callback = (function(response) { michael@0: if (response.errorMsg) { michael@0: // Request fails. Rollback to the original radiostate. michael@0: let state = message.enabled ? RIL.GECKO_DETAILED_RADIOSTATE_DISABLED michael@0: : RIL.GECKO_DETAILED_RADIOSTATE_ENABLED; michael@0: this.handleDetailedRadioStateChanged(state); michael@0: } michael@0: this.setRadioEnabledResponse(target, response); michael@0: return false; michael@0: }).bind(this); michael@0: michael@0: this.setRadioEnabledInternal(message, callback); michael@0: }, michael@0: michael@0: setRadioEnabledInternal: function(message, callback) { michael@0: let state = message.enabled ? RIL.GECKO_DETAILED_RADIOSTATE_ENABLING michael@0: : RIL.GECKO_DETAILED_RADIOSTATE_DISABLING; michael@0: this.handleDetailedRadioStateChanged(state); michael@0: this.workerMessenger.send("setRadioEnabled", message, callback); michael@0: }, michael@0: michael@0: /** michael@0: * List of tuples of national language identifier pairs. michael@0: * michael@0: * TODO: Support static/runtime settings, see bug 733331. michael@0: */ michael@0: enabledGsmTableTuples: [ michael@0: [RIL.PDU_NL_IDENTIFIER_DEFAULT, RIL.PDU_NL_IDENTIFIER_DEFAULT], michael@0: ], michael@0: michael@0: /** michael@0: * Use 16-bit reference number for concatenated outgoint messages. michael@0: * michael@0: * TODO: Support static/runtime settings, see bug 733331. michael@0: */ michael@0: segmentRef16Bit: false, michael@0: michael@0: /** michael@0: * Get valid SMS concatenation reference number. michael@0: */ michael@0: _segmentRef: 0, michael@0: get nextSegmentRef() { michael@0: let ref = this._segmentRef++; michael@0: michael@0: this._segmentRef %= (this.segmentRef16Bit ? 65535 : 255); michael@0: michael@0: // 0 is not a valid SMS concatenation reference number. michael@0: return ref + 1; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate encoded length using specified locking/single shift table michael@0: * michael@0: * @param message michael@0: * message string to be encoded. michael@0: * @param langTable michael@0: * locking shift table string. michael@0: * @param langShiftTable michael@0: * single shift table string. michael@0: * @param strict7BitEncoding michael@0: * Optional. Enable Latin characters replacement with corresponding michael@0: * ones in GSM SMS 7-bit default alphabet. michael@0: * michael@0: * @return encoded length in septets. michael@0: * michael@0: * @note that the algorithm used in this function must match exactly with michael@0: * GsmPDUHelper#writeStringAsSeptets. michael@0: */ michael@0: _countGsm7BitSeptets: function(message, langTable, langShiftTable, strict7BitEncoding) { michael@0: let length = 0; michael@0: for (let msgIndex = 0; msgIndex < message.length; msgIndex++) { michael@0: let c = message.charAt(msgIndex); michael@0: if (strict7BitEncoding) { michael@0: c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c; michael@0: } michael@0: michael@0: let septet = langTable.indexOf(c); michael@0: michael@0: // According to 3GPP TS 23.038, section 6.1.1 General notes, "The michael@0: // characters marked '1)' are not used but are displayed as a space." michael@0: if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) { michael@0: continue; michael@0: } michael@0: michael@0: if (septet >= 0) { michael@0: length++; michael@0: continue; michael@0: } michael@0: michael@0: septet = langShiftTable.indexOf(c); michael@0: if (septet < 0) { michael@0: if (!strict7BitEncoding) { michael@0: return -1; michael@0: } michael@0: michael@0: // Bug 816082, when strict7BitEncoding is enabled, we should replace michael@0: // characters that can't be encoded with GSM 7-Bit alphabets with '*'. michael@0: c = "*"; michael@0: if (langTable.indexOf(c) >= 0) { michael@0: length++; michael@0: } else if (langShiftTable.indexOf(c) >= 0) { michael@0: length += 2; michael@0: } else { michael@0: // We can't even encode a '*' character with current configuration. michael@0: return -1; michael@0: } michael@0: michael@0: continue; michael@0: } michael@0: michael@0: // According to 3GPP TS 23.038 B.2, "This code represents a control michael@0: // character and therefore must not be used for language specific michael@0: // characters." michael@0: if (septet == RIL.PDU_NL_RESERVED_CONTROL) { michael@0: continue; michael@0: } michael@0: michael@0: // The character is not found in locking shfit table, but could be michael@0: // encoded as with single shift table. Note that it's michael@0: // still possible for septet to has the value of PDU_NL_EXTENDED_ESCAPE, michael@0: // but we can display it as a space in this case as said in previous michael@0: // comment. michael@0: length += 2; michael@0: } michael@0: michael@0: return length; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate user data length of specified message string encoded in GSM 7Bit michael@0: * alphabets. michael@0: * michael@0: * @param message michael@0: * a message string to be encoded. michael@0: * @param strict7BitEncoding michael@0: * Optional. Enable Latin characters replacement with corresponding michael@0: * ones in GSM SMS 7-bit default alphabet. michael@0: * michael@0: * @return null or an options object with attributes `dcs`, michael@0: * `userDataHeaderLength`, `encodedFullBodyLength`, `langIndex`, michael@0: * `langShiftIndex`, `segmentMaxSeq` set. michael@0: * michael@0: * @see #_calculateUserDataLength(). michael@0: */ michael@0: _calculateUserDataLength7Bit: function(message, strict7BitEncoding) { michael@0: let options = null; michael@0: let minUserDataSeptets = Number.MAX_VALUE; michael@0: for (let i = 0; i < this.enabledGsmTableTuples.length; i++) { michael@0: let [langIndex, langShiftIndex] = this.enabledGsmTableTuples[i]; michael@0: michael@0: const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; michael@0: const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; michael@0: michael@0: let bodySeptets = this._countGsm7BitSeptets(message, michael@0: langTable, michael@0: langShiftTable, michael@0: strict7BitEncoding); michael@0: if (bodySeptets < 0) { michael@0: continue; michael@0: } michael@0: michael@0: let headerLen = 0; michael@0: if (langIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) { michael@0: headerLen += 3; // IEI + len + langIndex michael@0: } michael@0: if (langShiftIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) { michael@0: headerLen += 3; // IEI + len + langShiftIndex michael@0: } michael@0: michael@0: // Calculate full user data length, note the extra byte is for header len michael@0: let headerSeptets = Math.ceil((headerLen ? headerLen + 1 : 0) * 8 / 7); michael@0: let segmentSeptets = RIL.PDU_MAX_USER_DATA_7BIT; michael@0: if ((bodySeptets + headerSeptets) > segmentSeptets) { michael@0: headerLen += this.segmentRef16Bit ? 6 : 5; michael@0: headerSeptets = Math.ceil((headerLen + 1) * 8 / 7); michael@0: segmentSeptets -= headerSeptets; michael@0: } michael@0: michael@0: let segments = Math.ceil(bodySeptets / segmentSeptets); michael@0: let userDataSeptets = bodySeptets + headerSeptets * segments; michael@0: if (userDataSeptets >= minUserDataSeptets) { michael@0: continue; michael@0: } michael@0: michael@0: minUserDataSeptets = userDataSeptets; michael@0: michael@0: options = { michael@0: dcs: RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET, michael@0: encodedFullBodyLength: bodySeptets, michael@0: userDataHeaderLength: headerLen, michael@0: langIndex: langIndex, michael@0: langShiftIndex: langShiftIndex, michael@0: segmentMaxSeq: segments, michael@0: segmentChars: segmentSeptets, michael@0: }; michael@0: } michael@0: michael@0: return options; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate user data length of specified message string encoded in UCS2. michael@0: * michael@0: * @param message michael@0: * a message string to be encoded. michael@0: * michael@0: * @return an options object with attributes `dcs`, `userDataHeaderLength`, michael@0: * `encodedFullBodyLength`, `segmentMaxSeq` set. michael@0: * michael@0: * @see #_calculateUserDataLength(). michael@0: */ michael@0: _calculateUserDataLengthUCS2: function(message) { michael@0: let bodyChars = message.length; michael@0: let headerLen = 0; michael@0: let headerChars = Math.ceil((headerLen ? headerLen + 1 : 0) / 2); michael@0: let segmentChars = RIL.PDU_MAX_USER_DATA_UCS2; michael@0: if ((bodyChars + headerChars) > segmentChars) { michael@0: headerLen += this.segmentRef16Bit ? 6 : 5; michael@0: headerChars = Math.ceil((headerLen + 1) / 2); michael@0: segmentChars -= headerChars; michael@0: } michael@0: michael@0: let segments = Math.ceil(bodyChars / segmentChars); michael@0: michael@0: return { michael@0: dcs: RIL.PDU_DCS_MSG_CODING_16BITS_ALPHABET, michael@0: encodedFullBodyLength: bodyChars * 2, michael@0: userDataHeaderLength: headerLen, michael@0: segmentMaxSeq: segments, michael@0: segmentChars: segmentChars, michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate user data length and its encoding. michael@0: * michael@0: * @param message michael@0: * a message string to be encoded. michael@0: * @param strict7BitEncoding michael@0: * Optional. Enable Latin characters replacement with corresponding michael@0: * ones in GSM SMS 7-bit default alphabet. michael@0: * michael@0: * @return an options object with some or all of following attributes set: michael@0: * michael@0: * @param dcs michael@0: * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET michael@0: * constants. michael@0: * @param userDataHeaderLength michael@0: * Length of embedded user data header, in bytes. The whole header michael@0: * size will be userDataHeaderLength + 1; 0 for no header. michael@0: * @param encodedFullBodyLength michael@0: * Length of the message body when encoded with the given DCS. For michael@0: * UCS2, in bytes; for 7-bit, in septets. michael@0: * @param langIndex michael@0: * Table index used for normal 7-bit encoded character lookup. michael@0: * @param langShiftIndex michael@0: * Table index used for escaped 7-bit encoded character lookup. michael@0: * @param segmentMaxSeq michael@0: * Max sequence number of a multi-part messages, or 1 for single one. michael@0: * This number might not be accurate for a multi-part message until michael@0: * it's processed by #_fragmentText() again. michael@0: */ michael@0: _calculateUserDataLength: function(message, strict7BitEncoding) { michael@0: let options = this._calculateUserDataLength7Bit(message, strict7BitEncoding); michael@0: if (!options) { michael@0: options = this._calculateUserDataLengthUCS2(message); michael@0: } michael@0: michael@0: if (DEBUG) this.debug("_calculateUserDataLength: " + JSON.stringify(options)); michael@0: return options; michael@0: }, michael@0: michael@0: /** michael@0: * Fragment GSM 7-Bit encodable string for transmission. michael@0: * michael@0: * @param text michael@0: * text string to be fragmented. michael@0: * @param langTable michael@0: * locking shift table string. michael@0: * @param langShiftTable michael@0: * single shift table string. michael@0: * @param segmentSeptets michael@0: * Number of available spetets per segment. michael@0: * @param strict7BitEncoding michael@0: * Optional. Enable Latin characters replacement with corresponding michael@0: * ones in GSM SMS 7-bit default alphabet. michael@0: * michael@0: * @return an array of objects. See #_fragmentText() for detailed definition. michael@0: */ michael@0: _fragmentText7Bit: function(text, langTable, langShiftTable, segmentSeptets, strict7BitEncoding) { michael@0: let ret = []; michael@0: let body = "", len = 0; michael@0: // If the message is empty, we only push the empty message to ret. michael@0: if (text.length === 0) { michael@0: ret.push({ michael@0: body: text, michael@0: encodedBodyLength: text.length, michael@0: }); michael@0: return ret; michael@0: } michael@0: michael@0: for (let i = 0, inc = 0; i < text.length; i++) { michael@0: let c = text.charAt(i); michael@0: if (strict7BitEncoding) { michael@0: c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c; michael@0: } michael@0: michael@0: let septet = langTable.indexOf(c); michael@0: if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) { michael@0: continue; michael@0: } michael@0: michael@0: if (septet >= 0) { michael@0: inc = 1; michael@0: } else { michael@0: septet = langShiftTable.indexOf(c); michael@0: if (septet == RIL.PDU_NL_RESERVED_CONTROL) { michael@0: continue; michael@0: } michael@0: michael@0: inc = 2; michael@0: if (septet < 0) { michael@0: if (!strict7BitEncoding) { michael@0: throw new Error("Given text cannot be encoded with GSM 7-bit Alphabet!"); michael@0: } michael@0: michael@0: // Bug 816082, when strict7BitEncoding is enabled, we should replace michael@0: // characters that can't be encoded with GSM 7-Bit alphabets with '*'. michael@0: c = "*"; michael@0: if (langTable.indexOf(c) >= 0) { michael@0: inc = 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if ((len + inc) > segmentSeptets) { michael@0: ret.push({ michael@0: body: body, michael@0: encodedBodyLength: len, michael@0: }); michael@0: body = c; michael@0: len = inc; michael@0: } else { michael@0: body += c; michael@0: len += inc; michael@0: } michael@0: } michael@0: michael@0: if (len) { michael@0: ret.push({ michael@0: body: body, michael@0: encodedBodyLength: len, michael@0: }); michael@0: } michael@0: michael@0: return ret; michael@0: }, michael@0: michael@0: /** michael@0: * Fragment UCS2 encodable string for transmission. michael@0: * michael@0: * @param text michael@0: * text string to be fragmented. michael@0: * @param segmentChars michael@0: * Number of available characters per segment. michael@0: * michael@0: * @return an array of objects. See #_fragmentText() for detailed definition. michael@0: */ michael@0: _fragmentTextUCS2: function(text, segmentChars) { michael@0: let ret = []; michael@0: // If the message is empty, we only push the empty message to ret. michael@0: if (text.length === 0) { michael@0: ret.push({ michael@0: body: text, michael@0: encodedBodyLength: text.length, michael@0: }); michael@0: return ret; michael@0: } michael@0: michael@0: for (let offset = 0; offset < text.length; offset += segmentChars) { michael@0: let str = text.substr(offset, segmentChars); michael@0: ret.push({ michael@0: body: str, michael@0: encodedBodyLength: str.length * 2, michael@0: }); michael@0: } michael@0: michael@0: return ret; michael@0: }, michael@0: michael@0: /** michael@0: * Fragment string for transmission. michael@0: * michael@0: * Fragment input text string into an array of objects that contains michael@0: * attributes `body`, substring for this segment, `encodedBodyLength`, michael@0: * length of the encoded segment body in septets. michael@0: * michael@0: * @param text michael@0: * Text string to be fragmented. michael@0: * @param options michael@0: * Optional pre-calculated option object. The output array will be michael@0: * stored at options.segments if there are multiple segments. michael@0: * @param strict7BitEncoding michael@0: * Optional. Enable Latin characters replacement with corresponding michael@0: * ones in GSM SMS 7-bit default alphabet. michael@0: * michael@0: * @return Populated options object. michael@0: */ michael@0: _fragmentText: function(text, options, strict7BitEncoding) { michael@0: if (!options) { michael@0: options = this._calculateUserDataLength(text, strict7BitEncoding); michael@0: } michael@0: michael@0: if (options.dcs == RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET) { michael@0: const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[options.langIndex]; michael@0: const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[options.langShiftIndex]; michael@0: options.segments = this._fragmentText7Bit(text, michael@0: langTable, langShiftTable, michael@0: options.segmentChars, michael@0: strict7BitEncoding); michael@0: } else { michael@0: options.segments = this._fragmentTextUCS2(text, michael@0: options.segmentChars); michael@0: } michael@0: michael@0: // Re-sync options.segmentMaxSeq with actual length of returning array. michael@0: options.segmentMaxSeq = options.segments.length; michael@0: michael@0: return options; michael@0: }, michael@0: michael@0: getSegmentInfoForText: function(text, request) { michael@0: let strict7BitEncoding; michael@0: try { michael@0: strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding"); michael@0: } catch (e) { michael@0: strict7BitEncoding = false; michael@0: } michael@0: michael@0: let options = this._fragmentText(text, null, strict7BitEncoding); michael@0: let charsInLastSegment; michael@0: if (options.segmentMaxSeq) { michael@0: let lastSegment = options.segments[options.segmentMaxSeq - 1]; michael@0: charsInLastSegment = lastSegment.encodedBodyLength; michael@0: if (options.dcs == RIL.PDU_DCS_MSG_CODING_16BITS_ALPHABET) { michael@0: // In UCS2 encoding, encodedBodyLength is in octets. michael@0: charsInLastSegment /= 2; michael@0: } michael@0: } else { michael@0: charsInLastSegment = 0; michael@0: } michael@0: michael@0: let result = gMobileMessageService michael@0: .createSmsSegmentInfo(options.segmentMaxSeq, michael@0: options.segmentChars, michael@0: options.segmentChars - charsInLastSegment); michael@0: request.notifySegmentInfoForTextGot(result); michael@0: }, michael@0: michael@0: getSmscAddress: function(request) { michael@0: this.workerMessenger.send("getSmscAddress", michael@0: null, michael@0: (function(response) { michael@0: if (!response.errorMsg) { michael@0: request.notifyGetSmscAddress(response.smscAddress); michael@0: } else { michael@0: request.notifyGetSmscAddressFailed(response.errorMsg); michael@0: } michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: sendSMS: function(number, message, silent, request) { michael@0: let strict7BitEncoding; michael@0: try { michael@0: strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding"); michael@0: } catch (e) { michael@0: strict7BitEncoding = false; michael@0: } michael@0: michael@0: let options = this._fragmentText(message, null, strict7BitEncoding); michael@0: options.number = PhoneNumberUtils.normalize(number); michael@0: let requestStatusReport; michael@0: try { michael@0: requestStatusReport = michael@0: Services.prefs.getBoolPref("dom.sms.requestStatusReport"); michael@0: } catch (e) { michael@0: requestStatusReport = true; michael@0: } michael@0: options.requestStatusReport = requestStatusReport && !silent; michael@0: if (options.segmentMaxSeq > 1) { michael@0: options.segmentRef16Bit = this.segmentRef16Bit; michael@0: options.segmentRef = this.nextSegmentRef; michael@0: } michael@0: michael@0: let notifyResult = (function notifyResult(rv, domMessage) { michael@0: if (!Components.isSuccessCode(rv)) { michael@0: if (DEBUG) this.debug("Error! Fail to save sending message! rv = " + rv); michael@0: request.notifySendMessageFailed( michael@0: gMobileMessageDatabaseService.translateCrErrorToMessageCallbackError(rv)); michael@0: Services.obs.notifyObservers(domMessage, kSmsFailedObserverTopic, null); michael@0: return; michael@0: } michael@0: michael@0: if (!silent) { michael@0: Services.obs.notifyObservers(domMessage, kSmsSendingObserverTopic, null); michael@0: } michael@0: michael@0: // If the radio is disabled or the SIM card is not ready, just directly michael@0: // return with the corresponding error code. michael@0: let errorCode; michael@0: if (!PhoneNumberUtils.isPlainPhoneNumber(options.number)) { michael@0: if (DEBUG) this.debug("Error! Address is invalid when sending SMS: " + michael@0: options.number); michael@0: errorCode = Ci.nsIMobileMessageCallback.INVALID_ADDRESS_ERROR; michael@0: } else if (this.rilContext.detailedRadioState == michael@0: RIL.GECKO_DETAILED_RADIOSTATE_DISABLED) { michael@0: if (DEBUG) this.debug("Error! Radio is disabled when sending SMS."); michael@0: errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR; michael@0: } else if (this.rilContext.cardState != "ready") { michael@0: if (DEBUG) this.debug("Error! SIM card is not ready when sending SMS."); michael@0: errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR; michael@0: } michael@0: if (errorCode) { michael@0: if (silent) { michael@0: request.notifySendMessageFailed(errorCode); michael@0: return; michael@0: } michael@0: michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryByMessageId(domMessage.id, michael@0: null, michael@0: DOM_MOBILE_MESSAGE_DELIVERY_ERROR, michael@0: RIL.GECKO_SMS_DELIVERY_STATUS_ERROR, michael@0: null, michael@0: function notifyResult(rv, domMessage) { michael@0: // TODO bug 832140 handle !Components.isSuccessCode(rv) michael@0: request.notifySendMessageFailed(errorCode); michael@0: Services.obs.notifyObservers(domMessage, kSmsFailedObserverTopic, null); michael@0: }); michael@0: return; michael@0: } michael@0: michael@0: // Keep current SMS message info for sent/delivered notifications michael@0: let context = { michael@0: request: request, michael@0: sms: domMessage, michael@0: requestStatusReport: options.requestStatusReport, michael@0: silent: silent michael@0: }; michael@0: michael@0: // This is the entry point starting to send SMS. michael@0: this.workerMessenger.send("sendSMS", options, michael@0: (function(context, response) { michael@0: if (response.errorMsg) { michael@0: // Failed to send SMS out. michael@0: let error = Ci.nsIMobileMessageCallback.UNKNOWN_ERROR; michael@0: switch (response.errorMsg) { michael@0: case RIL.ERROR_RADIO_NOT_AVAILABLE: michael@0: error = Ci.nsIMobileMessageCallback.NO_SIGNAL_ERROR; michael@0: break; michael@0: case RIL.ERROR_FDN_CHECK_FAILURE: michael@0: error = Ci.nsIMobileMessageCallback.FDN_CHECK_ERROR; michael@0: break; michael@0: } michael@0: michael@0: if (context.silent) { michael@0: context.request.notifySendMessageFailed(error); michael@0: return false; michael@0: } michael@0: michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryByMessageId(context.sms.id, michael@0: null, michael@0: DOM_MOBILE_MESSAGE_DELIVERY_ERROR, michael@0: RIL.GECKO_SMS_DELIVERY_STATUS_ERROR, michael@0: null, michael@0: function notifyResult(rv, domMessage) { michael@0: // TODO bug 832140 handle !Components.isSuccessCode(rv) michael@0: context.request.notifySendMessageFailed(error); michael@0: Services.obs.notifyObservers(domMessage, kSmsFailedObserverTopic, null); michael@0: }); michael@0: return false; michael@0: } // End of send failure. michael@0: michael@0: if (response.deliveryStatus) { michael@0: // Message delivery. michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryByMessageId(context.sms.id, michael@0: null, michael@0: context.sms.delivery, michael@0: response.deliveryStatus, michael@0: null, michael@0: (function notifyResult(rv, domMessage) { michael@0: // TODO bug 832140 handle !Components.isSuccessCode(rv) michael@0: michael@0: let topic = (response.deliveryStatus == michael@0: RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS) michael@0: ? kSmsDeliverySuccessObserverTopic michael@0: : kSmsDeliveryErrorObserverTopic; michael@0: michael@0: // Broadcasting a "sms-delivery-success" system message to open apps. michael@0: if (topic == kSmsDeliverySuccessObserverTopic) { michael@0: this.broadcastSmsSystemMessage(topic, domMessage); michael@0: } michael@0: michael@0: // Notifying observers the delivery status is updated. michael@0: Services.obs.notifyObservers(domMessage, topic, null); michael@0: }).bind(this)); michael@0: michael@0: // Send transaction has ended completely. michael@0: return false; michael@0: } // End of message delivery. michael@0: michael@0: // Message sent. michael@0: if (context.silent) { michael@0: // There is no way to modify nsIDOMMozSmsMessage attributes as they michael@0: // are read only so we just create a new sms instance to send along michael@0: // with the notification. michael@0: let sms = context.sms; michael@0: context.request.notifyMessageSent( michael@0: gMobileMessageService.createSmsMessage(sms.id, michael@0: sms.threadId, michael@0: sms.iccId, michael@0: DOM_MOBILE_MESSAGE_DELIVERY_SENT, michael@0: sms.deliveryStatus, michael@0: sms.sender, michael@0: sms.receiver, michael@0: sms.body, michael@0: sms.messageClass, michael@0: sms.timestamp, michael@0: Date.now(), michael@0: 0, michael@0: sms.read)); michael@0: // We don't wait for SMS-DELIVER-REPORT for silent one. michael@0: return false; michael@0: } michael@0: michael@0: gMobileMessageDatabaseService michael@0: .setMessageDeliveryByMessageId(context.sms.id, michael@0: null, michael@0: DOM_MOBILE_MESSAGE_DELIVERY_SENT, michael@0: context.sms.deliveryStatus, michael@0: null, michael@0: (function notifyResult(rv, domMessage) { michael@0: // TODO bug 832140 handle !Components.isSuccessCode(rv) michael@0: michael@0: if (context.requestStatusReport) { michael@0: context.sms = domMessage; michael@0: } michael@0: michael@0: this.broadcastSmsSystemMessage(kSmsSentObserverTopic, domMessage); michael@0: context.request.notifyMessageSent(domMessage); michael@0: Services.obs.notifyObservers(domMessage, kSmsSentObserverTopic, null); michael@0: }).bind(this)); michael@0: michael@0: // Only keep current context if we have requested for delivery report. michael@0: return context.requestStatusReport; michael@0: }).bind(this, context)); // End of |workerMessenger.send| callback. michael@0: }).bind(this); // End of DB saveSendingMessage callback. michael@0: michael@0: let sendingMessage = { michael@0: type: "sms", michael@0: sender: this.getPhoneNumber(), michael@0: receiver: number, michael@0: body: message, michael@0: deliveryStatusRequested: options.requestStatusReport, michael@0: timestamp: Date.now(), michael@0: iccId: this.getIccId() michael@0: }; michael@0: michael@0: if (silent) { michael@0: let delivery = DOM_MOBILE_MESSAGE_DELIVERY_SENDING; michael@0: let deliveryStatus = RIL.GECKO_SMS_DELIVERY_STATUS_PENDING; michael@0: let domMessage = michael@0: gMobileMessageService.createSmsMessage(-1, // id michael@0: 0, // threadId michael@0: sendingMessage.iccId, michael@0: delivery, michael@0: deliveryStatus, michael@0: sendingMessage.sender, michael@0: sendingMessage.receiver, michael@0: sendingMessage.body, michael@0: "normal", // message class michael@0: sendingMessage.timestamp, michael@0: 0, michael@0: 0, michael@0: false); michael@0: notifyResult(Cr.NS_OK, domMessage); michael@0: return; michael@0: } michael@0: michael@0: let id = gMobileMessageDatabaseService.saveSendingMessage( michael@0: sendingMessage, notifyResult); michael@0: }, michael@0: michael@0: // TODO: Bug 928861 - B2G NetworkManager: Provide a more generic function michael@0: // for connecting michael@0: setupDataCallByType: function(apntype) { michael@0: let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); michael@0: connHandler.setupDataCallByType(apntype); michael@0: }, michael@0: michael@0: // TODO: Bug 928861 - B2G NetworkManager: Provide a more generic function michael@0: // for connecting michael@0: deactivateDataCallByType: function(apntype) { michael@0: let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); michael@0: connHandler.deactivateDataCallByType(apntype); michael@0: }, michael@0: michael@0: // TODO: Bug 904514 - [meta] NetworkManager enhancement michael@0: getDataCallStateByType: function(apntype) { michael@0: let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId); michael@0: return connHandler.getDataCallStateByType(apntype); michael@0: }, michael@0: michael@0: setupDataCall: function(radioTech, apn, user, passwd, chappap, pdptype) { michael@0: this.workerMessenger.send("setupDataCall", { radioTech: radioTech, michael@0: apn: apn, michael@0: user: user, michael@0: passwd: passwd, michael@0: chappap: chappap, michael@0: pdptype: pdptype }); michael@0: }, michael@0: michael@0: deactivateDataCall: function(cid, reason) { michael@0: this.workerMessenger.send("deactivateDataCall", { cid: cid, michael@0: reason: reason }); michael@0: }, michael@0: michael@0: sendWorkerMessage: function(rilMessageType, message, callback) { michael@0: if (callback) { michael@0: this.workerMessenger.send(rilMessageType, message, function(response) { michael@0: return callback.handleResponse(response); michael@0: }); michael@0: } else { michael@0: this.workerMessenger.send(rilMessageType, message); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function RILNetworkInterface(dataConnectionHandler, apnSetting) { michael@0: this.dataConnectionHandler = dataConnectionHandler; michael@0: this.apnSetting = apnSetting; michael@0: this.connectedTypes = []; michael@0: michael@0: this.ips = []; michael@0: this.prefixLengths = []; michael@0: this.dnses = []; michael@0: this.gateways = []; michael@0: } michael@0: michael@0: RILNetworkInterface.prototype = { michael@0: classID: RILNETWORKINTERFACE_CID, michael@0: classInfo: XPCOMUtils.generateCI({classID: RILNETWORKINTERFACE_CID, michael@0: classDescription: "RILNetworkInterface", michael@0: interfaces: [Ci.nsINetworkInterface, michael@0: Ci.nsIRilNetworkInterface]}), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface, michael@0: Ci.nsIRilNetworkInterface]), michael@0: michael@0: // nsINetworkInterface michael@0: michael@0: NETWORK_STATE_UNKNOWN: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN, michael@0: NETWORK_STATE_CONNECTING: Ci.nsINetworkInterface.CONNECTING, michael@0: NETWORK_STATE_CONNECTED: Ci.nsINetworkInterface.CONNECTED, michael@0: NETWORK_STATE_DISCONNECTING: Ci.nsINetworkInterface.DISCONNECTING, michael@0: NETWORK_STATE_DISCONNECTED: Ci.nsINetworkInterface.DISCONNECTED, michael@0: michael@0: NETWORK_TYPE_WIFI: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, michael@0: NETWORK_TYPE_MOBILE: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, michael@0: NETWORK_TYPE_MOBILE_MMS: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS, michael@0: NETWORK_TYPE_MOBILE_SUPL: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL, michael@0: NETWORK_TYPE_MOBILE_IMS: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_IMS, michael@0: NETWORK_TYPE_MOBILE_DUN: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN, michael@0: // The network manager should only need to add the host route for "other" michael@0: // types, which is the same handling method as the supl type. So let the michael@0: // definition of other types to be the same as the one of supl type. michael@0: NETWORK_TYPE_MOBILE_OTHERS: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL, michael@0: michael@0: /** michael@0: * Standard values for the APN connection retry process michael@0: * Retry funcion: time(secs) = A * numer_of_retries^2 + B michael@0: */ michael@0: NETWORK_APNRETRY_FACTOR: 8, michael@0: NETWORK_APNRETRY_ORIGIN: 3, michael@0: NETWORK_APNRETRY_MAXRETRIES: 10, michael@0: michael@0: // Event timer for connection retries michael@0: timer: null, michael@0: michael@0: /** michael@0: * nsINetworkInterface Implementation michael@0: */ michael@0: michael@0: state: Ci.nsINetworkInterface.NETWORK_STATE_UNKNOWN, michael@0: michael@0: get type() { michael@0: if (this.connectedTypes.indexOf("default") != -1) { michael@0: return this.NETWORK_TYPE_MOBILE; michael@0: } michael@0: if (this.connectedTypes.indexOf("mms") != -1) { michael@0: return this.NETWORK_TYPE_MOBILE_MMS; michael@0: } michael@0: if (this.connectedTypes.indexOf("supl") != -1) { michael@0: return this.NETWORK_TYPE_MOBILE_SUPL; michael@0: } michael@0: if (this.connectedTypes.indexOf("ims") != -1) { michael@0: return this.NETWORK_TYPE_MOBILE_IMS; michael@0: } michael@0: if (this.connectedTypes.indexOf("dun") != -1) { michael@0: return this.NETWORK_TYPE_MOBILE_DUN; michael@0: } michael@0: michael@0: return this.NETWORK_TYPE_MOBILE_OTHERS; michael@0: }, michael@0: michael@0: name: null, michael@0: michael@0: ips: null, michael@0: michael@0: prefixLengths: null, michael@0: michael@0: gateways: null, michael@0: michael@0: dnses: null, michael@0: michael@0: get httpProxyHost() { michael@0: return this.apnSetting.proxy || ""; michael@0: }, michael@0: michael@0: get httpProxyPort() { michael@0: return this.apnSetting.port || ""; michael@0: }, michael@0: michael@0: /** michael@0: * nsIRilNetworkInterface Implementation michael@0: */ michael@0: michael@0: get serviceId() { michael@0: return this.dataConnectionHandler.clientId; michael@0: }, michael@0: michael@0: get iccId() { michael@0: let iccInfo = this.dataConnectionHandler.radioInterface.rilContext.iccInfo; michael@0: return iccInfo && iccInfo.iccid; michael@0: }, michael@0: michael@0: get mmsc() { michael@0: if (!this.inConnectedTypes("mms")) { michael@0: if (DEBUG) this.debug("Error! Only MMS network can get MMSC."); michael@0: throw Cr.NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: let mmsc = this.apnSetting.mmsc; michael@0: if (!mmsc) { michael@0: try { michael@0: mmsc = Services.prefs.getCharPref("ril.mms.mmsc"); michael@0: } catch (e) { michael@0: mmsc = ""; michael@0: } michael@0: } michael@0: michael@0: return mmsc; michael@0: }, michael@0: michael@0: get mmsProxy() { michael@0: if (!this.inConnectedTypes("mms")) { michael@0: if (DEBUG) this.debug("Error! Only MMS network can get MMS proxy."); michael@0: throw Cr.NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: let proxy = this.apnSetting.mmsproxy; michael@0: if (!proxy) { michael@0: try { michael@0: proxy = Services.prefs.getCharPref("ril.mms.mmsproxy"); michael@0: } catch (e) { michael@0: proxy = ""; michael@0: } michael@0: } michael@0: michael@0: return proxy; michael@0: }, michael@0: michael@0: get mmsPort() { michael@0: if (!this.inConnectedTypes("mms")) { michael@0: if (DEBUG) this.debug("Error! Only MMS network can get MMS port."); michael@0: throw Cr.NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: let port = this.apnSetting.mmsport; michael@0: if (!port) { michael@0: try { michael@0: port = Services.prefs.getIntPref("ril.mms.mmsport"); michael@0: } catch (e) { michael@0: port = -1; michael@0: } michael@0: } michael@0: michael@0: return port; michael@0: }, michael@0: michael@0: getAddresses: function (ips, prefixLengths) { michael@0: ips.value = this.ips.slice(); michael@0: prefixLengths.value = this.prefixLengths.slice(); michael@0: michael@0: return this.ips.length; michael@0: }, michael@0: michael@0: getGateways: function (count) { michael@0: if (count) { michael@0: count.value = this.gateways.length; michael@0: } michael@0: return this.gateways.slice(); michael@0: }, michael@0: michael@0: getDnses: function (count) { michael@0: if (count) { michael@0: count.value = this.dnses.length; michael@0: } michael@0: return this.dnses.slice(); michael@0: }, michael@0: michael@0: debug: function(s) { michael@0: dump("-*- RILNetworkInterface[" + this.dataConnectionHandler.clientId + ":" + michael@0: this.type + "]: " + s + "\n"); michael@0: }, michael@0: michael@0: dataCallError: function(message) { michael@0: if (message.apn != this.apnSetting.apn) { michael@0: return; michael@0: } michael@0: if (DEBUG) this.debug("Data call error on APN: " + message.apn); michael@0: this.reset(); michael@0: }, michael@0: michael@0: dataCallStateChanged: function(datacall) { michael@0: if (this.cid && this.cid != datacall.cid) { michael@0: // If data call for this connection existed but cid mismatched, michael@0: // it means this datacall state change is not for us. michael@0: return; michael@0: } michael@0: // If data call for this connection does not exist, it could be state michael@0: // change for new data call. We only update data call state change michael@0: // if APN name matched. michael@0: if (!this.cid && datacall.apn != this.apnSetting.apn) { michael@0: return; michael@0: } michael@0: if (DEBUG) { michael@0: this.debug("Data call ID: " + datacall.cid + ", interface name: " + michael@0: datacall.ifname + ", APN name: " + datacall.apn); michael@0: } michael@0: if (this.connecting && michael@0: (datacall.state == RIL.GECKO_NETWORK_STATE_CONNECTING || michael@0: datacall.state == RIL.GECKO_NETWORK_STATE_CONNECTED)) { michael@0: this.connecting = false; michael@0: this.cid = datacall.cid; michael@0: this.name = datacall.ifname; michael@0: for (let entry of datacall.addresses) { michael@0: this.ips.push(entry.address); michael@0: this.prefixLengths.push(entry.prefixLength); michael@0: } michael@0: this.gateways = datacall.gateways.slice(); michael@0: this.dnses = datacall.dnses.slice(); michael@0: if (!this.registeredAsNetworkInterface) { michael@0: gNetworkManager.registerNetworkInterface(this); michael@0: this.registeredAsNetworkInterface = true; michael@0: } michael@0: } michael@0: // In current design, we don't update status of secondary APN if it shares michael@0: // same APN name with the default APN. In this condition, this.cid will michael@0: // not be set and we don't want to update its status. michael@0: if (this.cid == null) { michael@0: return; michael@0: } michael@0: michael@0: if (this.state == datacall.state) { michael@0: if (datacall.state != RIL.GECKO_NETWORK_STATE_CONNECTED) { michael@0: return; michael@0: } michael@0: // State remains connected, check for minor changes. michael@0: let changed = false; michael@0: if (this.ips.length != datacall.addresses.length) { michael@0: changed = true; michael@0: this.ips = []; michael@0: this.prefixLengths = []; michael@0: for (let entry of datacall.addresses) { michael@0: this.ips.push(entry.address); michael@0: this.prefixLengths.push(entry.prefixLength); michael@0: } michael@0: } michael@0: michael@0: let reduceFunc = function(aRhs, aChanged, aElement, aIndex) { michael@0: return aChanged || (aElement != aRhs[aIndex]); michael@0: }; michael@0: for (let field of ["gateways", "dnses"]) { michael@0: let lhs = this[field], rhs = datacall[field]; michael@0: if (lhs.length != rhs.length || michael@0: lhs.reduce(reduceFunc.bind(null, rhs), false)) { michael@0: changed = true; michael@0: this[field] = rhs.slice(); michael@0: } michael@0: } michael@0: michael@0: if (changed) { michael@0: if (DEBUG) this.debug("Notify for data call minor changes."); michael@0: Services.obs.notifyObservers(this, michael@0: kNetworkInterfaceStateChangedTopic, michael@0: null); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: this.state = datacall.state; michael@0: michael@0: Services.obs.notifyObservers(this, michael@0: kNetworkInterfaceStateChangedTopic, michael@0: null); michael@0: michael@0: if ((this.state == RIL.GECKO_NETWORK_STATE_UNKNOWN || michael@0: this.state == RIL.GECKO_NETWORK_STATE_DISCONNECTED) && michael@0: this.registeredAsNetworkInterface) { michael@0: gNetworkManager.unregisterNetworkInterface(this); michael@0: this.registeredAsNetworkInterface = false; michael@0: this.cid = null; michael@0: this.connectedTypes = []; michael@0: michael@0: this.ips = []; michael@0: this.prefixLengths = []; michael@0: this.dnses = []; michael@0: this.gateways = []; michael@0: } michael@0: michael@0: // In case the data setting changed while the datacall was being started or michael@0: // ended, let's re-check the setting and potentially adjust the datacall michael@0: // state again. michael@0: let apnSettings = this.dataConnectionHandler.apnSettings; michael@0: if (apnSettings.byType.default && michael@0: (apnSettings.byType.default.apn == this.apnSetting.apn)) { michael@0: this.dataConnectionHandler.updateRILNetworkInterface(); michael@0: } michael@0: }, michael@0: michael@0: // Helpers michael@0: michael@0: cid: null, michael@0: registeredAsDataCallCallback: false, michael@0: registeredAsNetworkInterface: false, michael@0: connecting: false, michael@0: apnSetting: null, michael@0: michael@0: // APN failed connections. Retry counter michael@0: apnRetryCounter: 0, michael@0: michael@0: connectedTypes: null, michael@0: michael@0: inConnectedTypes: function(type) { michael@0: return this.connectedTypes.indexOf(type) != -1; michael@0: }, michael@0: michael@0: get connected() { michael@0: return this.state == RIL.GECKO_NETWORK_STATE_CONNECTED; michael@0: }, michael@0: michael@0: connect: function(apntype) { michael@0: if (apntype && !this.inConnectedTypes(apntype)) { michael@0: this.connectedTypes.push(apntype); michael@0: } michael@0: michael@0: if (this.connecting || this.connected) { michael@0: return; michael@0: } michael@0: michael@0: // When the retry mechanism is running in background and someone calls michael@0: // disconnect(), this.connectedTypes.length has chances to become 0. michael@0: if (!this.connectedTypes.length) { michael@0: return; michael@0: } michael@0: michael@0: if (!this.registeredAsDataCallCallback) { michael@0: this.dataConnectionHandler.registerDataCallCallback(this); michael@0: this.registeredAsDataCallCallback = true; michael@0: } michael@0: michael@0: if (!this.apnSetting.apn) { michael@0: if (DEBUG) this.debug("APN name is empty, nothing to do."); michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.debug("Going to set up data connection with APN " + michael@0: this.apnSetting.apn); michael@0: } michael@0: let radioInterface = this.dataConnectionHandler.radioInterface; michael@0: let radioTechType = radioInterface.rilContext.data.type; michael@0: let radioTechnology = RIL.GECKO_RADIO_TECH.indexOf(radioTechType); michael@0: let authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(this.apnSetting.authtype); michael@0: // Use the default authType if the value in database is invalid. michael@0: // For the case that user might not select the authentication type. michael@0: if (authType == -1) { michael@0: if (DEBUG) { michael@0: this.debug("Invalid authType " + this.apnSetting.authtype); michael@0: } michael@0: authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(RIL.GECKO_DATACALL_AUTH_DEFAULT); michael@0: } michael@0: let pdpType = RIL.GECKO_DATACALL_PDP_TYPE_IP; michael@0: if (RILQUIRKS_HAVE_IPV6) { michael@0: pdpType = !radioInterface.rilContext.data.roaming michael@0: ? this.apnSetting.protocol michael@0: : this.apnSetting.roaming_protocol; michael@0: if (RIL.RIL_DATACALL_PDP_TYPES.indexOf(pdpType) < 0) { michael@0: if (DEBUG) { michael@0: this.debug("Invalid pdpType '" + pdpType + "', using '" + michael@0: RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT + "'"); michael@0: } michael@0: pdpType = RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT; michael@0: } michael@0: } michael@0: radioInterface.setupDataCall(radioTechnology, michael@0: this.apnSetting.apn, michael@0: this.apnSetting.user, michael@0: this.apnSetting.password, michael@0: authType, michael@0: pdpType); michael@0: this.connecting = true; michael@0: }, michael@0: michael@0: reset: function() { michael@0: let apnRetryTimer; michael@0: this.connecting = false; michael@0: // We will retry the connection in increasing times michael@0: // based on the function: time = A * numer_of_retries^2 + B michael@0: if (this.apnRetryCounter >= this.NETWORK_APNRETRY_MAXRETRIES) { michael@0: this.apnRetryCounter = 0; michael@0: this.timer = null; michael@0: this.connectedTypes = []; michael@0: if (DEBUG) this.debug("Too many APN Connection retries - STOP retrying"); michael@0: return; michael@0: } michael@0: michael@0: apnRetryTimer = this.NETWORK_APNRETRY_FACTOR * michael@0: (this.apnRetryCounter * this.apnRetryCounter) + michael@0: this.NETWORK_APNRETRY_ORIGIN; michael@0: this.apnRetryCounter++; michael@0: if (DEBUG) { michael@0: this.debug("Data call - APN Connection Retry Timer (secs-counter): " + michael@0: apnRetryTimer + "-" + this.apnRetryCounter); michael@0: } michael@0: michael@0: if (this.timer == null) { michael@0: // Event timer for connection retries michael@0: this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: } michael@0: this.timer.initWithCallback(this, apnRetryTimer * 1000, michael@0: Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }, michael@0: michael@0: disconnect: function(apntype) { michael@0: let index = this.connectedTypes.indexOf(apntype); michael@0: if (index != -1) { michael@0: this.connectedTypes.splice(index, 1); michael@0: } michael@0: michael@0: if (this.connectedTypes.length) { michael@0: return; michael@0: } michael@0: michael@0: if (this.state == RIL.GECKO_NETWORK_STATE_DISCONNECTING || michael@0: this.state == RIL.GECKO_NETWORK_STATE_DISCONNECTED || michael@0: this.state == RIL.GECKO_NETWORK_STATE_UNKNOWN) { michael@0: return; michael@0: } michael@0: let reason = RIL.DATACALL_DEACTIVATE_NO_REASON; michael@0: if (DEBUG) this.debug("Going to disconnet data connection " + this.cid); michael@0: this.dataConnectionHandler.radioInterface.deactivateDataCall(this.cid, michael@0: reason); michael@0: }, michael@0: michael@0: // Entry method for timer events. Used to reconnect to a failed APN michael@0: notify: function(timer) { michael@0: this.connect(); michael@0: }, michael@0: michael@0: shutdown: function() { michael@0: this.timer = null; michael@0: } michael@0: michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RadioInterfaceLayer]);