mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-10 11:54:40 +00:00
1726 lines
55 KiB
JavaScript
1726 lines
55 KiB
JavaScript
/* 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 <ifname> 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 <datacall>.<field>.
|
|
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]); |