diff options
Diffstat (limited to 'dom/system/gonk/TetheringService.js')
-rw-r--r-- | dom/system/gonk/TetheringService.js | 891 |
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]); |