/* 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]);