summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/TetheringService.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/gonk/TetheringService.js')
-rw-r--r--dom/system/gonk/TetheringService.js891
1 files changed, 891 insertions, 0 deletions
diff --git a/dom/system/gonk/TetheringService.js b/dom/system/gonk/TetheringService.js
new file mode 100644
index 000000000..c5c478180
--- /dev/null
+++ b/dom/system/gonk/TetheringService.js
@@ -0,0 +1,891 @@
+/* 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/FileUtils.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+const TETHERINGSERVICE_CONTRACTID = "@mozilla.org/tethering/service;1";
+const TETHERINGSERVICE_CID =
+ Components.ID("{527a4121-ee5a-4651-be9c-f46f59cf7c01}");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+ "@mozilla.org/network/manager;1",
+ "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+ "@mozilla.org/network/service;1",
+ "nsINetworkService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gMobileConnectionService",
+ "@mozilla.org/mobileconnection/mobileconnectionservice;1",
+ "nsIMobileConnectionService");
+
+XPCOMUtils.defineLazyGetter(this, "gRil", function() {
+ try {
+ return Cc["@mozilla.org/ril;1"].getService(Ci.nsIRadioInterfaceLayer);
+ } catch (e) {}
+
+ return null;
+});
+
+const TOPIC_MOZSETTINGS_CHANGED = "mozsettings-changed";
+const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
+const TOPIC_PREF_CHANGED = "nsPref:changed";
+const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown";
+const PREF_MANAGE_OFFLINE_STATUS = "network.gonk.manage-offline-status";
+const PREF_NETWORK_DEBUG_ENABLED = "network.debugging.enabled";
+
+const POSSIBLE_USB_INTERFACE_NAME = "rndis0,usb0";
+const DEFAULT_USB_INTERFACE_NAME = "rndis0";
+const DEFAULT_3G_INTERFACE_NAME = "rmnet0";
+const DEFAULT_WIFI_INTERFACE_NAME = "wlan0";
+
+// The kernel's proc entry for network lists.
+const KERNEL_NETWORK_ENTRY = "/sys/class/net";
+
+const TETHERING_TYPE_WIFI = "WiFi";
+const TETHERING_TYPE_USB = "USB";
+
+const WIFI_FIRMWARE_AP = "AP";
+const WIFI_FIRMWARE_STATION = "STA";
+const WIFI_SECURITY_TYPE_NONE = "open";
+const WIFI_SECURITY_TYPE_WPA_PSK = "wpa-psk";
+const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
+const WIFI_CTRL_INTERFACE = "wl0.1";
+
+const NETWORK_INTERFACE_UP = "up";
+const NETWORK_INTERFACE_DOWN = "down";
+
+const TETHERING_STATE_ONGOING = "ongoing";
+const TETHERING_STATE_IDLE = "idle";
+const TETHERING_STATE_ACTIVE = "active";
+
+// Settings DB path for USB tethering.
+const SETTINGS_USB_ENABLED = "tethering.usb.enabled";
+const SETTINGS_USB_IP = "tethering.usb.ip";
+const SETTINGS_USB_PREFIX = "tethering.usb.prefix";
+const SETTINGS_USB_DHCPSERVER_STARTIP = "tethering.usb.dhcpserver.startip";
+const SETTINGS_USB_DHCPSERVER_ENDIP = "tethering.usb.dhcpserver.endip";
+const SETTINGS_USB_DNS1 = "tethering.usb.dns1";
+const SETTINGS_USB_DNS2 = "tethering.usb.dns2";
+
+// Settings DB path for WIFI tethering.
+const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
+const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip";
+
+// Settings DB patch for dun required setting.
+const SETTINGS_DUN_REQUIRED = "tethering.dun.required";
+
+// Default value for USB tethering.
+const DEFAULT_USB_IP = "192.168.0.1";
+const DEFAULT_USB_PREFIX = "24";
+const DEFAULT_USB_DHCPSERVER_STARTIP = "192.168.0.10";
+const DEFAULT_USB_DHCPSERVER_ENDIP = "192.168.0.30";
+
+const DEFAULT_DNS1 = "8.8.8.8";
+const DEFAULT_DNS2 = "8.8.4.4";
+
+const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10";
+const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30";
+
+const SETTINGS_DATA_DEFAULT_SERVICE_ID = "ril.data.defaultServiceId";
+const MOBILE_DUN_CONNECT_TIMEOUT = 30000;
+const MOBILE_DUN_RETRY_INTERVAL = 5000;
+const MOBILE_DUN_MAX_RETRIES = 5;
+
+var debug;
+function updateDebug() {
+ let debugPref = false; // set default value here.
+ try {
+ debugPref = debugPref || Services.prefs.getBoolPref(PREF_NETWORK_DEBUG_ENABLED);
+ } catch (e) {}
+
+ if (debugPref) {
+ debug = function(s) {
+ dump("-*- TetheringService: " + s + "\n");
+ };
+ } else {
+ debug = function(s) {};
+ }
+}
+updateDebug();
+
+function TetheringService() {
+ Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false);
+ Services.obs.addObserver(this, TOPIC_MOZSETTINGS_CHANGED, false);
+ Services.obs.addObserver(this, TOPIC_CONNECTION_STATE_CHANGED, false);
+ Services.prefs.addObserver(PREF_NETWORK_DEBUG_ENABLED, this, false);
+ Services.prefs.addObserver(PREF_MANAGE_OFFLINE_STATUS, this, false);
+
+ try {
+ this._manageOfflineStatus =
+ Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
+ } catch(ex) {
+ // Ignore.
+ }
+
+ this._dataDefaultServiceId = 0;
+
+ // Possible usb tethering interfaces for different gonk platform.
+ this.possibleInterface = POSSIBLE_USB_INTERFACE_NAME.split(",");
+
+ // Default values for internal and external interfaces.
+ this._tetheringInterface = {};
+ this._tetheringInterface[TETHERING_TYPE_USB] = {
+ externalInterface: DEFAULT_3G_INTERFACE_NAME,
+ internalInterface: DEFAULT_USB_INTERFACE_NAME
+ };
+ this._tetheringInterface[TETHERING_TYPE_WIFI] = {
+ externalInterface: DEFAULT_3G_INTERFACE_NAME,
+ internalInterface: DEFAULT_WIFI_INTERFACE_NAME
+ };
+
+ this.tetheringSettings = {};
+ this.initTetheringSettings();
+
+ let settingsLock = gSettingsService.createLock();
+ // Read the default service id for data call.
+ settingsLock.get(SETTINGS_DATA_DEFAULT_SERVICE_ID, this);
+
+ // Read usb tethering data from settings DB.
+ settingsLock.get(SETTINGS_USB_IP, this);
+ settingsLock.get(SETTINGS_USB_PREFIX, this);
+ settingsLock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
+ settingsLock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
+ settingsLock.get(SETTINGS_USB_DNS1, this);
+ settingsLock.get(SETTINGS_USB_DNS2, this);
+ settingsLock.get(SETTINGS_USB_ENABLED, this);
+
+ // Read wifi tethering data from settings DB.
+ settingsLock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
+ settingsLock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
+
+ this._usbTetheringSettingsToRead = [SETTINGS_USB_IP,
+ SETTINGS_USB_PREFIX,
+ SETTINGS_USB_DHCPSERVER_STARTIP,
+ SETTINGS_USB_DHCPSERVER_ENDIP,
+ SETTINGS_USB_DNS1,
+ SETTINGS_USB_DNS2,
+ SETTINGS_USB_ENABLED,
+ SETTINGS_WIFI_DHCPSERVER_STARTIP,
+ SETTINGS_WIFI_DHCPSERVER_ENDIP];
+
+ this.wantConnectionEvent = null;
+
+ this.dunConnectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ this.dunRetryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ this._pendingTetheringRequests = [];
+}
+TetheringService.prototype = {
+ classID: TETHERINGSERVICE_CID,
+ classInfo: XPCOMUtils.generateCI({classID: TETHERINGSERVICE_CID,
+ contractID: TETHERINGSERVICE_CONTRACTID,
+ classDescription: "Tethering Service",
+ interfaces: [Ci.nsITetheringService]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITetheringService,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ Ci.nsISettingsServiceCallback]),
+
+ // Flag to record the default client id for data call.
+ _dataDefaultServiceId: null,
+
+ // Number of usb tehering requests to be processed.
+ _usbTetheringRequestCount: 0,
+
+ // Usb tethering state.
+ _usbTetheringAction: TETHERING_STATE_IDLE,
+
+ // Tethering settings.
+ tetheringSettings: null,
+
+ // Tethering settings need to be read from settings DB.
+ _usbTetheringSettingsToRead: null,
+
+ // Previous usb tethering enabled state.
+ _oldUsbTetheringEnabledState: null,
+
+ // External and internal interface name.
+ _tetheringInterface: null,
+
+ // Dun connection timer.
+ dunConnectTimer: null,
+
+ // Dun connection retry times.
+ dunRetryTimes: 0,
+
+ // Dun retry timer.
+ dunRetryTimer: null,
+
+ // Pending tethering request to handle after dun is connected.
+ _pendingTetheringRequests: null,
+
+ // Flag to indicate wether wifi tethering is being processed.
+ _wifiTetheringRequestOngoing: false,
+
+ // Arguments for pending wifi tethering request.
+ _pendingWifiTetheringRequestArgs: null,
+
+ // The state of tethering.
+ state: Ci.nsITetheringService.TETHERING_STATE_INACTIVE,
+
+ // Flag to check if we can modify the Services.io.offline.
+ _manageOfflineStatus: true,
+
+ // nsIObserver
+
+ observe: function(aSubject, aTopic, aData) {
+ let network;
+
+ switch(aTopic) {
+ case TOPIC_PREF_CHANGED:
+ if (aData === PREF_NETWORK_DEBUG_ENABLED) {
+ updateDebug();
+ }
+ break;
+ case TOPIC_MOZSETTINGS_CHANGED:
+ if ("wrappedJSObject" in aSubject) {
+ aSubject = aSubject.wrappedJSObject;
+ }
+ this.handle(aSubject.key, aSubject.value);
+ break;
+ case TOPIC_CONNECTION_STATE_CHANGED:
+ network = aSubject.QueryInterface(Ci.nsINetworkInfo);
+ debug("Network " + network.type + "/" + network.name +
+ " changed state to " + network.state);
+ this.onConnectionChanged(network);
+ break;
+ case TOPIC_XPCOM_SHUTDOWN:
+ Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
+ Services.obs.removeObserver(this, TOPIC_MOZSETTINGS_CHANGED);
+ Services.obs.removeObserver(this, TOPIC_CONNECTION_STATE_CHANGED);
+ Services.prefs.removeObserver(PREF_NETWORK_DEBUG_ENABLED, this);
+ Services.prefs.removeObserver(PREF_MANAGE_OFFLINE_STATUS, this);
+
+ this.dunConnectTimer.cancel();
+ this.dunRetryTimer.cancel();
+ break;
+ case PREF_MANAGE_OFFLINE_STATUS:
+ try {
+ this._manageOfflineStatus =
+ Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
+ } catch(ex) {
+ // Ignore.
+ }
+ break;
+ }
+ },
+
+ // nsISettingsServiceCallback
+
+ handle: function(aName, aResult) {
+ switch(aName) {
+ case SETTINGS_DATA_DEFAULT_SERVICE_ID:
+ this._dataDefaultServiceId = aResult || 0;
+ debug("'_dataDefaultServiceId' is now " + this._dataDefaultServiceId);
+ break;
+ case SETTINGS_USB_ENABLED:
+ this._oldUsbTetheringEnabledState = this.tetheringSettings[SETTINGS_USB_ENABLED];
+ case SETTINGS_USB_IP:
+ case SETTINGS_USB_PREFIX:
+ case SETTINGS_USB_DHCPSERVER_STARTIP:
+ case SETTINGS_USB_DHCPSERVER_ENDIP:
+ case SETTINGS_USB_DNS1:
+ case SETTINGS_USB_DNS2:
+ case SETTINGS_WIFI_DHCPSERVER_STARTIP:
+ case SETTINGS_WIFI_DHCPSERVER_ENDIP:
+ if (aResult !== null) {
+ this.tetheringSettings[aName] = aResult;
+ }
+ debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
+ let index = this._usbTetheringSettingsToRead.indexOf(aName);
+
+ if (index != -1) {
+ this._usbTetheringSettingsToRead.splice(index, 1);
+ }
+
+ if (this._usbTetheringSettingsToRead.length) {
+ debug("We haven't read completely the usb Tethering data from settings db.");
+ break;
+ }
+
+ if (this._oldUsbTetheringEnabledState === this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+ debug("No changes for SETTINGS_USB_ENABLED flag. Nothing to do.");
+ this.handlePendingWifiTetheringRequest();
+ break;
+ }
+
+ this._usbTetheringRequestCount++;
+ if (this._usbTetheringRequestCount === 1) {
+ if (this._wifiTetheringRequestOngoing) {
+ debug('USB tethering request is blocked by ongoing wifi tethering request.');
+ } else {
+ this.handleLastUsbTetheringRequest();
+ }
+ }
+ break;
+ };
+ },
+
+ handleError: function(aErrorMessage) {
+ debug("There was an error while reading Tethering settings.");
+ this.tetheringSettings = {};
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
+ },
+
+ initTetheringSettings: function() {
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
+ this.tetheringSettings[SETTINGS_USB_IP] = DEFAULT_USB_IP;
+ this.tetheringSettings[SETTINGS_USB_PREFIX] = DEFAULT_USB_PREFIX;
+ this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
+ this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
+ this.tetheringSettings[SETTINGS_USB_DNS1] = DEFAULT_DNS1;
+ this.tetheringSettings[SETTINGS_USB_DNS2] = DEFAULT_DNS2;
+
+ this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
+ this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
+
+ this.tetheringSettings[SETTINGS_DUN_REQUIRED] =
+ libcutils.property_get("ro.tethering.dun_required") === "1";
+ },
+
+ getNetworkInfo: function(aType, aServiceId) {
+ for (let networkId in gNetworkManager.allNetworkInfo) {
+ let networkInfo = gNetworkManager.allNetworkInfo[networkId];
+ if (networkInfo.type == aType) {
+ try {
+ if (networkInfo instanceof Ci.nsIRilNetworkInfo) {
+ let rilNetwork = networkInfo.QueryInterface(Ci.nsIRilNetworkInfo);
+ if (rilNetwork.serviceId != aServiceId) {
+ continue;
+ }
+ }
+ } catch (e) {}
+ return networkInfo;
+ }
+ }
+ return null;
+ },
+
+ handleLastUsbTetheringRequest: function() {
+ debug('handleLastUsbTetheringRequest... ' + this._usbTetheringRequestCount);
+
+ if (this._usbTetheringRequestCount === 0) {
+ if (this.wantConnectionEvent) {
+ if (this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+ this.wantConnectionEvent.call(this);
+ }
+ this.wantConnectionEvent = null;
+ }
+ this.handlePendingWifiTetheringRequest();
+ return;
+ }
+
+ // Cancel the accumlated count to 1 since we only care about the
+ // last state.
+ this._usbTetheringRequestCount = 1;
+ this.handleUSBTetheringToggle(this.tetheringSettings[SETTINGS_USB_ENABLED]);
+ this.wantConnectionEvent = null;
+ },
+
+ handlePendingWifiTetheringRequest: function() {
+ if (this._pendingWifiTetheringRequestArgs) {
+ this.setWifiTethering.apply(this, this._pendingWifiTetheringRequestArgs);
+ this._pendingWifiTetheringRequestArgs = null;
+ }
+ },
+
+ /**
+ * Callback when dun connection fails to connect within timeout.
+ */
+ onDunConnectTimerTimeout: function() {
+ while (this._pendingTetheringRequests.length > 0) {
+ debug("onDunConnectTimerTimeout: callback without network info.");
+ let callback = this._pendingTetheringRequests.shift();
+ if (typeof callback === 'function') {
+ callback();
+ }
+ }
+ },
+
+ setupDunConnection: function() {
+ this.dunRetryTimer.cancel();
+ let connection =
+ gMobileConnectionService.getItemByServiceId(this._dataDefaultServiceId);
+ let data = connection && connection.data;
+ if (data && data.state === "registered") {
+ let ril = gRil.getRadioInterface(this._dataDefaultServiceId);
+
+ this.dunRetryTimes = 0;
+ ril.setupDataCallByType(Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN);
+ this.dunConnectTimer.cancel();
+ this.dunConnectTimer.
+ initWithCallback(this.onDunConnectTimerTimeout.bind(this),
+ MOBILE_DUN_CONNECT_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
+ return;
+ }
+
+ if (this.dunRetryTimes++ >= this.MOBILE_DUN_MAX_RETRIES) {
+ debug("setupDunConnection: max retries reached.");
+ this.dunRetryTimes = 0;
+ // same as dun connect timeout.
+ this.onDunConnectTimerTimeout();
+ return;
+ }
+
+ debug("Data not ready, retry dun after " + MOBILE_DUN_RETRY_INTERVAL + " ms.");
+ this.dunRetryTimer.
+ initWithCallback(this.setupDunConnection.bind(this),
+ MOBILE_DUN_RETRY_INTERVAL, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ _dunActiveUsers: 0,
+ handleDunConnection: function(aEnable, aCallback) {
+ debug("handleDunConnection: " + aEnable);
+ let dun = this.getNetworkInfo(
+ Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN, this._dataDefaultServiceId);
+
+ if (!aEnable) {
+ this._dunActiveUsers--;
+ if (this._dunActiveUsers > 0) {
+ debug("Dun still needed by others, do not disconnect.")
+ return;
+ }
+
+ this.dunRetryTimes = 0;
+ this.dunRetryTimer.cancel();
+ this.dunConnectTimer.cancel();
+ this._pendingTetheringRequests = [];
+
+ if (dun && (dun.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED)) {
+ gRil.getRadioInterface(this._dataDefaultServiceId)
+ .deactivateDataCallByType(Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN);
+ }
+ return;
+ }
+
+ this._dunActiveUsers++;
+ if (!dun || (dun.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED)) {
+ debug("DUN data call inactive, setup dun data call!")
+ this._pendingTetheringRequests.push(aCallback);
+ this.dunRetryTimes = 0;
+ this.setupDunConnection();
+
+ return;
+ }
+
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = dun.name;
+ aCallback(dun);
+ },
+
+ handleUSBTetheringToggle: function(aEnable) {
+ debug("handleUSBTetheringToggle: " + aEnable);
+ if (aEnable &&
+ (this._usbTetheringAction === TETHERING_STATE_ONGOING ||
+ this._usbTetheringAction === TETHERING_STATE_ACTIVE)) {
+ debug("Usb tethering already connecting/connected.");
+ this._usbTetheringRequestCount = 0;
+ this.handlePendingWifiTetheringRequest();
+ return;
+ }
+
+ if (!aEnable &&
+ this._usbTetheringAction === TETHERING_STATE_IDLE) {
+ debug("Usb tethering already disconnected.");
+ this._usbTetheringRequestCount = 0;
+ this.handlePendingWifiTetheringRequest();
+ return;
+ }
+
+ if (!aEnable) {
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
+ gNetworkService.enableUsbRndis(false, this.enableUsbRndisResult.bind(this));
+ return;
+ }
+
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = true;
+ this._usbTetheringAction = TETHERING_STATE_ONGOING;
+
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(true, (aNetworkInfo) => {
+ if (!aNetworkInfo){
+ this.usbTetheringResultReport(aEnable, "Dun connection failed");
+ return;
+ }
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface =
+ aNetworkInfo.name;
+ gNetworkService.enableUsbRndis(true, this.enableUsbRndisResult.bind(this));
+ });
+ return;
+ }
+
+ if (gNetworkManager.activeNetworkInfo) {
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface =
+ gNetworkManager.activeNetworkInfo.name;
+ } else {
+ let mobile = this.getNetworkInfo(
+ Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, this._dataDefaultServiceId);
+ if (mobile && mobile.name) {
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = mobile.name;
+ }
+ }
+ gNetworkService.enableUsbRndis(true, this.enableUsbRndisResult.bind(this));
+ },
+
+ getUSBTetheringParameters: function(aEnable, aTetheringInterface) {
+ let interfaceIp = this.tetheringSettings[SETTINGS_USB_IP];
+ let prefix = this.tetheringSettings[SETTINGS_USB_PREFIX];
+ let wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
+ let wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
+ let usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP];
+ let usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP];
+ let dns1 = this.tetheringSettings[SETTINGS_USB_DNS1];
+ let dns2 = this.tetheringSettings[SETTINGS_USB_DNS2];
+ let internalInterface = aTetheringInterface.internalInterface;
+ let externalInterface = aTetheringInterface.externalInterface;
+
+ // Using the default values here until application support these settings.
+ if (interfaceIp == "" || prefix == "" ||
+ wifiDhcpStartIp == "" || wifiDhcpEndIp == "" ||
+ usbDhcpStartIp == "" || usbDhcpEndIp == "") {
+ debug("Invalid subnet information.");
+ return null;
+ }
+
+ return {
+ ifname: internalInterface,
+ ip: interfaceIp,
+ prefix: prefix,
+ wifiStartIp: wifiDhcpStartIp,
+ wifiEndIp: wifiDhcpEndIp,
+ usbStartIp: usbDhcpStartIp,
+ usbEndIp: usbDhcpEndIp,
+ dns1: dns1,
+ dns2: dns2,
+ internalIfname: internalInterface,
+ externalIfname: externalInterface,
+ enable: aEnable,
+ link: aEnable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
+ };
+ },
+
+ notifyError: function(aResetSettings, aCallback, aMsg) {
+ if (aResetSettings) {
+ let settingsLock = gSettingsService.createLock();
+ // Disable wifi tethering with a useful error message for the user.
+ settingsLock.set("tethering.wifi.enabled", false, null, aMsg);
+ }
+
+ debug("setWifiTethering: " + (aMsg ? aMsg : "success"));
+
+ if (aCallback) {
+ // Callback asynchronously to avoid netsted toggling.
+ Services.tm.currentThread.dispatch(() => {
+ aCallback.wifiTetheringEnabledChange(aMsg);
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+
+ enableWifiTethering: function(aEnable, aConfig, aCallback) {
+ // Fill in config's required fields.
+ aConfig.ifname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
+ aConfig.internalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
+ aConfig.externalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface;
+
+ this._wifiTetheringRequestOngoing = true;
+ gNetworkService.setWifiTethering(aEnable, aConfig, (aError) => {
+ // Change the tethering state to WIFI if there is no error.
+ if (aEnable && !aError) {
+ this.state = Ci.nsITetheringService.TETHERING_STATE_WIFI;
+ } else {
+ // If wifi thethering is disable, or any error happens,
+ // then consider the following statements.
+
+ // Check whether the state is USB now or not. If no then just change
+ // it to INACTIVE, if yes then just keep it.
+ // It means that don't let the disable or error of WIFI affect
+ // the original active state.
+ if (this.state != Ci.nsITetheringService.TETHERING_STATE_USB) {
+ this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
+ }
+
+ // Disconnect dun on error or when wifi tethering is disabled.
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(false);
+ }
+ }
+
+ if (this._manageOfflineStatus) {
+ Services.io.offline = !this.isAnyConnected() &&
+ (this.state ===
+ Ci.nsITetheringService.TETHERING_STATE_INACTIVE);
+ }
+
+ let resetSettings = aError;
+ debug('gNetworkService.setWifiTethering finished');
+ this.notifyError(resetSettings, aCallback, aError);
+ this._wifiTetheringRequestOngoing = false;
+ if (this._usbTetheringRequestCount > 0) {
+ debug('Perform pending USB tethering requests.');
+ this.handleLastUsbTetheringRequest();
+ }
+ });
+ },
+
+ // Enable/disable WiFi tethering by sending commands to netd.
+ setWifiTethering: function(aEnable, aInterfaceName, aConfig, aCallback) {
+ debug("setWifiTethering: " + aEnable);
+ if (!aInterfaceName) {
+ this.notifyError(true, aCallback, "invalid network interface name");
+ return;
+ }
+
+ if (!aConfig) {
+ this.notifyError(true, aCallback, "invalid configuration");
+ return;
+ }
+
+ if (this._usbTetheringRequestCount > 0) {
+ // If there's still pending usb tethering request, save
+ // the request params and redo |setWifiTethering| on
+ // usb tethering task complete.
+ debug('USB tethering request is being processed. Queue this wifi tethering request.');
+ this._pendingWifiTetheringRequestArgs = Array.prototype.slice.call(arguments);
+ debug('Pending args: ' + JSON.stringify(this._pendingWifiTetheringRequestArgs));
+ return;
+ }
+
+ // Re-check again, test cases set this property later.
+ this.tetheringSettings[SETTINGS_DUN_REQUIRED] =
+ libcutils.property_get("ro.tethering.dun_required") === "1";
+
+ if (!aEnable) {
+ this.enableWifiTethering(false, aConfig, aCallback);
+ return;
+ }
+
+ this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface =
+ aInterfaceName;
+
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(true, (aNetworkInfo) => {
+ if (!aNetworkInfo) {
+ this.notifyError(true, aCallback, "Dun connection failed");
+ return;
+ }
+ this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface =
+ aNetworkInfo.name;
+ this.enableWifiTethering(true, aConfig, aCallback);
+ });
+ return;
+ }
+
+ let mobile = this.getNetworkInfo(
+ Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, this._dataDefaultServiceId);
+ // Update the real interface name
+ if (mobile && mobile.name) {
+ this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface = mobile.name;
+ }
+
+ this.enableWifiTethering(true, aConfig, aCallback);
+ },
+
+ // Enable/disable USB tethering by sending commands to netd.
+ setUSBTethering: function(aEnable, aTetheringInterface, aCallback) {
+ let params = this.getUSBTetheringParameters(aEnable, aTetheringInterface);
+
+ if (params === null) {
+ gNetworkService.enableUsbRndis(false, function() {
+ this.usbTetheringResultReport(aEnable, "Invalid parameters");
+ });
+ return;
+ }
+
+ gNetworkService.setUSBTethering(aEnable, params, aCallback);
+ },
+
+ getUsbInterface: function() {
+ // Find the rndis interface.
+ for (let i = 0; i < this.possibleInterface.length; i++) {
+ try {
+ let file = new FileUtils.File(KERNEL_NETWORK_ENTRY + "/" +
+ this.possibleInterface[i]);
+ if (file.exists()) {
+ return this.possibleInterface[i];
+ }
+ } catch (e) {
+ debug("Not " + this.possibleInterface[i] + " interface.");
+ }
+ }
+ debug("Can't find rndis interface in possible lists.");
+ return DEFAULT_USB_INTERFACE_NAME;
+ },
+
+ enableUsbRndisResult: function(aSuccess, aEnable) {
+ if (aSuccess) {
+ // If enable is false, don't find usb interface cause it is already down,
+ // just use the internal interface in settings.
+ if (aEnable) {
+ this._tetheringInterface[TETHERING_TYPE_USB].internalInterface =
+ this.getUsbInterface();
+ }
+ this.setUSBTethering(aEnable,
+ this._tetheringInterface[TETHERING_TYPE_USB],
+ this.usbTetheringResultReport.bind(this, aEnable));
+ } else {
+ this.usbTetheringResultReport(aEnable, "enableUsbRndisResult failure");
+ throw new Error("failed to set USB Function to adb");
+ }
+ },
+
+ usbTetheringResultReport: function(aEnable, aError) {
+ this._usbTetheringRequestCount--;
+
+ let settingsLock = gSettingsService.createLock();
+
+ debug('usbTetheringResultReport callback. enable: ' + aEnable +
+ ', error: ' + aError);
+
+ // Disable tethering settings when fail to enable it.
+ if (aError) {
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
+ settingsLock.set("tethering.usb.enabled", false, null);
+ // Skip others request when we found an error.
+ this._usbTetheringRequestCount = 0;
+ this._usbTetheringAction = TETHERING_STATE_IDLE;
+ // If the thethering state is WIFI now, then just keep it,
+ // if not, just change the state to INACTIVE.
+ // It means that don't let the error of USB affect the original active state.
+ if (this.state != Ci.nsITetheringService.TETHERING_STATE_WIFI) {
+ this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
+ }
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(false);
+ }
+ } else {
+ if (aEnable) {
+ this._usbTetheringAction = TETHERING_STATE_ACTIVE;
+ this.state = Ci.nsITetheringService.TETHERING_STATE_USB;
+ } else {
+ this._usbTetheringAction = TETHERING_STATE_IDLE;
+ // If the state is now WIFI, don't let the disable of USB affect it.
+ if (this.state != Ci.nsITetheringService.TETHERING_STATE_WIFI) {
+ this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
+ }
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(false);
+ }
+ }
+
+ if (this._manageOfflineStatus) {
+ Services.io.offline = !this.isAnyConnected() &&
+ (this.state ===
+ Ci.nsITetheringService.TETHERING_STATE_INACTIVE);
+ }
+
+ this.handleLastUsbTetheringRequest();
+ }
+ },
+
+ onConnectionChangedReport: function(aSuccess, aExternalIfname) {
+ debug("onConnectionChangedReport result: success " + aSuccess);
+
+ if (aSuccess) {
+ // Update the external interface.
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface =
+ aExternalIfname;
+ debug("Change the interface name to " + aExternalIfname);
+ }
+ },
+
+ onConnectionChanged: function(aNetworkInfo) {
+ if (aNetworkInfo.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ debug("We are only interested in CONNECTED event");
+ return;
+ }
+
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] &&
+ aNetworkInfo.type === Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN) {
+ this.dunConnectTimer.cancel();
+ debug("DUN data call connected, process callbacks.");
+ while (this._pendingTetheringRequests.length > 0) {
+ let callback = this._pendingTetheringRequests.shift();
+ if (typeof callback === 'function') {
+ callback(aNetworkInfo);
+ }
+ }
+ return;
+ }
+
+ if (!this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+ debug("Usb tethering settings is not enabled");
+ return;
+ }
+
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] &&
+ aNetworkInfo.type === Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN &&
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface ===
+ aNetworkInfo.name) {
+ debug("Dun required and dun interface is the same");
+ return;
+ }
+
+ if (this._tetheringInterface[TETHERING_TYPE_USB].externalInterface ===
+ gNetworkManager.activeNetworkInfo.name) {
+ debug("The active interface is the same");
+ return;
+ }
+
+ let previous = {
+ internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
+ externalIfname: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface
+ };
+
+ let current = {
+ internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
+ externalIfname: aNetworkInfo.name
+ };
+
+ let callback = (() => {
+ // Update external network interface.
+ debug("Update upstream interface to " + aNetworkInfo.name);
+ gNetworkService.updateUpStream(previous, current,
+ this.onConnectionChangedReport.bind(this));
+ });
+
+ if (this._usbTetheringAction === TETHERING_STATE_ONGOING) {
+ debug("Postpone the event and handle it when state is idle.");
+ this.wantConnectionEvent = callback;
+ return;
+ }
+ this.wantConnectionEvent = null;
+
+ callback.call(this);
+ },
+
+ isAnyConnected: function() {
+ let allNetworkInfo = gNetworkManager.allNetworkInfo;
+ for (let networkId in allNetworkInfo) {
+ if (allNetworkInfo.hasOwnProperty(networkId) &&
+ allNetworkInfo[networkId].state === Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ return true;
+ }
+ }
+ return false;
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TetheringService]);