/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "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/systemlibs.js"); XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService", "@mozilla.org/settingsService;1", "nsISettingsService"); XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager", "@mozilla.org/network/manager;1", "nsINetworkManager"); XPCOMUtils.defineLazyServiceGetter(this, "gMobileConnectionService", "@mozilla.org/mobileconnection/mobileconnectionservice;1", "nsIMobileConnectionService"); XPCOMUtils.defineLazyServiceGetter(this, "gIccService", "@mozilla.org/icc/iccservice;1", "nsIIccService"); XPCOMUtils.defineLazyServiceGetter(this, "gDataCallInterfaceService", "@mozilla.org/datacall/interfaceservice;1", "nsIDataCallInterfaceService"); XPCOMUtils.defineLazyGetter(this, "RIL", function() { let obj = {}; Cu.import("resource://gre/modules/ril_consts.js", obj); return obj; }); // Ril quirk to attach data registration on demand. var RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true"; // Ril quirk to control the uicc/data subscription. var RILQUIRKS_SUBSCRIPTION_CONTROL = libcutils.property_get("ro.moz.ril.subscription_control", "false") == "true"; // Ril quirk to enable IPv6 protocol/roaming protocol in APN settings. var RILQUIRKS_HAVE_IPV6 = libcutils.property_get("ro.moz.ril.ipv6", "false") == "true"; const DATACALLMANAGER_CID = Components.ID("{35b9efa2-e42c-45ce-8210-0a13e6f4aadc}"); const DATACALLHANDLER_CID = Components.ID("{132b650f-c4d8-4731-96c5-83785cb31dee}"); const RILNETWORKINTERFACE_CID = Components.ID("{9574ee84-5d0d-4814-b9e6-8b279e03dcf4}"); const RILNETWORKINFO_CID = Components.ID("{dd6cf2f0-f0e3-449f-a69e-7c34fdcb8d4b}"); const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown"; const TOPIC_MOZSETTINGS_CHANGED = "mozsettings-changed"; const TOPIC_PREF_CHANGED = "nsPref:changed"; const TOPIC_DATA_CALL_ERROR = "data-call-error"; const PREF_RIL_DEBUG_ENABLED = "ril.debugging.enabled"; const NETWORK_TYPE_UNKNOWN = Ci.nsINetworkInfo.NETWORK_TYPE_UNKNOWN; const NETWORK_TYPE_WIFI = Ci.nsINetworkInfo.NETWORK_TYPE_WIFI; const NETWORK_TYPE_MOBILE = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE; const NETWORK_TYPE_MOBILE_MMS = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS; const NETWORK_TYPE_MOBILE_SUPL = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL; const NETWORK_TYPE_MOBILE_IMS = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_IMS; const NETWORK_TYPE_MOBILE_DUN = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN; const NETWORK_TYPE_MOBILE_FOTA = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_FOTA; const NETWORK_STATE_UNKNOWN = Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN; const NETWORK_STATE_CONNECTING = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTING; const NETWORK_STATE_CONNECTED = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED; const NETWORK_STATE_DISCONNECTING = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTING; const NETWORK_STATE_DISCONNECTED = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED; const INT32_MAX = 2147483647; // set to true in ril_consts.js to see debug messages var DEBUG = RIL.DEBUG_RIL; function updateDebugFlag() { // Read debug setting from pref let debugPref; try { debugPref = Services.prefs.getBoolPref(PREF_RIL_DEBUG_ENABLED); } catch (e) { debugPref = false; } DEBUG = debugPref || RIL.DEBUG_RIL; } updateDebugFlag(); function DataCallManager() { this._connectionHandlers = []; let numRadioInterfaces = gMobileConnectionService.numItems; for (let clientId = 0; clientId < numRadioInterfaces; clientId++) { this._connectionHandlers.push(new DataCallHandler(clientId)); } 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, TOPIC_XPCOM_SHUTDOWN, false); Services.obs.addObserver(this, TOPIC_MOZSETTINGS_CHANGED, false); Services.prefs.addObserver(PREF_RIL_DEBUG_ENABLED, this, false); } DataCallManager.prototype = { classID: DATACALLMANAGER_CID, classInfo: XPCOMUtils.generateCI({classID: DATACALLMANAGER_CID, classDescription: "Data Call Manager", interfaces: [Ci.nsIDataCallManager]}), QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallManager, 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(aMsg) { dump("-*- DataCallManager: " + aMsg + "\n"); }, get dataDefaultServiceId() { return this._dataDefaultClientId; }, getDataCallHandler: function(aClientId) { let handler = this._connectionHandlers[aClientId] if (!handler) { throw Cr.NS_ERROR_UNEXPECTED; } return handler; }, _setDataRegistration: function(aDataCallInterface, aAttach) { return new Promise(function(aResolve, aReject) { let callback = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallCallback]), notifySuccess: function() { aResolve(); }, notifyError: function(aErrorMsg) { aReject(aErrorMsg); } }; aDataCallInterface.setDataRegistration(aAttach, callback); }); }, _handleDataClientIdChange: function(aNewClientId) { if (this._dataDefaultClientId === aNewClientId) { return; } this._dataDefaultClientId = aNewClientId; // This is to handle boot up stage. if (this._currentDataClientId == -1) { this._currentDataClientId = this._dataDefaultClientId; let connHandler = this._connectionHandlers[this._currentDataClientId]; let dcInterface = connHandler.dataCallInterface; if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND || RILQUIRKS_SUBSCRIPTION_CONTROL) { this._setDataRegistration(dcInterface, 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.dataCallInterface; let oldSettings = oldConnHandler.dataCallSettings; let newConnHandler = this._connectionHandlers[this._dataDefaultClientId]; let newIface = newConnHandler.dataCallInterface; let newSettings = newConnHandler.dataCallSettings; let applyPendingDataSettings = () => { if (DEBUG) { this.debug("Apply pending data registration and settings."); } if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND || RILQUIRKS_SUBSCRIPTION_CONTROL) { this._setDataRegistration(oldIface, false).then(() => { if (this._dataEnabled) { newSettings.oldEnabled = newSettings.enabled; newSettings.enabled = true; } this._currentDataClientId = this._dataDefaultClientId; this._setDataRegistration(newIface, true).then(() => { newConnHandler.updateRILNetworkInterface(); }); }); return; } if (this._dataEnabled) { newSettings.oldEnabled = newSettings.enabled; newSettings.enabled = true; } this._currentDataClientId = this._dataDefaultClientId; newConnHandler.updateRILNetworkInterface(); }; if (this._dataEnabled) { oldSettings.oldEnabled = oldSettings.enabled; oldSettings.enabled = false; } oldConnHandler.deactivateDataCallsAndWait().then(() => { applyPendingDataSettings(); }); }, _shutdown: function() { for (let handler of this._connectionHandlers) { handler.shutdown(); } this._connectionHandlers = null; Services.prefs.removeObserver(PREF_RIL_DEBUG_ENABLED, this); Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN); Services.obs.removeObserver(this, TOPIC_MOZSETTINGS_CHANGED); }, /** * nsISettingsServiceCallback */ handle: function(aName, aResult) { switch (aName) { case "ril.data.apnSettings": if (DEBUG) { this.debug("'ril.data.apnSettings' is now " + JSON.stringify(aResult)); } if (!aResult) { break; } for (let clientId in this._connectionHandlers) { let handler = this._connectionHandlers[clientId]; let apnSetting = aResult[clientId]; if (handler && apnSetting) { handler.updateApnSettings(apnSetting); } } break; case "ril.data.enabled": if (DEBUG) { this.debug("'ril.data.enabled' is now " + aResult); } if (this._dataEnabled === aResult) { break; } this._dataEnabled = aResult; 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 = aResult; connHandler.updateRILNetworkInterface(); break; case "ril.data.roaming_enabled": if (DEBUG) { this.debug("'ril.data.roaming_enabled' is now " + aResult); this.debug("Default id for data call: " + this._dataDefaultClientId); } for (let clientId = 0; clientId < this._connectionHandlers.length; clientId++) { let connHandler = this._connectionHandlers[clientId]; let settings = connHandler.dataCallSettings; settings.roamingEnabled = Array.isArray(aResult) ? aResult[clientId] : aResult; } 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": aResult = aResult || 0; if (DEBUG) { this.debug("'ril.data.defaultServiceId' is now " + aResult); } this._handleDataClientIdChange(aResult); break; } }, handleError: function(aErrorMessage) { if (DEBUG) { this.debug("There was an error while reading RIL settings."); } }, /** * nsIObserver interface methods. */ observe: function(aSubject, aTopic, aData) { switch (aTopic) { case TOPIC_MOZSETTINGS_CHANGED: if ("wrappedJSObject" in aSubject) { aSubject = aSubject.wrappedJSObject; } this.handle(aSubject.key, aSubject.value); break; case TOPIC_PREF_CHANGED: if (aData === PREF_RIL_DEBUG_ENABLED) { updateDebugFlag(); } break; case TOPIC_XPCOM_SHUTDOWN: this._shutdown(); break; } }, }; function DataCallHandler(aClientId) { // Initial owning attributes. this.clientId = aClientId; this.dataCallSettings = { oldEnabled: false, enabled: false, roamingEnabled: false }; this._dataCalls = []; this._listeners = []; // This map is used to collect all the apn types and its corresponding // RILNetworkInterface. this.dataNetworkInterfaces = new Map(); this.dataCallInterface = gDataCallInterfaceService.getDataCallInterface(aClientId); this.dataCallInterface.registerListener(this); let mobileConnection = gMobileConnectionService.getItemByServiceId(aClientId); mobileConnection.registerListener(this); this._dataInfo = { state: mobileConnection.data.state, type: mobileConnection.data.type, roaming: mobileConnection.data.roaming } } DataCallHandler.prototype = { classID: DATACALLHANDLER_CID, classInfo: XPCOMUtils.generateCI({classID: DATACALLHANDLER_CID, classDescription: "Data Call Handler", interfaces: [Ci.nsIDataCallHandler]}), QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallHandler, Ci.nsIDataCallInterfaceListener, Ci.nsIMobileConnectionListener]), clientId: 0, dataCallInterface: null, dataCallSettings: null, dataNetworkInterfaces: null, _dataCalls: null, _dataInfo: null, // Apn settings to be setup after data call are cleared. _pendingApnSettings: null, debug: function(aMsg) { dump("-*- DataCallHandler[" + this.clientId + "]: " + aMsg + "\n"); }, shutdown: function() { // Shutdown all RIL network interfaces this.dataNetworkInterfaces.forEach(function(networkInterface) { gNetworkManager.unregisterNetworkInterface(networkInterface); networkInterface.shutdown(); networkInterface = null; }); this.dataNetworkInterfaces.clear(); this._dataCalls = []; this.clientId = null; this.dataCallInterface.unregisterListener(this); this.dataCallInterface = null; let mobileConnection = gMobileConnectionService.getItemByServiceId(this.clientId); mobileConnection.unregisterListener(this); }, /** * Check if we get all necessary APN data. */ _validateApnSetting: function(aApnSetting) { return (aApnSetting && aApnSetting.apn && aApnSetting.types && aApnSetting.types.length); }, _convertApnType: function(aApnType) { switch (aApnType) { case "default": return NETWORK_TYPE_MOBILE; case "mms": return NETWORK_TYPE_MOBILE_MMS; case "supl": return NETWORK_TYPE_MOBILE_SUPL; case "ims": return NETWORK_TYPE_MOBILE_IMS; case "dun": return NETWORK_TYPE_MOBILE_DUN; case "fota": return NETWORK_TYPE_MOBILE_FOTA; default: return NETWORK_TYPE_UNKNOWN; } }, _compareDataCallOptions: function(aDataCall, aNewDataCall) { return aDataCall.apnProfile.apn == aNewDataCall.apnProfile.apn && aDataCall.apnProfile.user == aNewDataCall.apnProfile.user && aDataCall.apnProfile.password == aNewDataCall.apnProfile.passwd && aDataCall.apnProfile.authType == aNewDataCall.apnProfile.authType && aDataCall.apnProfile.protocol == aNewDataCall.apnProfile.protocol && aDataCall.apnProfile.roaming_protocol == aNewDataCall.apnProfile.roaming_protocol; }, /** * 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(aNewApnSettings) { if (!aNewApnSettings) { return; } if (DEBUG) this.debug("setupApnSettings: " + JSON.stringify(aNewApnSettings)); // Shutdown all network interfaces and clear data calls. this.dataNetworkInterfaces.forEach(function(networkInterface) { gNetworkManager.unregisterNetworkInterface(networkInterface); networkInterface.shutdown(); networkInterface = null; }); this.dataNetworkInterfaces.clear(); this._dataCalls = []; // Cache the APN settings by APNs and by types in the RIL. for (let inputApnSetting of aNewApnSettings) { if (!this._validateApnSetting(inputApnSetting)) { continue; } // Use APN type as the key of dataNetworkInterfaces to refer to the // corresponding RILNetworkInterface. for (let i = 0; i < inputApnSetting.types.length; i++) { let apnType = inputApnSetting.types[i]; let networkType = this._convertApnType(apnType); if (networkType === NETWORK_TYPE_UNKNOWN) { if (DEBUG) this.debug("Invalid apn type: " + apnType); continue; } if (DEBUG) this.debug("Preparing RILNetworkInterface for type: " + apnType); // Create DataCall for RILNetworkInterface or reuse one that is shareable. let dataCall; for (let i = 0; i < this._dataCalls.length; i++) { if (this._dataCalls[i].canHandleApn(inputApnSetting)) { if (DEBUG) this.debug("Found shareable DataCall, reusing it."); dataCall = this._dataCalls[i]; break; } } if (!dataCall) { if (DEBUG) this.debug("No shareable DataCall found, creating one."); dataCall = new DataCall(this.clientId, inputApnSetting, this); this._dataCalls.push(dataCall); } try { let networkInterface = new RILNetworkInterface(this, networkType, inputApnSetting, dataCall); gNetworkManager.registerNetworkInterface(networkInterface); this.dataNetworkInterfaces.set(networkType, networkInterface); } catch (e) { if (DEBUG) { this.debug("Error setting up RILNetworkInterface for type " + apnType + ": " + e); } } } } }, /** * Check if all data is disconnected. */ allDataDisconnected: function() { for (let i = 0; i < this._dataCalls.length; i++) { let dataCall = this._dataCalls[i]; if (dataCall.state != NETWORK_STATE_UNKNOWN && dataCall.state != NETWORK_STATE_DISCONNECTED) { return false; } } return true; }, deactivateDataCallsAndWait: function() { return new Promise((aResolve, aReject) => { this.deactivateDataCalls({ notifyDataCallsDisconnected: function() { aResolve(); } }); }); }, updateApnSettings: function(aNewApnSettings) { if (!aNewApnSettings) { return; } if (this._pendingApnSettings) { // Change of apn settings in process, just update to the newest. this._pengingApnSettings = aNewApnSettings; return; } this._pendingApnSettings = aNewApnSettings; this.deactivateDataCallsAndWait().then(() => { this._setupApnSettings(this._pendingApnSettings); this._pendingApnSettings = null; this.updateRILNetworkInterface(); }); }, updateRILNetworkInterface: function() { let networkInterface = this.dataNetworkInterfaces.get(NETWORK_TYPE_MOBILE); if (!networkInterface) { if (DEBUG) { this.debug("No network interface for default data."); } return; } let connection = gMobileConnectionService.getItemByServiceId(this.clientId); // This check avoids data call connection if the radio is not ready // yet after toggling off airplane mode. let radioState = connection && connection.radioState; if (radioState != Ci.nsIMobileConnection.MOBILE_RADIO_STATE_ENABLED) { 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 dataInfo = connection && connection.data; let isRegistered = dataInfo && dataInfo.state == RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED; let haveDataConnection = dataInfo && 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.activeNetworkInfo && gNetworkManager.activeNetworkInfo.type == NETWORK_TYPE_WIFI) { wifi_active = true; } let defaultDataCallConnected = networkInterface.connected; // We have moved part of the decision making into DataCall, the rest will be // moved after Bug 904514 - [meta] NetworkManager enhancement. if (networkInterface.enabled && (!this.dataCallSettings.enabled || (dataInfo.roaming && !this.dataCallSettings.roamingEnabled))) { if (DEBUG) { this.debug("Data call settings: disconnect data call."); } networkInterface.disconnect(); return; } if (networkInterface.enabled && wifi_active) { if (DEBUG) { this.debug("Disconnect data call when Wifi is connected."); } networkInterface.disconnect(); 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; } if (this._deactivatingDataCalls) { if (DEBUG) this.debug("We're deactivating all data calls, ignore any changes."); return; } if (DEBUG) { this.debug("Data call settings: connect data call."); } networkInterface.connect(); }, _isMobileNetworkType: function(aNetworkType) { if (aNetworkType === NETWORK_TYPE_MOBILE || aNetworkType === NETWORK_TYPE_MOBILE_MMS || aNetworkType === NETWORK_TYPE_MOBILE_SUPL || aNetworkType === NETWORK_TYPE_MOBILE_IMS || aNetworkType === NETWORK_TYPE_MOBILE_DUN || aNetworkType === NETWORK_TYPE_MOBILE_FOTA) { return true; } return false; }, getDataCallStateByType: function(aNetworkType) { if (!this._isMobileNetworkType(aNetworkType)) { if (DEBUG) this.debug(aNetworkType + " is not a mobile network type!"); throw Cr.NS_ERROR_INVALID_ARG; } let networkInterface = this.dataNetworkInterfaces.get(aNetworkType); if (!networkInterface) { return NETWORK_STATE_UNKNOWN; } return networkInterface.info.state; }, setupDataCallByType: function(aNetworkType) { if (DEBUG) { this.debug("setupDataCallByType: " + aNetworkType); } if (!this._isMobileNetworkType(aNetworkType)) { if (DEBUG) this.debug(aNetworkType + " is not a mobile network type!"); throw Cr.NS_ERROR_INVALID_ARG; } let networkInterface = this.dataNetworkInterfaces.get(aNetworkType); if (!networkInterface) { if (DEBUG) { this.debug("No network interface for type: " + aNetworkType); } return; } networkInterface.connect(); }, deactivateDataCallByType: function(aNetworkType) { if (DEBUG) { this.debug("deactivateDataCallByType: " + aNetworkType); } if (!this._isMobileNetworkType(aNetworkType)) { if (DEBUG) this.debug(aNetworkType + " is not a mobile network type!"); throw Cr.NS_ERROR_INVALID_ARG; } let networkInterface = this.dataNetworkInterfaces.get(aNetworkType); if (!networkInterface) { if (DEBUG) { this.debug("No network interface for type: " + aNetworkType); } return; } networkInterface.disconnect(); }, _deactivatingDataCalls: false, deactivateDataCalls: function(aCallback) { let dataDisconnecting = false; this.dataNetworkInterfaces.forEach(function(networkInterface) { if (networkInterface.enabled) { if (networkInterface.info.state != NETWORK_STATE_UNKNOWN && networkInterface.info.state != NETWORK_STATE_DISCONNECTED) { dataDisconnecting = true; } networkInterface.disconnect(); } }); this._deactivatingDataCalls = dataDisconnecting; if (!dataDisconnecting) { aCallback.notifyDataCallsDisconnected(); return; } let callback = { notifyAllDataDisconnected: () => { this._unregisterListener(callback); aCallback.notifyDataCallsDisconnected(); } }; this._registerListener(callback); }, _listeners: null, _notifyListeners: function(aMethodName, aArgs) { let listeners = this._listeners.slice(); for (let listener of listeners) { if (this._listeners.indexOf(listener) == -1) { // Listener has been unregistered in previous run. continue; } let handler = listener[aMethodName]; try { handler.apply(listener, aArgs); } catch (e) { this.debug("listener for " + aMethodName + " threw an exception: " + e); } } }, _registerListener: function(aListener) { if (this._listeners.indexOf(aListener) >= 0) { return; } this._listeners.push(aListener); }, _unregisterListener: function(aListener) { let index = this._listeners.indexOf(aListener); if (index >= 0) { this._listeners.splice(index, 1); } }, _findDataCallByCid: function(aCid) { if (aCid === undefined || aCid < 0) { return -1; } for (let i = 0; i < this._dataCalls.length; i++) { let datacall = this._dataCalls[i]; if (datacall.linkInfo.cid != null && datacall.linkInfo.cid == aCid) { return i; } } return -1; }, /** * Notify about data call setup error, called from DataCall. */ notifyDataCallError: function(aDataCall, aErrorMsg) { // Notify data call error only for data APN let networkInterface = this.dataNetworkInterfaces.get(NETWORK_TYPE_MOBILE); if (networkInterface && networkInterface.enabled) { let dataCall = networkInterface.dataCall; if (this._compareDataCallOptions(dataCall, aDataCall)) { Services.obs.notifyObservers(networkInterface.info, TOPIC_DATA_CALL_ERROR, aErrorMsg); } } }, /** * Notify about data call changed, called from DataCall. */ notifyDataCallChanged: function(aUpdatedDataCall) { // Process pending radio power off request after all data calls // are disconnected. if (aUpdatedDataCall.state == NETWORK_STATE_DISCONNECTED || aUpdatedDataCall.state == NETWORK_STATE_UNKNOWN && this.allDataDisconnected() && this._deactivatingDataCalls) { this._deactivatingDataCalls = false; this._notifyListeners("notifyAllDataDisconnected", { clientId: this.clientId }); } }, // nsIDataCallInterfaceListener notifyDataCallListChanged: function(aCount, aDataCallList) { let currentDataCalls = this._dataCalls.slice(); for (let i = 0; i < aDataCallList.length; i++) { let dataCall = aDataCallList[i]; let index = this._findDataCallByCid(dataCall.cid); if (index == -1) { if (DEBUG) { this.debug("Unexpected new data call: " + JSON.stringify(dataCall)); } continue; } currentDataCalls[index].onDataCallChanged(dataCall); currentDataCalls[index] = null; } // If there is any CONNECTED DataCall left in currentDataCalls, means that // it is missing in dataCallList, we should send a DISCONNECTED event to // notify about this. for (let i = 0; i < currentDataCalls.length; i++) { let currentDataCall = currentDataCalls[i]; if (currentDataCall && currentDataCall.linkInfo.cid != null && currentDataCall.state == NETWORK_STATE_CONNECTED) { if (DEBUG) { this.debug("Expected data call missing: " + JSON.stringify( currentDataCall.apnProfile) + ", must have been DISCONNECTED."); } currentDataCall.onDataCallChanged({ state: NETWORK_STATE_DISCONNECTED }); } } }, // nsIMobileConnectionListener notifyVoiceChanged: function() {}, notifyDataChanged: function () { let connection = gMobileConnectionService.getItemByServiceId(this.clientId); let newDataInfo = connection.data; if (this._dataInfo.state == newDataInfo.state && this._dataInfo.type == newDataInfo.type && this._dataInfo.roaming == newDataInfo.roaming) { return; } this._dataInfo.state = newDataInfo.state; this._dataInfo.type = newDataInfo.type; this._dataInfo.roaming = newDataInfo.roaming; this.updateRILNetworkInterface(); }, notifyDataError: function (aMessage) {}, notifyCFStateChanged: function(aAction, aReason, aNumber, aTimeSeconds, aServiceClass) {}, notifyEmergencyCbModeChanged: function(aActive, aTimeoutMs) {}, notifyOtaStatusChanged: function(aStatus) {}, notifyRadioStateChanged: function() {}, notifyClirModeChanged: function(aMode) {}, notifyLastKnownNetworkChanged: function() {}, notifyLastKnownHomeNetworkChanged: function() {}, notifyNetworkSelectionModeChanged: function() {}, notifyDeviceIdentitiesChanged: function() {} }; function DataCall(aClientId, aApnSetting, aDataCallHandler) { this.clientId = aClientId; this.dataCallHandler = aDataCallHandler; this.apnProfile = { apn: aApnSetting.apn, user: aApnSetting.user, password: aApnSetting.password, authType: aApnSetting.authtype, protocol: aApnSetting.protocol, roaming_protocol: aApnSetting.roaming_protocol }; this.linkInfo = { cid: null, ifname: null, addresses: [], dnses: [], gateways: [], pcscf: [], mtu: null }; this.state = NETWORK_STATE_UNKNOWN; this.requestedNetworkIfaces = []; } DataCall.prototype = { /** * 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, dataCallHandler: null, // Event timer for connection retries timer: null, // APN failed connections. Retry counter apnRetryCounter: 0, // Array to hold RILNetworkInterfaces that requested this DataCall. requestedNetworkIfaces: null, /** * @return "deactivate" if changes or one of the aCurrentDataCall * addresses is missing in updatedDataCall, or "identical" if no * changes found, or "changed" otherwise. */ _compareDataCallLink: function(aUpdatedDataCall, aCurrentDataCall) { // If network interface is changed, report as "deactivate". if (aUpdatedDataCall.ifname != aCurrentDataCall.ifname) { return "deactivate"; } // If any existing address is missing, report as "deactivate". for (let i = 0; i < aCurrentDataCall.addresses.length; i++) { let address = aCurrentDataCall.addresses[i]; if (aUpdatedDataCall.addresses.indexOf(address) < 0) { return "deactivate"; } } if (aCurrentDataCall.addresses.length != aUpdatedDataCall.addresses.length) { // Since now all |aCurrentDataCall.addresses| are found in // |aUpdatedDataCall.addresses|, this means one or more new addresses are // reported. return "changed"; } let fields = ["gateways", "dnses"]; for (let i = 0; i < fields.length; i++) { // Compare .. let field = fields[i]; let lhs = aUpdatedDataCall[field], rhs = aCurrentDataCall[field]; if (lhs.length != rhs.length) { return "changed"; } for (let i = 0; i < lhs.length; i++) { if (lhs[i] != rhs[i]) { return "changed"; } } } if (aCurrentDataCall.mtu != aUpdatedDataCall.mtu) { return "changed"; } return "identical"; }, _getGeckoDataCallState:function (aDataCall) { if (aDataCall.active == Ci.nsIDataCallInterface.DATACALL_STATE_ACTIVE_UP || aDataCall.active == Ci.nsIDataCallInterface.DATACALL_STATE_ACTIVE_DOWN) { return NETWORK_STATE_CONNECTED; } return NETWORK_STATE_DISCONNECTED; }, onSetupDataCallResult: function(aDataCall) { this.debug("onSetupDataCallResult: " + JSON.stringify(aDataCall)); let errorMsg = aDataCall.errorMsg; if (aDataCall.failCause && aDataCall.failCause != Ci.nsIDataCallInterface.DATACALL_FAIL_NONE) { errorMsg = RIL.RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[aDataCall.failCause]; } if (errorMsg) { if (DEBUG) { this.debug("SetupDataCall error for apn " + this.apnProfile.apn + ": " + errorMsg + " (" + aDataCall.failCause + "), retry time: " + aDataCall.suggestedRetryTime); } this.state = NETWORK_STATE_DISCONNECTED; if (this.requestedNetworkIfaces.length === 0) { if (DEBUG) this.debug("This DataCall is not requested anymore."); return; } // Let DataCallHandler notify MobileConnectionService this.dataCallHandler.notifyDataCallError(this, errorMsg); // For suggestedRetryTime, the value of INT32_MAX(0x7fffffff) means no retry. if (aDataCall.suggestedRetryTime === INT32_MAX || this.isPermanentFail(aDataCall.failCause, errorMsg)) { if (DEBUG) this.debug("Data call error: no retry needed."); return; } this.retry(aDataCall.suggestedRetryTime); return; } this.apnRetryCounter = 0; this.linkInfo.cid = aDataCall.cid; if (this.requestedNetworkIfaces.length === 0) { if (DEBUG) { this.debug("State is connected, but no network interface requested" + " this DataCall"); } this.deactivate(); return; } this.linkInfo.ifname = aDataCall.ifname; this.linkInfo.addresses = aDataCall.addresses ? aDataCall.addresses.split(" ") : []; this.linkInfo.gateways = aDataCall.gateways ? aDataCall.gateways.split(" ") : []; this.linkInfo.dnses = aDataCall.dnses ? aDataCall.dnses.split(" ") : []; this.linkInfo.pcscf = aDataCall.pcscf ? aDataCall.pcscf.split(" ") : []; this.linkInfo.mtu = aDataCall.mtu > 0 ? aDataCall.mtu : 0; this.state = this._getGeckoDataCallState(aDataCall); // Notify DataCallHandler about data call connected. this.dataCallHandler.notifyDataCallChanged(this); for (let i = 0; i < this.requestedNetworkIfaces.length; i++) { this.requestedNetworkIfaces[i].notifyRILNetworkInterface(); } }, onDeactivateDataCallResult: function() { if (DEBUG) this.debug("onDeactivateDataCallResult"); this.reset(); if (this.requestedNetworkIfaces.length > 0) { if (DEBUG) { this.debug("State is disconnected/unknown, but this DataCall is" + " requested."); } this.setup(); return; } // Notify DataCallHandler about data call disconnected. this.dataCallHandler.notifyDataCallChanged(this); }, onDataCallChanged: function(aUpdatedDataCall) { if (DEBUG) { this.debug("onDataCallChanged: " + JSON.stringify(aUpdatedDataCall)); } if (this.state == NETWORK_STATE_CONNECTING || this.state == NETWORK_STATE_DISCONNECTING) { if (DEBUG) { this.debug("We are in connecting/disconnecting state, ignore any " + "unsolicited event for now."); } return; } let dataCallState = this._getGeckoDataCallState(aUpdatedDataCall); if (this.state == dataCallState && dataCallState != NETWORK_STATE_CONNECTED) { return; } let newLinkInfo = { ifname: aUpdatedDataCall.ifname, addresses: aUpdatedDataCall.addresses ? aUpdatedDataCall.addresses.split(" ") : [], dnses: aUpdatedDataCall.dnses ? aUpdatedDataCall.dnses.split(" ") : [], gateways: aUpdatedDataCall.gateways ? aUpdatedDataCall.gateways.split(" ") : [], pcscf: aUpdatedDataCall.pcscf ? aUpdatedDataCall.pcscf.split(" ") : [], mtu: aUpdatedDataCall.mtu > 0 ? aUpdatedDataCall.mtu : 0 }; switch (dataCallState) { case NETWORK_STATE_CONNECTED: if (this.state == NETWORK_STATE_CONNECTED) { let result = this._compareDataCallLink(newLinkInfo, this.linkInfo); if (result == "identical") { if (DEBUG) this.debug("No changes in data call."); return; } if (result == "deactivate") { if (DEBUG) this.debug("Data link changed, cleanup."); this.deactivate(); return; } // Minor change, just update and notify. if (DEBUG) { this.debug("Data link minor change, just update and notify."); } this.linkInfo.addresses = newLinkInfo.addresses.slice(); this.linkInfo.gateways = newLinkInfo.gateways.slice(); this.linkInfo.dnses = newLinkInfo.dnses.slice(); this.linkInfo.pcscf = newLinkInfo.pcscf.slice(); this.linkInfo.mtu = newLinkInfo.mtu; } break; case NETWORK_STATE_DISCONNECTED: case NETWORK_STATE_UNKNOWN: if (this.state == NETWORK_STATE_CONNECTED) { // Notify first on unexpected data call disconnection. this.state = dataCallState; for (let i = 0; i < this.requestedNetworkIfaces.length; i++) { this.requestedNetworkIfaces[i].notifyRILNetworkInterface(); } } this.reset(); if (this.requestedNetworkIfaces.length > 0) { if (DEBUG) { this.debug("State is disconnected/unknown, but this DataCall is" + " requested."); } this.setup(); return; } break; } this.state = dataCallState; // Notify DataCallHandler about data call changed. this.dataCallHandler.notifyDataCallChanged(this); for (let i = 0; i < this.requestedNetworkIfaces.length; i++) { this.requestedNetworkIfaces[i].notifyRILNetworkInterface(); } }, // Helpers debug: function(aMsg) { dump("-*- DataCall[" + this.clientId + ":" + this.apnProfile.apn + "]: " + aMsg + "\n"); }, get connected() { return this.state == NETWORK_STATE_CONNECTED; }, isPermanentFail: function(aDataFailCause, aErrorMsg) { // Check ril.h for 'no retry' data call fail causes. if (aErrorMsg === RIL.GECKO_ERROR_RADIO_NOT_AVAILABLE || aErrorMsg === RIL.GECKO_ERROR_INVALID_PARAMETER || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_OPERATOR_BARRED || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_MISSING_UKNOWN_APN || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_UNKNOWN_PDP_ADDRESS_TYPE || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_USER_AUTHENTICATION || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_ACTIVATION_REJECT_GGSN || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_SERVICE_OPTION_NOT_SUPPORTED || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_NSAPI_IN_USE || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_ONLY_IPV4_ALLOWED || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_ONLY_IPV6_ALLOWED || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_PROTOCOL_ERRORS || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_RADIO_POWER_OFF || aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_TETHERED_CALL_ACTIVE) { return true; } return false; }, inRequestedTypes: function(aType) { for (let i = 0; i < this.requestedNetworkIfaces.length; i++) { if (this.requestedNetworkIfaces[i].info.type == aType) { return true; } } return false; }, canHandleApn: function(aApnSetting) { let isIdentical = this.apnProfile.apn == aApnSetting.apn && (this.apnProfile.user || '') == (aApnSetting.user || '') && (this.apnProfile.password || '') == (aApnSetting.password || '') && (this.apnProfile.authType || '') == (aApnSetting.authtype || ''); if (RILQUIRKS_HAVE_IPV6) { isIdentical = isIdentical && (this.apnProfile.protocol || '') == (aApnSetting.protocol || '') && (this.apnProfile.roaming_protocol || '') == (aApnSetting.roaming_protocol || ''); } return isIdentical; }, resetLinkInfo: function() { this.linkInfo.cid = null; this.linkInfo.ifname = null; this.linkInfo.addresses = []; this.linkInfo.dnses = []; this.linkInfo.gateways = []; this.linkInfo.pcscf = []; this.linkInfo.mtu = null; }, reset: function() { this.resetLinkInfo(); this.state = NETWORK_STATE_UNKNOWN; }, connect: function(aNetworkInterface) { if (DEBUG) this.debug("connect: " + aNetworkInterface.info.type); if (this.requestedNetworkIfaces.indexOf(aNetworkInterface) == -1) { this.requestedNetworkIfaces.push(aNetworkInterface); } if (this.state == NETWORK_STATE_CONNECTING || this.state == NETWORK_STATE_DISCONNECTING) { return; } if (this.state == NETWORK_STATE_CONNECTED) { // This needs to run asynchronously, to behave the same way as the case of // non-shared apn, see bug 1059110. Services.tm.currentThread.dispatch(() => { // Do not notify if state changed while this event was being dispatched, // the state probably was notified already or need not to be notified. if (aNetworkInterface.info.state == RIL.GECKO_NETWORK_STATE_CONNECTED) { aNetworkInterface.notifyRILNetworkInterface(); } }, Ci.nsIEventTarget.DISPATCH_NORMAL); return; } // If retry mechanism is running on background, stop it since we are going // to setup data call now. if (this.timer) { this.timer.cancel(); } this.setup(); }, setup: function() { if (DEBUG) { this.debug("Going to set up data connection with APN " + this.apnProfile.apn); } let connection = gMobileConnectionService.getItemByServiceId(this.clientId); let dataInfo = connection && connection.data; if (dataInfo == null || dataInfo.state != RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED || dataInfo.type == RIL.GECKO_MOBILE_CONNECTION_STATE_UNKNOWN) { return; } let radioTechType = dataInfo.type; let radioTechnology = RIL.GECKO_RADIO_TECH.indexOf(radioTechType); let authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(this.apnProfile.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.apnProfile.authtype + "', using '" + RIL.GECKO_DATACALL_AUTH_DEFAULT + "'"); } authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(RIL.GECKO_DATACALL_AUTH_DEFAULT); } let pdpType = Ci.nsIDataCallInterface.DATACALL_PDP_TYPE_IPV4; if (RILQUIRKS_HAVE_IPV6) { pdpType = !dataInfo.roaming ? RIL.RIL_DATACALL_PDP_TYPES.indexOf(this.apnProfile.protocol) : RIL.RIL_DATACALL_PDP_TYPES.indexOf(this.apnProfile.roaming_protocol); if (pdpType == -1) { if (DEBUG) { this.debug("Invalid pdpType '" + (!dataInfo.roaming ? this.apnProfile.protocol : this.apnProfile.roaming_protocol) + "', using '" + RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT + "'"); } pdpType = RIL.RIL_DATACALL_PDP_TYPES.indexOf(RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT); } } let dcInterface = this.dataCallHandler.dataCallInterface; dcInterface.setupDataCall( this.apnProfile.apn, this.apnProfile.user, this.apnProfile.password, authType, pdpType, { QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallCallback]), notifySetupDataCallSuccess: (aDataCall) => { this.onSetupDataCallResult(aDataCall); }, notifyError: (aErrorMsg) => { this.onSetupDataCallResult({errorMsg: aErrorMsg}); } }); this.state = NETWORK_STATE_CONNECTING; }, retry: function(aSuggestedRetryTime) { let apnRetryTimer; // 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; if (DEBUG) this.debug("Too many APN Connection retries - STOP retrying"); return; } // If there is a valid aSuggestedRetryTime, override the retry timer. if (aSuggestedRetryTime !== undefined && aSuggestedRetryTime >= 0) { apnRetryTimer = aSuggestedRetryTime / 1000; } else { 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(aNetworkInterface) { if (DEBUG) this.debug("disconnect: " + aNetworkInterface.info.type); let index = this.requestedNetworkIfaces.indexOf(aNetworkInterface); if (index != -1) { this.requestedNetworkIfaces.splice(index, 1); if (this.state == NETWORK_STATE_DISCONNECTED || this.state == NETWORK_STATE_UNKNOWN) { if (this.timer) { this.timer.cancel(); } this.reset(); return; } // Notify the DISCONNECTED event immediately after network interface is // removed from requestedNetworkIfaces, to make the DataCall, shared or // not, to have the same behavior. Services.tm.currentThread.dispatch(() => { // Do not notify if state changed while this event was being dispatched, // the state probably was notified already or need not to be notified. if (aNetworkInterface.info.state == RIL.GECKO_NETWORK_STATE_DISCONNECTED) { aNetworkInterface.notifyRILNetworkInterface(); // Clear link info after notifying NetworkManager. if (this.requestedNetworkIfaces.length === 0) { this.resetLinkInfo(); } } }, Ci.nsIEventTarget.DISPATCH_NORMAL); } // Only deactivate data call if no more network interface needs this // DataCall and if state is CONNECTED, for other states, we simply remove // the network interface from requestedNetworkIfaces. if (this.requestedNetworkIfaces.length > 0 || this.state != NETWORK_STATE_CONNECTED) { return; } this.deactivate(); }, deactivate: function() { let reason = Ci.nsIDataCallInterface.DATACALL_DEACTIVATE_NO_REASON; if (DEBUG) { this.debug("Going to disconnect data connection cid " + this.linkInfo.cid); } let dcInterface = this.dataCallHandler.dataCallInterface; dcInterface.deactivateDataCall(this.linkInfo.cid, reason, { QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallCallback]), notifySuccess: () => { this.onDeactivateDataCallResult(); }, notifyError: (aErrorMsg) => { this.onDeactivateDataCallResult(); } }); this.state = NETWORK_STATE_DISCONNECTING; }, // Entry method for timer events. Used to reconnect to a failed APN notify: function(aTimer) { this.setup(); }, shutdown: function() { if (this.timer) { this.timer.cancel(); this.timer = null; } } }; function RILNetworkInfo(aClientId, aType, aNetworkInterface) { this.serviceId = aClientId; this.type = aType; this.networkInterface = aNetworkInterface; } RILNetworkInfo.prototype = { classID: RILNETWORKINFO_CID, classInfo: XPCOMUtils.generateCI({classID: RILNETWORKINFO_CID, classDescription: "RILNetworkInfo", interfaces: [Ci.nsINetworkInfo, Ci.nsIRilNetworkInfo]}), QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo, Ci.nsIRilNetworkInfo]), networkInterface: null, getDataCall: function() { return this.networkInterface.dataCall; }, getApnSetting: function() { return this.networkInterface.apnSetting; }, debug: function(aMsg) { dump("-*- RILNetworkInfo[" + this.serviceId + ":" + this.type + "]: " + aMsg + "\n"); }, /** * nsINetworkInfo Implementation */ get state() { let dataCall = this.getDataCall(); if (!dataCall.inRequestedTypes(this.type)) { return NETWORK_STATE_DISCONNECTED; } return dataCall.state; }, type: null, get name() { return this.getDataCall().linkInfo.ifname; }, getAddresses: function(aIps, aPrefixLengths) { let addresses = this.getDataCall().linkInfo.addresses; let ips = []; let prefixLengths = []; for (let i = 0; i < addresses.length; i++) { let [ip, prefixLength] = addresses[i].split("/"); ips.push(ip); prefixLengths.push(prefixLength); } aIps.value = ips.slice(); aPrefixLengths.value = prefixLengths.slice(); return ips.length; }, getGateways: function(aCount) { let linkInfo = this.getDataCall().linkInfo; if (aCount) { aCount.value = linkInfo.gateways.length; } return linkInfo.gateways.slice(); }, getDnses: function(aCount) { let linkInfo = this.getDataCall().linkInfo; if (aCount) { aCount.value = linkInfo.dnses.length; } return linkInfo.dnses.slice(); }, /** * nsIRilNetworkInfo Implementation */ serviceId: 0, get iccId() { let icc = gIccService.getIccByServiceId(this.serviceId); let iccInfo = icc && icc.iccInfo; return iccInfo && iccInfo.iccid; }, get mmsc() { if (this.type != NETWORK_TYPE_MOBILE_MMS) { if (DEBUG) this.debug("Error! Only MMS network can get MMSC."); throw Cr.NS_ERROR_UNEXPECTED; } return this.getApnSetting().mmsc || ""; }, get mmsProxy() { if (this.type != NETWORK_TYPE_MOBILE_MMS) { if (DEBUG) this.debug("Error! Only MMS network can get MMS proxy."); throw Cr.NS_ERROR_UNEXPECTED; } return this.getApnSetting().mmsproxy || ""; }, get mmsPort() { if (this.type != NETWORK_TYPE_MOBILE_MMS) { if (DEBUG) this.debug("Error! Only MMS network can get MMS port."); throw Cr.NS_ERROR_UNEXPECTED; } // Note: Port 0 is reserved, so we treat it as invalid as well. // See http://www.iana.org/assignments/port-numbers return this.getApnSetting().mmsport || -1; }, getPcscf: function(aCount) { if (this.type != NETWORK_TYPE_MOBILE_IMS) { if (DEBUG) this.debug("Error! Only IMS network can get pcscf."); throw Cr.NS_ERROR_UNEXPECTED; } let linkInfo = this.getDataCall().linkInfo; if (aCount) { aCount.value = linkInfo.pcscf.length; } return linkInfo.pcscf.slice(); }, }; function RILNetworkInterface(aDataCallHandler, aType, aApnSetting, aDataCall) { if (!aDataCall) { throw new Error("No dataCall for RILNetworkInterface: " + type); } this.dataCallHandler = aDataCallHandler; this.enabled = false; this.dataCall = aDataCall; this.apnSetting = aApnSetting; this.info = new RILNetworkInfo(aDataCallHandler.clientId, aType, this); } RILNetworkInterface.prototype = { classID: RILNETWORKINTERFACE_CID, classInfo: XPCOMUtils.generateCI({classID: RILNETWORKINTERFACE_CID, classDescription: "RILNetworkInterface", interfaces: [Ci.nsINetworkInterface]}), QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]), // If this RILNetworkInterface type is enabled or not. enabled: null, apnSetting: null, dataCall: null, /** * nsINetworkInterface Implementation */ info: null, get httpProxyHost() { return this.apnSetting.proxy || ""; }, get httpProxyPort() { return this.apnSetting.port || ""; }, get mtu() { // Value provided by network has higher priority than apn settings. return this.dataCall.linkInfo.mtu || this.apnSetting.mtu || -1; }, // Helpers debug: function(aMsg) { dump("-*- RILNetworkInterface[" + this.dataCallHandler.clientId + ":" + this.info.type + "]: " + aMsg + "\n"); }, get connected() { return this.info.state == NETWORK_STATE_CONNECTED; }, notifyRILNetworkInterface: function() { if (DEBUG) { this.debug("notifyRILNetworkInterface type: " + this.info.type + ", state: " + this.info.state); } gNetworkManager.updateNetworkInterface(this); }, connect: function() { this.enabled = true; this.dataCall.connect(this); }, disconnect: function() { if (!this.enabled) { return; } this.enabled = false; this.dataCall.disconnect(this); }, shutdown: function() { this.dataCall.shutdown(); this.dataCall = null; } }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataCallManager]);