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