mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
24ab66bc98
--HG-- extra : commitid : DcjxshJqlKg extra : rebase_source : 23bc9a985f1e6d13e13837e31bb9b88b9be24d55
3929 lines
128 KiB
JavaScript
3929 lines
128 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
|
/* 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");
|
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
|
Cu.import("resource://gre/modules/WifiCommand.jsm");
|
|
Cu.import("resource://gre/modules/WifiNetUtil.jsm");
|
|
Cu.import("resource://gre/modules/WifiP2pManager.jsm");
|
|
Cu.import("resource://gre/modules/WifiP2pWorkerObserver.jsm");
|
|
|
|
var DEBUG = false; // set to true to show debug messages.
|
|
|
|
const WIFIWORKER_CONTRACTID = "@mozilla.org/wifi/worker;1";
|
|
const WIFIWORKER_CID = Components.ID("{a14e8977-d259-433a-a88d-58dd44657e5b}");
|
|
|
|
const WIFIWORKER_WORKER = "resource://gre/modules/wifi_worker.js";
|
|
|
|
const kMozSettingsChangedObserverTopic = "mozsettings-changed";
|
|
|
|
const MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
|
|
const MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
|
|
const MAX_RETRIES_ON_DHCP_FAILURE = 2;
|
|
|
|
// Settings DB path for wifi
|
|
const SETTINGS_WIFI_ENABLED = "wifi.enabled";
|
|
const SETTINGS_WIFI_DEBUG_ENABLED = "wifi.debugging.enabled";
|
|
// Settings DB path for Wifi tethering.
|
|
const SETTINGS_WIFI_TETHERING_ENABLED = "tethering.wifi.enabled";
|
|
const SETTINGS_WIFI_SSID = "tethering.wifi.ssid";
|
|
const SETTINGS_WIFI_SECURITY_TYPE = "tethering.wifi.security.type";
|
|
const SETTINGS_WIFI_SECURITY_PASSWORD = "tethering.wifi.security.password";
|
|
const SETTINGS_WIFI_IP = "tethering.wifi.ip";
|
|
const SETTINGS_WIFI_PREFIX = "tethering.wifi.prefix";
|
|
const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
|
|
const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip";
|
|
const SETTINGS_WIFI_DNS1 = "tethering.wifi.dns1";
|
|
const SETTINGS_WIFI_DNS2 = "tethering.wifi.dns2";
|
|
|
|
// Settings DB path for USB tethering.
|
|
const SETTINGS_USB_DHCPSERVER_STARTIP = "tethering.usb.dhcpserver.startip";
|
|
const SETTINGS_USB_DHCPSERVER_ENDIP = "tethering.usb.dhcpserver.endip";
|
|
|
|
// Default value for WIFI tethering.
|
|
const DEFAULT_WIFI_IP = "192.168.1.1";
|
|
const DEFAULT_WIFI_PREFIX = "24";
|
|
const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10";
|
|
const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30";
|
|
const DEFAULT_WIFI_SSID = "FirefoxHotspot";
|
|
const DEFAULT_WIFI_SECURITY_TYPE = "open";
|
|
const DEFAULT_WIFI_SECURITY_PASSWORD = "1234567890";
|
|
const DEFAULT_DNS1 = "8.8.8.8";
|
|
const DEFAULT_DNS2 = "8.8.4.4";
|
|
|
|
// Default value for USB tethering.
|
|
const DEFAULT_USB_DHCPSERVER_STARTIP = "192.168.0.10";
|
|
const DEFAULT_USB_DHCPSERVER_ENDIP = "192.168.0.30";
|
|
|
|
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 NETWORK_INTERFACE_UP = "up";
|
|
const NETWORK_INTERFACE_DOWN = "down";
|
|
|
|
const DEFAULT_WLAN_INTERFACE = "wlan0";
|
|
|
|
const DRIVER_READY_WAIT = 2000;
|
|
|
|
const SUPP_PROP = "init.svc.wpa_supplicant";
|
|
const WPA_SUPPLICANT = "wpa_supplicant";
|
|
const DHCP_PROP = "init.svc.dhcpcd";
|
|
const DHCP = "dhcpcd";
|
|
|
|
const MODE_ESS = 0;
|
|
const MODE_IBSS = 1;
|
|
|
|
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, "gTetheringService",
|
|
"@mozilla.org/tethering/service;1",
|
|
"nsITetheringService");
|
|
|
|
// A note about errors and error handling in this file:
|
|
// The libraries that we use in this file are intended for C code. For
|
|
// C code, it is natural to return -1 for errors and 0 for success.
|
|
// Therefore, the code that interacts directly with the worker uses this
|
|
// convention (note: command functions do get boolean results since the
|
|
// command always succeeds and we do a string/boolean check for the
|
|
// expected results).
|
|
var WifiManager = (function() {
|
|
var manager = {};
|
|
|
|
function getStartupPrefs() {
|
|
return {
|
|
sdkVersion: parseInt(libcutils.property_get("ro.build.version.sdk"), 10),
|
|
unloadDriverEnabled: libcutils.property_get("ro.moz.wifi.unloaddriver") === "1",
|
|
schedScanRecovery: libcutils.property_get("ro.moz.wifi.sched_scan_recover") === "false" ? false : true,
|
|
driverDelay: libcutils.property_get("ro.moz.wifi.driverDelay"),
|
|
p2pSupported: libcutils.property_get("ro.moz.wifi.p2p_supported") === "1",
|
|
eapSimSupported: libcutils.property_get("ro.moz.wifi.eapsim_supported") === "1",
|
|
ibssSupported: libcutils.property_get("ro.moz.wifi.ibss_supported", "true") === "true",
|
|
ifname: libcutils.property_get("wifi.interface")
|
|
};
|
|
}
|
|
|
|
let {sdkVersion, unloadDriverEnabled, schedScanRecovery,
|
|
driverDelay, p2pSupported, eapSimSupported, ibssSupported, ifname} = getStartupPrefs();
|
|
|
|
let capabilities = {
|
|
security: ["OPEN", "WEP", "WPA-PSK", "WPA-EAP"],
|
|
eapMethod: ["PEAP", "TTLS", "TLS"],
|
|
eapPhase2: ["MSCHAPV2"],
|
|
certificate: ["SERVER"],
|
|
mode: [MODE_ESS]
|
|
};
|
|
if (eapSimSupported) {
|
|
capabilities.eapMethod.unshift("SIM");
|
|
}
|
|
if (ibssSupported) {
|
|
capabilities.mode.push(MODE_IBSS);
|
|
}
|
|
|
|
let wifiListener = {
|
|
onWaitEvent: function(event, iface) {
|
|
if (manager.ifname === iface && handleEvent(event)) {
|
|
waitForEvent(iface);
|
|
} else if (p2pSupported) {
|
|
// Please refer to
|
|
// http://androidxref.com/4.4.2_r1/xref/frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java#519
|
|
// for interface event mux/demux rules. In short words, both
|
|
// 'p2p0' and 'p2p-' should go to Wifi P2P state machine.
|
|
if (WifiP2pManager.INTERFACE_NAME === iface || -1 !== iface.indexOf('p2p-')) {
|
|
// If the connection is closed, wifi.c::wifi_wait_for_event()
|
|
// will still return 'CTRL-EVENT-TERMINATING - connection closed'
|
|
// rather than blocking. So when we see this special event string,
|
|
// just return immediately.
|
|
const TERMINATED_EVENT = 'CTRL-EVENT-TERMINATING - connection closed';
|
|
if (-1 !== event.indexOf(TERMINATED_EVENT)) {
|
|
return;
|
|
}
|
|
p2pManager.handleEvent(event);
|
|
waitForEvent(iface);
|
|
}
|
|
}
|
|
},
|
|
|
|
onCommand: function(event, iface) {
|
|
onmessageresult(event, iface);
|
|
}
|
|
}
|
|
|
|
manager.ifname = ifname;
|
|
manager.connectToSupplicant = false;
|
|
// Emulator build runs to here.
|
|
// The debug() should only be used after WifiManager.
|
|
if (!ifname) {
|
|
manager.ifname = DEFAULT_WLAN_INTERFACE;
|
|
}
|
|
manager.schedScanRecovery = schedScanRecovery;
|
|
manager.driverDelay = driverDelay ? parseInt(driverDelay, 10) : DRIVER_READY_WAIT;
|
|
|
|
// Regular Wifi stuff.
|
|
var netUtil = WifiNetUtil(controlMessage);
|
|
var wifiCommand = WifiCommand(controlMessage, manager.ifname, sdkVersion);
|
|
|
|
// Wifi P2P stuff
|
|
var p2pManager;
|
|
if (p2pSupported) {
|
|
let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME, sdkVersion);
|
|
p2pManager = WifiP2pManager(p2pCommand, netUtil);
|
|
}
|
|
|
|
let wifiService = Cc["@mozilla.org/wifi/service;1"];
|
|
if (wifiService) {
|
|
wifiService = wifiService.getService(Ci.nsIWifiProxyService);
|
|
let interfaces = [manager.ifname];
|
|
if (p2pSupported) {
|
|
interfaces.push(WifiP2pManager.INTERFACE_NAME);
|
|
}
|
|
wifiService.start(wifiListener, interfaces, interfaces.length);
|
|
} else {
|
|
debug("No wifi service component available!");
|
|
}
|
|
|
|
// Callbacks to invoke when a reply arrives from the wifi service.
|
|
var controlCallbacks = Object.create(null);
|
|
var idgen = 0;
|
|
|
|
function controlMessage(obj, callback) {
|
|
var id = idgen++;
|
|
obj.id = id;
|
|
if (callback) {
|
|
controlCallbacks[id] = callback;
|
|
}
|
|
wifiService.sendCommand(obj, obj.iface);
|
|
}
|
|
|
|
let onmessageresult = function(data, iface) {
|
|
var id = data.id;
|
|
var callback = controlCallbacks[id];
|
|
if (callback) {
|
|
callback(data);
|
|
delete controlCallbacks[id];
|
|
}
|
|
}
|
|
|
|
// Polling the status worker
|
|
var recvErrors = 0;
|
|
|
|
function waitForEvent(iface) {
|
|
wifiService.waitForEvent(iface);
|
|
}
|
|
|
|
// Commands to the control worker.
|
|
|
|
var driverLoaded = false;
|
|
|
|
function loadDriver(callback) {
|
|
if (driverLoaded) {
|
|
callback(0);
|
|
return;
|
|
}
|
|
|
|
wifiCommand.loadDriver(function (status) {
|
|
driverLoaded = (status >= 0);
|
|
callback(status)
|
|
});
|
|
}
|
|
|
|
function unloadDriver(type, callback) {
|
|
if (!unloadDriverEnabled) {
|
|
// Unloading drivers is generally unnecessary and
|
|
// can trigger bugs in some drivers.
|
|
// On properly written drivers, bringing the interface
|
|
// down powers down the interface.
|
|
if (type === WIFI_FIRMWARE_STATION) {
|
|
notify("supplicantlost", { success: true });
|
|
}
|
|
callback(0);
|
|
return;
|
|
}
|
|
|
|
wifiCommand.unloadDriver(function(status) {
|
|
driverLoaded = (status < 0);
|
|
if (type === WIFI_FIRMWARE_STATION) {
|
|
notify("supplicantlost", { success: true });
|
|
}
|
|
callback(status);
|
|
});
|
|
}
|
|
|
|
// A note about background scanning:
|
|
// Normally, background scanning shouldn't be necessary as wpa_supplicant
|
|
// has the capability to automatically schedule its own scans at appropriate
|
|
// intervals. However, with some drivers, this appears to get stuck after
|
|
// three scans, so we enable the driver's background scanning to work around
|
|
// that when we're not connected to any network. This ensures that we'll
|
|
// automatically reconnect to networks if one falls out of range.
|
|
var reEnableBackgroundScan = false;
|
|
|
|
// NB: This is part of the internal API.
|
|
manager.backgroundScanEnabled = false;
|
|
function setBackgroundScan(enable, callback) {
|
|
var doEnable = (enable === "ON");
|
|
if (doEnable === manager.backgroundScanEnabled) {
|
|
callback(false, true);
|
|
return;
|
|
}
|
|
|
|
manager.backgroundScanEnabled = doEnable;
|
|
wifiCommand.setBackgroundScan(manager.backgroundScanEnabled, callback);
|
|
}
|
|
|
|
var scanModeActive = false;
|
|
|
|
function scan(forceActive, callback) {
|
|
if (forceActive && !scanModeActive) {
|
|
// Note: we ignore errors from doSetScanMode.
|
|
wifiCommand.doSetScanMode(true, function(ignore) {
|
|
setBackgroundScan("OFF", function(turned, ignore) {
|
|
reEnableBackgroundScan = turned;
|
|
manager.handlePreWifiScan();
|
|
wifiCommand.scan(function(ok) {
|
|
wifiCommand.doSetScanMode(false, function(ignore) {
|
|
// The result of scanCommand is the result of the actual SCAN
|
|
// request.
|
|
callback(ok);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
manager.handlePreWifiScan();
|
|
wifiCommand.scan(callback);
|
|
}
|
|
|
|
var debugEnabled = false;
|
|
|
|
function syncDebug() {
|
|
if (debugEnabled !== DEBUG) {
|
|
let wanted = DEBUG;
|
|
wifiCommand.setLogLevel(wanted ? "DEBUG" : "INFO", function(ok) {
|
|
if (ok)
|
|
debugEnabled = wanted;
|
|
});
|
|
if (p2pSupported && p2pManager) {
|
|
p2pManager.setDebug(DEBUG);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getDebugEnabled(callback) {
|
|
wifiCommand.getLogLevel(function(level) {
|
|
if (level === null) {
|
|
debug("Unable to get wpa_supplicant's log level");
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
var lines = level.split("\n");
|
|
for (let i = 0; i < lines.length; ++i) {
|
|
let match = /Current level: (.*)/.exec(lines[i]);
|
|
if (match) {
|
|
debugEnabled = match[1].toLowerCase() === "debug";
|
|
callback(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we're here, we didn't get the current level.
|
|
callback(false);
|
|
});
|
|
}
|
|
|
|
function setScanMode(setActive, callback) {
|
|
scanModeActive = setActive;
|
|
wifiCommand.doSetScanMode(setActive, callback);
|
|
}
|
|
|
|
var httpProxyConfig = Object.create(null);
|
|
|
|
/**
|
|
* Given a network, configure http proxy when using wifi.
|
|
* @param network A network object to update http proxy
|
|
* @param info Info should have following field:
|
|
* - httpProxyHost ip address of http proxy.
|
|
* - httpProxyPort port of http proxy, set 0 to use default port 8080.
|
|
* @param callback callback function.
|
|
*/
|
|
function configureHttpProxy(network, info, callback) {
|
|
if (!network)
|
|
return;
|
|
|
|
let networkKey = getNetworkKey(network);
|
|
|
|
if (!info || info.httpProxyHost === "") {
|
|
delete httpProxyConfig[networkKey];
|
|
} else {
|
|
httpProxyConfig[networkKey] = network;
|
|
httpProxyConfig[networkKey].httpProxyHost = info.httpProxyHost;
|
|
httpProxyConfig[networkKey].httpProxyPort = info.httpProxyPort;
|
|
}
|
|
|
|
callback(true);
|
|
}
|
|
|
|
function getHttpProxyNetwork(network) {
|
|
if (!network)
|
|
return null;
|
|
|
|
let networkKey = getNetworkKey(network);
|
|
return httpProxyConfig[networkKey];
|
|
}
|
|
|
|
function setHttpProxy(network) {
|
|
if (!network)
|
|
return;
|
|
|
|
// If we got here, arg network must be the currentNetwork, so we just update
|
|
// WifiNetworkInterface correspondingly and notify NetworkManager.
|
|
WifiNetworkInterface.httpProxyHost = network.httpProxyHost;
|
|
WifiNetworkInterface.httpProxyPort = network.httpProxyPort;
|
|
|
|
if (WifiNetworkInterface.info.state ==
|
|
Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
|
|
gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
|
|
}
|
|
}
|
|
|
|
var staticIpConfig = Object.create(null);
|
|
function setStaticIpMode(network, info, callback) {
|
|
let setNetworkKey = getNetworkKey(network);
|
|
let curNetworkKey = null;
|
|
let currentNetwork = Object.create(null);
|
|
currentNetwork.netId = manager.connectionInfo.id;
|
|
|
|
manager.getNetworkConfiguration(currentNetwork, function () {
|
|
curNetworkKey = getNetworkKey(currentNetwork);
|
|
|
|
// Add additional information to static ip configuration
|
|
// It is used to compatiable with information dhcp callback.
|
|
info.ipaddr = netHelpers.stringToIP(info.ipaddr_str);
|
|
info.gateway = netHelpers.stringToIP(info.gateway_str);
|
|
info.mask_str = netHelpers.ipToString(netHelpers.makeMask(info.maskLength));
|
|
|
|
// Optional
|
|
info.dns1 = netHelpers.stringToIP(info.dns1_str);
|
|
info.dns2 = netHelpers.stringToIP(info.dns2_str);
|
|
info.proxy = netHelpers.stringToIP(info.proxy_str);
|
|
|
|
staticIpConfig[setNetworkKey] = info;
|
|
|
|
// If the ssid of current connection is the same as configured ssid
|
|
// It means we need update current connection to use static IP address.
|
|
if (setNetworkKey == curNetworkKey) {
|
|
// Use configureInterface directly doesn't work, the network interface
|
|
// and routing table is changed but still cannot connect to network
|
|
// so the workaround here is disable interface the enable again to
|
|
// trigger network reconnect with static ip.
|
|
gNetworkService.disableInterface(manager.ifname, function (ok) {
|
|
gNetworkService.enableInterface(manager.ifname, function (ok) {
|
|
callback(ok);
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
callback(true);
|
|
});
|
|
}
|
|
|
|
var dhcpInfo = null;
|
|
|
|
function runStaticIp(ifname, key) {
|
|
debug("Run static ip");
|
|
|
|
// Read static ip information from settings.
|
|
let staticIpInfo;
|
|
|
|
if (!(key in staticIpConfig))
|
|
return;
|
|
|
|
staticIpInfo = staticIpConfig[key];
|
|
|
|
// Stop dhcpd when use static IP
|
|
if (dhcpInfo != null) {
|
|
netUtil.stopDhcp(manager.ifname, function() {});
|
|
}
|
|
|
|
// Set ip, mask length, gateway, dns to network interface
|
|
gNetworkService.configureInterface( { ifname: ifname,
|
|
ipaddr: staticIpInfo.ipaddr,
|
|
mask: staticIpInfo.maskLength,
|
|
gateway: staticIpInfo.gateway,
|
|
dns1: staticIpInfo.dns1,
|
|
dns2: staticIpInfo.dns2 }, function (data) {
|
|
netUtil.runIpConfig(ifname, staticIpInfo, function(data) {
|
|
dhcpInfo = data.info;
|
|
notify("networkconnected", data);
|
|
});
|
|
});
|
|
}
|
|
|
|
var suppressEvents = false;
|
|
function notify(eventName, eventObject) {
|
|
if (suppressEvents)
|
|
return;
|
|
var handler = manager["on" + eventName];
|
|
if (handler) {
|
|
if (!eventObject)
|
|
eventObject = ({});
|
|
handler.call(eventObject);
|
|
}
|
|
}
|
|
|
|
function notifyStateChange(fields) {
|
|
// If we're already in the COMPLETED state, we might receive events from
|
|
// the supplicant that tell us that we're re-authenticating or reminding
|
|
// us that we're associated to a network. In those cases, we don't need to
|
|
// do anything, so just ignore them.
|
|
if (manager.state === "COMPLETED" &&
|
|
fields.state !== "DISCONNECTED" &&
|
|
fields.state !== "INTERFACE_DISABLED" &&
|
|
fields.state !== "INACTIVE" &&
|
|
fields.state !== "SCANNING") {
|
|
return false;
|
|
}
|
|
|
|
// Stop background scanning if we're trying to connect to a network.
|
|
if (manager.backgroundScanEnabled &&
|
|
(fields.state === "ASSOCIATING" ||
|
|
fields.state === "ASSOCIATED" ||
|
|
fields.state === "FOUR_WAY_HANDSHAKE" ||
|
|
fields.state === "GROUP_HANDSHAKE" ||
|
|
fields.state === "COMPLETED")) {
|
|
setBackgroundScan("OFF", function() {});
|
|
}
|
|
|
|
fields.prevState = manager.state;
|
|
// Detect wpa_supplicant's loop iterations.
|
|
manager.supplicantLoopDetection(fields.prevState, fields.state);
|
|
notify("statechange", fields);
|
|
|
|
// Don't update state when and after disabling.
|
|
if (manager.state === "DISABLING" ||
|
|
manager.state === "UNINITIALIZED") {
|
|
return false;
|
|
}
|
|
|
|
manager.state = fields.state;
|
|
return true;
|
|
}
|
|
|
|
function parseStatus(status) {
|
|
if (status === null) {
|
|
debug("Unable to get wpa supplicant's status");
|
|
return;
|
|
}
|
|
|
|
var ssid;
|
|
var bssid;
|
|
var state;
|
|
var ip_address;
|
|
var id;
|
|
|
|
var lines = status.split("\n");
|
|
for (let i = 0; i < lines.length; ++i) {
|
|
let [key, value] = lines[i].split("=");
|
|
switch (key) {
|
|
case "wpa_state":
|
|
state = value;
|
|
break;
|
|
case "ssid":
|
|
ssid = value;
|
|
break;
|
|
case "bssid":
|
|
bssid = value;
|
|
break;
|
|
case "ip_address":
|
|
ip_address = value;
|
|
break;
|
|
case "id":
|
|
id = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bssid && ssid) {
|
|
manager.connectionInfo.bssid = bssid;
|
|
manager.connectionInfo.ssid = ssid;
|
|
manager.connectionInfo.id = id;
|
|
}
|
|
|
|
if (ip_address)
|
|
dhcpInfo = { ip_address: ip_address };
|
|
|
|
notifyStateChange({ state: state, fromStatus: true });
|
|
|
|
// If we parse the status and the supplicant has already entered the
|
|
// COMPLETED state, then we need to set up DHCP right away.
|
|
if (state === "COMPLETED")
|
|
onconnected();
|
|
}
|
|
|
|
// try to connect to the supplicant
|
|
var connectTries = 0;
|
|
var retryTimer = null;
|
|
function connectCallback(ok) {
|
|
if (ok === 0) {
|
|
// Tell the event worker to start waiting for events.
|
|
retryTimer = null;
|
|
connectTries = 0;
|
|
recvErrors = 0;
|
|
manager.connectToSupplicant = true;
|
|
didConnectSupplicant(function(){});
|
|
return;
|
|
}
|
|
if (connectTries++ < 5) {
|
|
// Try again in 1 seconds.
|
|
if (!retryTimer)
|
|
retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
|
|
retryTimer.initWithCallback(function(timer) {
|
|
wifiCommand.connectToSupplicant(connectCallback);
|
|
}, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
|
|
return;
|
|
}
|
|
|
|
retryTimer = null;
|
|
connectTries = 0;
|
|
notify("supplicantlost", { success: false });
|
|
}
|
|
|
|
manager.connectionDropped = function(callback) {
|
|
// Reset network interface when connection drop
|
|
gNetworkService.configureInterface( { ifname: manager.ifname,
|
|
ipaddr: 0,
|
|
mask: 0,
|
|
gateway: 0,
|
|
dns1: 0,
|
|
dns2: 0 }, function (data) {
|
|
});
|
|
|
|
// If we got disconnected, kill the DHCP client in preparation for
|
|
// reconnection.
|
|
gNetworkService.resetConnections(manager.ifname, function() {
|
|
netUtil.stopDhcp(manager.ifname, function() {
|
|
callback();
|
|
});
|
|
});
|
|
}
|
|
|
|
manager.start = function() {
|
|
debug("detected SDK version " + sdkVersion);
|
|
wifiCommand.connectToSupplicant(connectCallback);
|
|
}
|
|
|
|
let dhcpRequestGen = 0;
|
|
|
|
function onconnected() {
|
|
// For now we do our own DHCP. In the future, this should be handed
|
|
// off to the Network Manager.
|
|
let currentNetwork = Object.create(null);
|
|
currentNetwork.netId = manager.connectionInfo.id;
|
|
|
|
manager.getNetworkConfiguration(currentNetwork, function (){
|
|
let key = getNetworkKey(currentNetwork);
|
|
if (staticIpConfig &&
|
|
(key in staticIpConfig) &&
|
|
staticIpConfig[key].enabled) {
|
|
debug("Run static ip");
|
|
runStaticIp(manager.ifname, key);
|
|
return;
|
|
}
|
|
netUtil.runDhcp(manager.ifname, dhcpRequestGen++, function(data, gen) {
|
|
dhcpInfo = data.info;
|
|
debug('dhcpRequestGen: ' + dhcpRequestGen + ', gen: ' + gen);
|
|
if (!dhcpInfo) {
|
|
if (gen + 1 < dhcpRequestGen) {
|
|
debug('Do not bother younger DHCP request.');
|
|
return;
|
|
}
|
|
if (++manager.dhcpFailuresCount >= MAX_RETRIES_ON_DHCP_FAILURE) {
|
|
manager.dhcpFailuresCount = 0;
|
|
notify("disconnected", {connectionInfo: manager.connectionInfo});
|
|
return;
|
|
}
|
|
// NB: We have to call disconnect first. Otherwise, we only reauth with
|
|
// the existing AP and don't retrigger DHCP.
|
|
manager.disconnect(function() {
|
|
manager.reassociate(function(){});
|
|
});
|
|
return;
|
|
}
|
|
|
|
manager.dhcpFailuresCount = 0;
|
|
notify("networkconnected", data);
|
|
});
|
|
});
|
|
}
|
|
|
|
var supplicantStatesMap = (sdkVersion >= 15) ?
|
|
["DISCONNECTED", "INTERFACE_DISABLED", "INACTIVE", "SCANNING",
|
|
"AUTHENTICATING", "ASSOCIATING", "ASSOCIATED", "FOUR_WAY_HANDSHAKE",
|
|
"GROUP_HANDSHAKE", "COMPLETED"]
|
|
:
|
|
["DISCONNECTED", "INACTIVE", "SCANNING", "ASSOCIATING",
|
|
"ASSOCIATED", "FOUR_WAY_HANDSHAKE", "GROUP_HANDSHAKE",
|
|
"COMPLETED", "DORMANT", "UNINITIALIZED"];
|
|
|
|
var driverEventMap = { STOPPED: "driverstopped", STARTED: "driverstarted", HANGED: "driverhung" };
|
|
|
|
manager.getNetworkId = function (ssid, callback) {
|
|
manager.getConfiguredNetworks(function(networks) {
|
|
if (!networks) {
|
|
debug("Unable to get configured networks");
|
|
return callback(null);
|
|
}
|
|
for (let net in networks) {
|
|
let network = networks[net];
|
|
// Trying to get netId from
|
|
// 1. network matching SSID if SSID is provided.
|
|
// 2. current network if no SSID is provided, it's not guaranteed that
|
|
// current network matches requested SSID.
|
|
if ((!ssid && network.status === "CURRENT") ||
|
|
(ssid && network.ssid && ssid === dequote(network.ssid))) {
|
|
return callback(net);
|
|
}
|
|
}
|
|
callback(null);
|
|
});
|
|
}
|
|
|
|
function handleWpaEapEvents(event) {
|
|
if (event.indexOf("CTRL-EVENT-EAP-FAILURE") !== -1) {
|
|
if (event.indexOf("EAP authentication failed") !== -1) {
|
|
notify("passwordmaybeincorrect");
|
|
if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
|
|
manager.authenticationFailuresCount = 0;
|
|
notify("disconnected", {connectionInfo: manager.connectionInfo});
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (event.indexOf("CTRL-EVENT-EAP-TLS-CERT-ERROR") !== -1) {
|
|
// Cert Error
|
|
notify("passwordmaybeincorrect");
|
|
if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
|
|
manager.authenticationFailuresCount = 0;
|
|
notify("disconnected", {connectionInfo: manager.connectionInfo});
|
|
}
|
|
return true;
|
|
}
|
|
if (event.indexOf("CTRL-EVENT-EAP-STARTED") !== -1) {
|
|
notifyStateChange({ state: "AUTHENTICATING" });
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Handle events sent to us by the event worker.
|
|
function handleEvent(event) {
|
|
debug("Event coming in: " + event);
|
|
if (event.indexOf("CTRL-EVENT-") !== 0 && event.indexOf("WPS") !== 0) {
|
|
// Handle connection fail exception on WEP-128, while password length
|
|
// is not 5 nor 13 bytes.
|
|
if (event.indexOf("Association request to the driver failed") !== -1) {
|
|
notify("passwordmaybeincorrect");
|
|
if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
|
|
manager.authenticationFailuresCount = 0;
|
|
notify("disconnected", {connectionInfo: manager.connectionInfo});
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (event.indexOf("WPA:") == 0 &&
|
|
event.indexOf("pre-shared key may be incorrect") != -1) {
|
|
notify("passwordmaybeincorrect");
|
|
}
|
|
|
|
// This is ugly, but we need to grab the SSID here. BSSID is not guaranteed
|
|
// to be provided, so don't grab BSSID here.
|
|
var match = /Trying to associate with.*SSID[ =]'(.*)'/.exec(event);
|
|
if (match) {
|
|
debug("Matched: " + match[1] + "\n");
|
|
manager.connectionInfo.ssid = match[1];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
var space = event.indexOf(" ");
|
|
var eventData = event.substr(0, space + 1);
|
|
if (eventData.indexOf("CTRL-EVENT-STATE-CHANGE") === 0) {
|
|
// Parse the event data.
|
|
var fields = {};
|
|
var tokens = event.substr(space + 1).split(" ");
|
|
for (var n = 0; n < tokens.length; ++n) {
|
|
var kv = tokens[n].split("=");
|
|
if (kv.length === 2)
|
|
fields[kv[0]] = kv[1];
|
|
}
|
|
if (!("state" in fields))
|
|
return true;
|
|
fields.state = supplicantStatesMap[fields.state];
|
|
|
|
// The BSSID field is only valid in the ASSOCIATING and ASSOCIATED
|
|
// states, except when we "reauth", except this seems to depend on the
|
|
// driver, so simply check to make sure that we don't have a null BSSID.
|
|
if (fields.BSSID !== "00:00:00:00:00:00")
|
|
manager.connectionInfo.bssid = fields.BSSID;
|
|
|
|
if (notifyStateChange(fields) && fields.state === "COMPLETED") {
|
|
onconnected();
|
|
}
|
|
return true;
|
|
}
|
|
if (eventData.indexOf("CTRL-EVENT-DRIVER-STATE") === 0) {
|
|
var handlerName = driverEventMap[eventData];
|
|
if (handlerName)
|
|
notify(handlerName);
|
|
return true;
|
|
}
|
|
if (eventData.indexOf("CTRL-EVENT-TERMINATING") === 0) {
|
|
// As long the monitor socket is not closed and we haven't seen too many
|
|
// recv errors yet, we will keep going for a bit longer.
|
|
if (event.indexOf("connection closed") === -1 &&
|
|
event.indexOf("recv error") !== -1 && ++recvErrors < 10)
|
|
return true;
|
|
|
|
notifyStateChange({ state: "DISCONNECTED", BSSID: null, id: -1 });
|
|
|
|
// If the supplicant is terminated as commanded, the supplicant lost
|
|
// notification will be sent after driver unloaded. In such case, the
|
|
// manager state will be "DISABLING" or "UNINITIALIZED".
|
|
// So if supplicant terminated with incorrect manager state, implying
|
|
// unexpected condition, we should notify supplicant lost here.
|
|
if (manager.state !== "DISABLING" && manager.state !== "UNINITIALIZED") {
|
|
notify("supplicantlost", { success: true });
|
|
}
|
|
|
|
if (manager.stopSupplicantCallback) {
|
|
cancelWaitForTerminateEventTimer();
|
|
// It's possible that the terminating event triggered by timer comes
|
|
// earlier than the event from wpa_supplicant. Since
|
|
// stopSupplicantCallback contains async. callbacks, swap it to local
|
|
// to prevent calling the callback twice.
|
|
let stopSupplicantCallback = manager.stopSupplicantCallback.bind(manager);
|
|
manager.stopSupplicantCallback = null;
|
|
stopSupplicantCallback();
|
|
stopSupplicantCallback = null;
|
|
}
|
|
return false;
|
|
}
|
|
if (eventData.indexOf("CTRL-EVENT-DISCONNECTED") === 0) {
|
|
var token = event.split(" ")[1];
|
|
var bssid = token.split("=")[1];
|
|
if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
|
|
manager.authenticationFailuresCount = 0;
|
|
notify("disconnected", {connectionInfo: manager.connectionInfo});
|
|
}
|
|
manager.connectionInfo.bssid = null;
|
|
manager.connectionInfo.ssid = null;
|
|
manager.connectionInfo.id = -1;
|
|
return true;
|
|
}
|
|
if (eventData.indexOf("CTRL-EVENT-CONNECTED") === 0) {
|
|
// Format: CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]
|
|
var bssid = event.split(" ")[4];
|
|
|
|
var keyword = "id=";
|
|
var id = event.substr(event.indexOf(keyword) + keyword.length).split(" ")[0];
|
|
// Read current BSSID here, it will always being provided.
|
|
manager.connectionInfo.id = id;
|
|
manager.connectionInfo.bssid = bssid;
|
|
return true;
|
|
}
|
|
if (eventData.indexOf("CTRL-EVENT-SCAN-RESULTS") === 0) {
|
|
debug("Notifying of scan results available");
|
|
if (reEnableBackgroundScan) {
|
|
reEnableBackgroundScan = false;
|
|
setBackgroundScan("ON", function() {});
|
|
}
|
|
manager.handlePostWifiScan();
|
|
notify("scanresultsavailable");
|
|
return true;
|
|
}
|
|
if (eventData.indexOf("CTRL-EVENT-EAP") === 0) {
|
|
return handleWpaEapEvents(event);
|
|
}
|
|
if (eventData.indexOf("CTRL-EVENT-ASSOC-REJECT") === 0) {
|
|
debug("CTRL-EVENT-ASSOC-REJECT: network error");
|
|
notify("passwordmaybeincorrect");
|
|
if (manager.authenticationFailuresCount > MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
|
|
manager.authenticationFailuresCount = 0;
|
|
debug("CTRL-EVENT-ASSOC-REJECT: disconnect network");
|
|
notify("disconnected", {connectionInfo: manager.connectionInfo});
|
|
}
|
|
return true;
|
|
}
|
|
if (eventData.indexOf("WPS-TIMEOUT") === 0) {
|
|
notifyStateChange({ state: "WPS_TIMEOUT", BSSID: null, id: -1 });
|
|
return true;
|
|
}
|
|
if (eventData.indexOf("WPS-FAIL") === 0) {
|
|
notifyStateChange({ state: "WPS_FAIL", BSSID: null, id: -1 });
|
|
return true;
|
|
}
|
|
if (eventData.indexOf("WPS-OVERLAP-DETECTED") === 0) {
|
|
notifyStateChange({ state: "WPS_OVERLAP_DETECTED", BSSID: null, id: -1 });
|
|
return true;
|
|
}
|
|
// Unknown event.
|
|
return true;
|
|
}
|
|
|
|
function setPowerSavingMode(enabled) {
|
|
let mode = enabled ? "AUTO" : "ACTIVE";
|
|
// Some wifi drivers may not implement this command. Set power mode
|
|
// even if suspend optimization command failed.
|
|
manager.setSuspendOptimizations(enabled, function(ok) {
|
|
manager.setPowerMode(mode, function() {});
|
|
});
|
|
}
|
|
|
|
function didConnectSupplicant(callback) {
|
|
waitForEvent(manager.ifname);
|
|
|
|
// Load up the supplicant state.
|
|
getDebugEnabled(function(ok) {
|
|
syncDebug();
|
|
});
|
|
wifiCommand.status(function(status) {
|
|
parseStatus(status);
|
|
notify("supplicantconnection");
|
|
callback();
|
|
});
|
|
// WPA supplicant already connected.
|
|
manager.setPowerSavingMode(true);
|
|
if (p2pSupported) {
|
|
manager.enableP2p(function(success) {});
|
|
}
|
|
}
|
|
|
|
function prepareForStartup(callback) {
|
|
let status = libcutils.property_get(DHCP_PROP + "_" + manager.ifname);
|
|
if (status !== "running") {
|
|
tryStopSupplicant();
|
|
return;
|
|
}
|
|
manager.connectionDropped(function() {
|
|
tryStopSupplicant();
|
|
});
|
|
|
|
// Ignore any errors and kill any currently-running supplicants. On some
|
|
// phones, stopSupplicant won't work for a supplicant that we didn't
|
|
// start, so we hand-roll it here.
|
|
function tryStopSupplicant () {
|
|
let status = libcutils.property_get(SUPP_PROP);
|
|
if (status !== "running") {
|
|
callback();
|
|
return;
|
|
}
|
|
suppressEvents = true;
|
|
wifiCommand.killSupplicant(function() {
|
|
gNetworkService.disableInterface(manager.ifname, function (ok) {
|
|
suppressEvents = false;
|
|
callback();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// Initial state.
|
|
manager.state = "UNINITIALIZED";
|
|
manager.tetheringState = "UNINITIALIZED";
|
|
manager.supplicantStarted = false;
|
|
manager.connectionInfo = { ssid: null, bssid: null, id: -1 };
|
|
manager.authenticationFailuresCount = 0;
|
|
manager.loopDetectionCount = 0;
|
|
manager.dhcpFailuresCount = 0;
|
|
manager.stopSupplicantCallback = null;
|
|
|
|
manager.__defineGetter__("enabled", function() {
|
|
switch (manager.state) {
|
|
case "UNINITIALIZED":
|
|
case "INITIALIZING":
|
|
case "DISABLING":
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
});
|
|
|
|
var waitForTerminateEventTimer = null;
|
|
function cancelWaitForTerminateEventTimer() {
|
|
if (waitForTerminateEventTimer) {
|
|
waitForTerminateEventTimer.cancel();
|
|
waitForTerminateEventTimer = null;
|
|
}
|
|
};
|
|
function createWaitForTerminateEventTimer(onTimeout) {
|
|
if (waitForTerminateEventTimer) {
|
|
return;
|
|
}
|
|
waitForTerminateEventTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
waitForTerminateEventTimer.initWithCallback(onTimeout,
|
|
4000,
|
|
Ci.nsITimer.TYPE_ONE_SHOT);
|
|
};
|
|
|
|
var waitForDriverReadyTimer = null;
|
|
function cancelWaitForDriverReadyTimer() {
|
|
if (waitForDriverReadyTimer) {
|
|
waitForDriverReadyTimer.cancel();
|
|
waitForDriverReadyTimer = null;
|
|
}
|
|
};
|
|
function createWaitForDriverReadyTimer(onTimeout) {
|
|
waitForDriverReadyTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
waitForDriverReadyTimer.initWithCallback(onTimeout,
|
|
manager.driverDelay,
|
|
Ci.nsITimer.TYPE_ONE_SHOT);
|
|
};
|
|
|
|
// Public interface of the wifi service.
|
|
manager.setWifiEnabled = function(enabled, callback) {
|
|
if (enabled === manager.isWifiEnabled(manager.state)) {
|
|
callback("no change");
|
|
return;
|
|
}
|
|
|
|
if (enabled) {
|
|
manager.state = "INITIALIZING";
|
|
// Register as network interface.
|
|
WifiNetworkInterface.info.name = manager.ifname;
|
|
if (!WifiNetworkInterface.registered) {
|
|
gNetworkManager.registerNetworkInterface(WifiNetworkInterface);
|
|
WifiNetworkInterface.registered = true;
|
|
}
|
|
WifiNetworkInterface.info.state =
|
|
Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
|
|
WifiNetworkInterface.info.ips = [];
|
|
WifiNetworkInterface.info.prefixLengths = [];
|
|
WifiNetworkInterface.info.gateways = [];
|
|
WifiNetworkInterface.info.dnses = [];
|
|
gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
|
|
|
|
prepareForStartup(function() {
|
|
loadDriver(function (status) {
|
|
if (status < 0) {
|
|
callback(status);
|
|
manager.state = "UNINITIALIZED";
|
|
return;
|
|
}
|
|
// This command is mandatory for Nexus 4. But some devices like
|
|
// Galaxy S2 don't support it. Continue to start wpa_supplicant
|
|
// even if we fail to set wifi operation mode to station.
|
|
gNetworkService.setWifiOperationMode(manager.ifname,
|
|
WIFI_FIRMWARE_STATION,
|
|
function (status) {
|
|
|
|
function startSupplicantInternal() {
|
|
wifiCommand.startSupplicant(function (status) {
|
|
if (status < 0) {
|
|
unloadDriver(WIFI_FIRMWARE_STATION, function() {
|
|
callback(status);
|
|
});
|
|
manager.state = "UNINITIALIZED";
|
|
return;
|
|
}
|
|
|
|
manager.supplicantStarted = true;
|
|
gNetworkService.enableInterface(manager.ifname, function (ok) {
|
|
callback(ok ? 0 : -1);
|
|
});
|
|
});
|
|
}
|
|
|
|
function doStartSupplicant() {
|
|
cancelWaitForDriverReadyTimer();
|
|
|
|
if (!manager.connectToSupplicant) {
|
|
startSupplicantInternal();
|
|
return;
|
|
}
|
|
wifiCommand.closeSupplicantConnection(function () {
|
|
manager.connectToSupplicant = false;
|
|
// closeSupplicantConnection() will trigger onsupplicantlost
|
|
// and set manager.state to "UNINITIALIZED", we have to
|
|
// restore it here.
|
|
manager.state = "INITIALIZING";
|
|
startSupplicantInternal();
|
|
});
|
|
}
|
|
// Driver startup on certain platforms takes longer than it takes for us
|
|
// to return from loadDriver, so wait 2 seconds before starting
|
|
// the supplicant to give it a chance to start.
|
|
if (manager.driverDelay > 0) {
|
|
createWaitForDriverReadyTimer(doStartSupplicant);
|
|
} else {
|
|
doStartSupplicant();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
} else {
|
|
manager.state = "DISABLING";
|
|
// Note these following calls ignore errors. If we fail to kill the
|
|
// supplicant gracefully, then we need to continue telling it to die
|
|
// until it does.
|
|
let doDisableWifi = function() {
|
|
manager.stopSupplicantCallback = (function () {
|
|
wifiCommand.stopSupplicant(function (status) {
|
|
wifiCommand.closeSupplicantConnection(function() {
|
|
manager.connectToSupplicant = false;
|
|
manager.state = "UNINITIALIZED";
|
|
gNetworkService.disableInterface(manager.ifname, function (ok) {
|
|
unloadDriver(WIFI_FIRMWARE_STATION, callback);
|
|
});
|
|
});
|
|
});
|
|
}).bind(this);
|
|
|
|
let terminateEventCallback = (function() {
|
|
handleEvent("CTRL-EVENT-TERMINATING");
|
|
}).bind(this);
|
|
createWaitForTerminateEventTimer(terminateEventCallback);
|
|
|
|
// We are going to terminate the connection between wpa_supplicant.
|
|
// Stop the polling timer immediately to prevent connection info update
|
|
// command blocking in control thread until socket timeout.
|
|
notify("stopconnectioninfotimer");
|
|
|
|
wifiCommand.terminateSupplicant(function (ok) {
|
|
manager.connectionDropped(function () {
|
|
});
|
|
});
|
|
}
|
|
|
|
if (p2pSupported) {
|
|
p2pManager.setEnabled(false, { onDisabled: doDisableWifi });
|
|
} else {
|
|
doDisableWifi();
|
|
}
|
|
}
|
|
}
|
|
|
|
var wifiHotspotStatusTimer = null;
|
|
function cancelWifiHotspotStatusTimer() {
|
|
if (wifiHotspotStatusTimer) {
|
|
wifiHotspotStatusTimer.cancel();
|
|
wifiHotspotStatusTimer = null;
|
|
}
|
|
}
|
|
|
|
function createWifiHotspotStatusTimer(onTimeout) {
|
|
wifiHotspotStatusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
wifiHotspotStatusTimer.init(onTimeout, 5000, Ci.nsITimer.TYPE_REPEATING_SLACK);
|
|
}
|
|
|
|
// Get wifi interface and load wifi driver when enable Ap mode.
|
|
manager.setWifiApEnabled = function(enabled, configuration, callback) {
|
|
if (enabled === manager.isWifiTetheringEnabled(manager.tetheringState)) {
|
|
callback("no change");
|
|
return;
|
|
}
|
|
|
|
if (enabled) {
|
|
manager.tetheringState = "INITIALIZING";
|
|
loadDriver(function (status) {
|
|
if (status < 0) {
|
|
callback();
|
|
manager.tetheringState = "UNINITIALIZED";
|
|
if (wifiHotspotStatusTimer) {
|
|
cancelWifiHotspotStatusTimer();
|
|
wifiCommand.closeHostapdConnection(function(result) {
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
function getWifiHotspotStatus() {
|
|
wifiCommand.hostapdGetStations(function(result) {
|
|
notify("stationinfoupdate", {station: result});
|
|
});
|
|
}
|
|
|
|
function doStartWifiTethering() {
|
|
cancelWaitForDriverReadyTimer();
|
|
WifiNetworkInterface.info.name =
|
|
libcutils.property_get("wifi.tethering.interface", manager.ifname);
|
|
gTetheringService.setWifiTethering(enabled,
|
|
WifiNetworkInterface.info.name,
|
|
configuration, function(result) {
|
|
if (result) {
|
|
manager.tetheringState = "UNINITIALIZED";
|
|
} else {
|
|
manager.tetheringState = "COMPLETED";
|
|
wifiCommand.connectToHostapd(function(result) {
|
|
if (result) {
|
|
return;
|
|
}
|
|
// Create a timer to track the connection status.
|
|
createWifiHotspotStatusTimer(getWifiHotspotStatus);
|
|
});
|
|
}
|
|
// Pop out current request.
|
|
callback();
|
|
// Should we fire a dom event if we fail to set wifi tethering ?
|
|
debug("Enable Wifi tethering result: " + (result ? result : "successfully"));
|
|
});
|
|
}
|
|
|
|
// Driver startup on certain platforms takes longer than it takes
|
|
// for us to return from loadDriver, so wait 2 seconds before
|
|
// turning on Wifi tethering.
|
|
createWaitForDriverReadyTimer(doStartWifiTethering);
|
|
});
|
|
} else {
|
|
cancelWifiHotspotStatusTimer();
|
|
gTetheringService.setWifiTethering(enabled, WifiNetworkInterface,
|
|
configuration, function(result) {
|
|
// Should we fire a dom event if we fail to set wifi tethering ?
|
|
debug("Disable Wifi tethering result: " + (result ? result : "successfully"));
|
|
// Unload wifi driver even if we fail to control wifi tethering.
|
|
unloadDriver(WIFI_FIRMWARE_AP, function(status) {
|
|
if (status < 0) {
|
|
debug("Fail to unload wifi driver");
|
|
}
|
|
manager.tetheringState = "UNINITIALIZED";
|
|
callback();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
manager.disconnect = wifiCommand.disconnect;
|
|
manager.reconnect = wifiCommand.reconnect;
|
|
manager.reassociate = wifiCommand.reassociate;
|
|
|
|
var networkConfigurationFields = [
|
|
{name: "ssid", type: "string"},
|
|
{name: "bssid", type: "string"},
|
|
{name: "psk", type: "string"},
|
|
{name: "wep_key0", type: "string"},
|
|
{name: "wep_key1", type: "string"},
|
|
{name: "wep_key2", type: "string"},
|
|
{name: "wep_key3", type: "string"},
|
|
{name: "wep_tx_keyidx", type: "integer"},
|
|
{name: "priority", type: "integer"},
|
|
{name: "key_mgmt", type: "string"},
|
|
{name: "scan_ssid", type: "string"},
|
|
{name: "disabled", type: "integer"},
|
|
{name: "identity", type: "string"},
|
|
{name: "password", type: "string"},
|
|
{name: "auth_alg", type: "string"},
|
|
{name: "phase1", type: "string"},
|
|
{name: "phase2", type: "string"},
|
|
{name: "eap", type: "string"},
|
|
{name: "pin", type: "string"},
|
|
{name: "pcsc", type: "string"},
|
|
{name: "ca_cert", type: "string"},
|
|
{name: "subject_match", type: "string"},
|
|
{name: "client_cert", type: "string"},
|
|
{name: "private_key", type: "stirng"},
|
|
{name: "engine", type: "integer"},
|
|
{name: "engine_id", type: "string"},
|
|
{name: "key_id", type: "string"},
|
|
{name: "frequency", type: "integer"},
|
|
{name: "mode", type: "integer"}
|
|
];
|
|
// These fields are only handled in IBSS (aka ad-hoc) mode
|
|
var ibssNetworkConfigurationFields = [
|
|
"frequency", "mode"
|
|
];
|
|
|
|
manager.getNetworkConfiguration = function(config, callback) {
|
|
var netId = config.netId;
|
|
var done = 0;
|
|
for (var n = 0; n < networkConfigurationFields.length; ++n) {
|
|
let fieldName = networkConfigurationFields[n].name;
|
|
let fieldType = networkConfigurationFields[n].type;
|
|
wifiCommand.getNetworkVariable(netId, fieldName, function(value) {
|
|
if (value !== null) {
|
|
if (fieldType === "integer") {
|
|
config[fieldName] = parseInt(value, 10);
|
|
} else if ( fieldName == "ssid" && value[0] != '"' ) {
|
|
// SET_NETWORK will set a quoted ssid to wpa_supplicant.
|
|
// But if ssid contains non-ascii char, it will be converted into utf-8.
|
|
// For example: "Test的wifi" --> 54657374e79a8477696669
|
|
// When GET_NETWORK receive a un-quoted utf-8 ssid, it must be decoded and quoted.
|
|
config[fieldName] = quote(decodeURIComponent(value.replace(/[0-9a-f]{2}/g, '%$&')));
|
|
} else {
|
|
// value is string type by default.
|
|
config[fieldName] = value;
|
|
}
|
|
}
|
|
if (++done == networkConfigurationFields.length)
|
|
callback(config);
|
|
});
|
|
}
|
|
}
|
|
manager.setNetworkConfiguration = function(config, callback) {
|
|
var netId = config.netId;
|
|
var done = 0;
|
|
var errors = 0;
|
|
|
|
function hasValidProperty(name) {
|
|
return ((name in config) &&
|
|
config[name] != null &&
|
|
(["password", "wep_key0", "psk"].indexOf(name) === -1 ||
|
|
config[name] !== '*'));
|
|
}
|
|
|
|
function getModeFromConfig() {
|
|
/* we use the mode from the config, or ESS as default */
|
|
return hasValidProperty("mode") ? config["mode"] : MODE_ESS;
|
|
}
|
|
|
|
var mode = getModeFromConfig();
|
|
|
|
function validForMode(name, mode) {
|
|
/* all fields are valid for IBSS */
|
|
return (mode == MODE_IBSS) ||
|
|
/* IBSS fields are not valid for ESS */
|
|
((mode == MODE_ESS) && !(name in ibssNetworkConfigurationFields));
|
|
}
|
|
|
|
for (var n = 0; n < networkConfigurationFields.length; ++n) {
|
|
let fieldName = networkConfigurationFields[n].name;
|
|
if (!hasValidProperty(fieldName) || !validForMode(fieldName, mode)) {
|
|
++done;
|
|
} else {
|
|
wifiCommand.setNetworkVariable(netId, fieldName, config[fieldName], function(ok) {
|
|
if (!ok)
|
|
++errors;
|
|
if (++done == networkConfigurationFields.length)
|
|
callback(errors == 0);
|
|
});
|
|
}
|
|
}
|
|
// If config didn't contain any of the fields we want, don't lose the error callback.
|
|
if (done == networkConfigurationFields.length)
|
|
callback(false);
|
|
}
|
|
manager.getConfiguredNetworks = function(callback) {
|
|
wifiCommand.listNetworks(function (reply) {
|
|
var networks = Object.create(null);
|
|
var lines = reply ? reply.split("\n") : 0;
|
|
if (lines.length <= 1) {
|
|
// We need to make sure we call the callback even if there are no
|
|
// configured networks.
|
|
callback(networks);
|
|
return;
|
|
}
|
|
|
|
var done = 0;
|
|
var errors = 0;
|
|
for (var n = 1; n < lines.length; ++n) {
|
|
var result = lines[n].split("\t");
|
|
var netId = parseInt(result[0], 10);
|
|
var config = networks[netId] = { netId: netId };
|
|
switch (result[3]) {
|
|
case "[CURRENT]":
|
|
config.status = "CURRENT";
|
|
break;
|
|
case "[DISABLED]":
|
|
config.status = "DISABLED";
|
|
break;
|
|
default:
|
|
config.status = "ENABLED";
|
|
break;
|
|
}
|
|
manager.getNetworkConfiguration(config, function (ok) {
|
|
if (!ok)
|
|
++errors;
|
|
if (++done == lines.length - 1) {
|
|
if (errors) {
|
|
// If an error occured, delete the new netId.
|
|
wifiCommand.removeNetwork(netId, function() {
|
|
callback(null);
|
|
});
|
|
} else {
|
|
callback(networks);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
manager.addNetwork = function(config, callback) {
|
|
wifiCommand.addNetwork(function (netId) {
|
|
config.netId = netId;
|
|
manager.setNetworkConfiguration(config, function (ok) {
|
|
if (!ok) {
|
|
wifiCommand.removeNetwork(netId, function() { callback(false); });
|
|
return;
|
|
}
|
|
|
|
callback(ok);
|
|
});
|
|
});
|
|
}
|
|
manager.updateNetwork = function(config, callback) {
|
|
manager.setNetworkConfiguration(config, callback);
|
|
}
|
|
manager.removeNetwork = function(netId, callback) {
|
|
wifiCommand.removeNetwork(netId, callback);
|
|
}
|
|
|
|
manager.saveConfig = function(callback) {
|
|
wifiCommand.saveConfig(callback);
|
|
}
|
|
manager.enableNetwork = function(netId, disableOthers, callback) {
|
|
if (p2pSupported) {
|
|
// We have to stop wifi direct scan before associating to an AP.
|
|
// Otherwise we will get a "REJECT" wpa supplicant event.
|
|
p2pManager.setScanEnabled(false, function(success) {});
|
|
}
|
|
wifiCommand.enableNetwork(netId, disableOthers, callback);
|
|
}
|
|
manager.disableNetwork = function(netId, callback) {
|
|
wifiCommand.disableNetwork(netId, callback);
|
|
}
|
|
manager.getMacAddress = wifiCommand.getMacAddress;
|
|
manager.getScanResults = wifiCommand.scanResults;
|
|
manager.setScanMode = function(mode, callback) {
|
|
setScanMode(mode === "active", callback); // Use our own version.
|
|
}
|
|
manager.setBackgroundScan = setBackgroundScan; // Use our own version.
|
|
manager.scan = scan; // Use our own version.
|
|
manager.wpsPbc = wifiCommand.wpsPbc;
|
|
manager.wpsPin = wifiCommand.wpsPin;
|
|
manager.wpsCancel = wifiCommand.wpsCancel;
|
|
manager.setPowerMode = (sdkVersion >= 16)
|
|
? wifiCommand.setPowerModeJB
|
|
: wifiCommand.setPowerModeICS;
|
|
manager.setPowerSavingMode = setPowerSavingMode;
|
|
manager.getHttpProxyNetwork = getHttpProxyNetwork;
|
|
manager.setHttpProxy = setHttpProxy;
|
|
manager.configureHttpProxy = configureHttpProxy;
|
|
manager.setSuspendOptimizations = (sdkVersion >= 16)
|
|
? wifiCommand.setSuspendOptimizationsJB
|
|
: wifiCommand.setSuspendOptimizationsICS;
|
|
manager.setStaticIpMode = setStaticIpMode;
|
|
manager.getRssiApprox = wifiCommand.getRssiApprox;
|
|
manager.getLinkSpeed = wifiCommand.getLinkSpeed;
|
|
manager.getDhcpInfo = function() { return dhcpInfo; }
|
|
manager.getConnectionInfo = (sdkVersion >= 15)
|
|
? wifiCommand.getConnectionInfoICS
|
|
: wifiCommand.getConnectionInfoGB;
|
|
|
|
manager.ensureSupplicantDetached = aCallback => {
|
|
if (!manager.enabled) {
|
|
aCallback();
|
|
return;
|
|
}
|
|
wifiCommand.closeSupplicantConnection(aCallback);
|
|
};
|
|
|
|
manager.isHandShakeState = function(state) {
|
|
switch (state) {
|
|
case "AUTHENTICATING":
|
|
case "ASSOCIATING":
|
|
case "ASSOCIATED":
|
|
case "FOUR_WAY_HANDSHAKE":
|
|
case "GROUP_HANDSHAKE":
|
|
return true;
|
|
case "DORMANT":
|
|
case "COMPLETED":
|
|
case "DISCONNECTED":
|
|
case "INTERFACE_DISABLED":
|
|
case "INACTIVE":
|
|
case "SCANNING":
|
|
case "UNINITIALIZED":
|
|
case "INVALID":
|
|
case "CONNECTED":
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
manager.isWifiEnabled = function(state) {
|
|
switch (state) {
|
|
case "UNINITIALIZED":
|
|
case "DISABLING":
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
manager.isWifiTetheringEnabled = function(state) {
|
|
switch (state) {
|
|
case "UNINITIALIZED":
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
manager.syncDebug = syncDebug;
|
|
manager.stateOrdinal = function(state) {
|
|
return supplicantStatesMap.indexOf(state);
|
|
}
|
|
manager.supplicantLoopDetection = function(prevState, state) {
|
|
var isPrevStateInHandShake = manager.isHandShakeState(prevState);
|
|
var isStateInHandShake = manager.isHandShakeState(state);
|
|
|
|
if (isPrevStateInHandShake) {
|
|
if (isStateInHandShake) {
|
|
// Increase the count only if we are in the loop.
|
|
if (manager.stateOrdinal(state) > manager.stateOrdinal(prevState)) {
|
|
manager.loopDetectionCount++;
|
|
}
|
|
if (manager.loopDetectionCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
|
|
notify("disconnected", {connectionInfo: manager.connectionInfo});
|
|
manager.loopDetectionCount = 0;
|
|
}
|
|
}
|
|
} else {
|
|
// From others state to HandShake state. Reset the count.
|
|
if (isStateInHandShake) {
|
|
manager.loopDetectionCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
manager.handlePreWifiScan = function() {
|
|
if (p2pSupported) {
|
|
// Before doing regular wifi scan, we have to disable wifi direct
|
|
// scan first. Otherwise we will never get the scan result.
|
|
p2pManager.blockScan();
|
|
}
|
|
};
|
|
|
|
manager.handlePostWifiScan = function() {
|
|
if (p2pSupported) {
|
|
// After regular wifi scanning, we should restore the restricted
|
|
// wifi direct scan.
|
|
p2pManager.unblockScan();
|
|
}
|
|
};
|
|
|
|
//
|
|
// Public APIs for P2P.
|
|
//
|
|
|
|
manager.p2pSupported = function() {
|
|
return p2pSupported;
|
|
};
|
|
|
|
manager.getP2pManager = function() {
|
|
return p2pManager;
|
|
};
|
|
|
|
manager.enableP2p = function(callback) {
|
|
p2pManager.setEnabled(true, {
|
|
onSupplicantConnected: function() {
|
|
waitForEvent(WifiP2pManager.INTERFACE_NAME);
|
|
},
|
|
|
|
onEnabled: function(success) {
|
|
callback(success);
|
|
}
|
|
});
|
|
};
|
|
|
|
manager.getCapabilities = function() {
|
|
return capabilities;
|
|
}
|
|
|
|
// Cert Services
|
|
let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"];
|
|
if (wifiCertService) {
|
|
wifiCertService = wifiCertService.getService(Ci.nsIWifiCertService);
|
|
wifiCertService.start(wifiListener);
|
|
} else {
|
|
debug("No wifi CA service component available");
|
|
}
|
|
|
|
manager.importCert = function(caInfo, callback) {
|
|
var id = idgen++;
|
|
if (callback) {
|
|
controlCallbacks[id] = callback;
|
|
}
|
|
|
|
wifiCertService.importCert(id, caInfo.certBlob, caInfo.certPassword,
|
|
caInfo.certNickname);
|
|
}
|
|
|
|
manager.deleteCert = function(caInfo, callback) {
|
|
var id = idgen++;
|
|
if (callback) {
|
|
controlCallbacks[id] = callback;
|
|
}
|
|
|
|
wifiCertService.deleteCert(id, caInfo.certNickname);
|
|
}
|
|
|
|
manager.sdkVersion = function() {
|
|
return sdkVersion;
|
|
}
|
|
|
|
return manager;
|
|
})();
|
|
|
|
// Get unique key for a network, now the key is created by escape(SSID)+Security.
|
|
// So networks of same SSID but different security mode can be identified.
|
|
function getNetworkKey(network)
|
|
{
|
|
var ssid = "",
|
|
encryption = "OPEN";
|
|
|
|
if ("security" in network) {
|
|
// manager network object, represents an AP
|
|
// object structure
|
|
// {
|
|
// .ssid : SSID of AP
|
|
// .security[] : "WPA-PSK" for WPA-PSK
|
|
// "WPA-EAP" for WPA-EAP
|
|
// "WEP" for WEP
|
|
// "" for OPEN
|
|
// other keys
|
|
// }
|
|
|
|
var security = network.security;
|
|
ssid = network.ssid;
|
|
|
|
for (let j = 0; j < security.length; j++) {
|
|
if (security[j] === "WPA-PSK") {
|
|
encryption = "WPA-PSK";
|
|
break;
|
|
} else if (security[j] === "WPA-EAP") {
|
|
encryption = "WPA-EAP";
|
|
break;
|
|
} else if (security[j] === "WEP") {
|
|
encryption = "WEP";
|
|
break;
|
|
}
|
|
}
|
|
} else if ("key_mgmt" in network) {
|
|
// configure network object, represents a network
|
|
// object structure
|
|
// {
|
|
// .ssid : SSID of network, quoted
|
|
// .key_mgmt : Encryption type
|
|
// "WPA-PSK" for WPA-PSK
|
|
// "WPA-EAP" for WPA-EAP
|
|
// "NONE" for WEP/OPEN
|
|
// .auth_alg : Encryption algorithm(WEP mode only)
|
|
// "OPEN_SHARED" for WEP
|
|
// other keys
|
|
// }
|
|
var key_mgmt = network.key_mgmt,
|
|
auth_alg = network.auth_alg;
|
|
ssid = dequote(network.ssid);
|
|
|
|
if (key_mgmt == "WPA-PSK") {
|
|
encryption = "WPA-PSK";
|
|
} else if (key_mgmt.indexOf("WPA-EAP") != -1) {
|
|
encryption = "WPA-EAP";
|
|
} else if (key_mgmt == "NONE" && auth_alg === "OPEN SHARED") {
|
|
encryption = "WEP";
|
|
}
|
|
}
|
|
|
|
// ssid here must be dequoted, and it's safer to esacpe it.
|
|
// encryption won't be empty and always be assigned one of the followings :
|
|
// "OPEN"/"WEP"/"WPA-PSK"/"WPA-EAP".
|
|
// So for a invalid network object, the returned key will be "OPEN".
|
|
return escape(ssid) + encryption;
|
|
}
|
|
|
|
function getMode(flags) {
|
|
if (/\[IBSS/.test(flags))
|
|
return MODE_IBSS;
|
|
|
|
return MODE_ESS;
|
|
}
|
|
|
|
function getKeyManagement(flags) {
|
|
var types = [];
|
|
if (!flags)
|
|
return types;
|
|
|
|
if (/\[WPA2?-PSK/.test(flags))
|
|
types.push("WPA-PSK");
|
|
if (/\[WPA2?-EAP/.test(flags))
|
|
types.push("WPA-EAP");
|
|
if (/\[WEP/.test(flags))
|
|
types.push("WEP");
|
|
return types;
|
|
}
|
|
|
|
function getCapabilities(flags) {
|
|
var types = [];
|
|
if (!flags)
|
|
return types;
|
|
|
|
if (/\[WPS/.test(flags))
|
|
types.push("WPS");
|
|
return types;
|
|
}
|
|
|
|
// These constants shamelessly ripped from WifiManager.java
|
|
// strength is the value returned by scan_results. It is nominally in dB. We
|
|
// transform it into a percentage for clients looking to simply show a
|
|
// relative indication of the strength of a network.
|
|
const MIN_RSSI = -100;
|
|
const MAX_RSSI = -55;
|
|
|
|
function calculateSignal(strength) {
|
|
// Some wifi drivers represent their signal strengths as 8-bit integers, so
|
|
// in order to avoid negative numbers, they add 256 to the actual values.
|
|
// While we don't *know* that this is the case here, we make an educated
|
|
// guess.
|
|
if (strength > 0)
|
|
strength -= 256;
|
|
|
|
if (strength <= MIN_RSSI)
|
|
return 0;
|
|
if (strength >= MAX_RSSI)
|
|
return 100;
|
|
return Math.floor(((strength - MIN_RSSI) / (MAX_RSSI - MIN_RSSI)) * 100);
|
|
}
|
|
|
|
function Network(ssid, mode, frequency, security, password, capabilities) {
|
|
this.ssid = ssid;
|
|
this.mode = mode;
|
|
this.frequency = frequency;
|
|
this.security = security;
|
|
|
|
if (typeof password !== "undefined")
|
|
this.password = password;
|
|
if (capabilities !== undefined)
|
|
this.capabilities = capabilities;
|
|
// TODO connected here as well?
|
|
|
|
this.__exposedProps__ = Network.api;
|
|
}
|
|
|
|
Network.api = {
|
|
ssid: "r",
|
|
mode: "r",
|
|
frequency: "r",
|
|
security: "r",
|
|
capabilities: "r",
|
|
known: "r",
|
|
|
|
password: "rw",
|
|
keyManagement: "rw",
|
|
psk: "rw",
|
|
identity: "rw",
|
|
wep: "rw",
|
|
hidden: "rw",
|
|
eap: "rw",
|
|
pin: "rw",
|
|
phase1: "rw",
|
|
phase2: "rw",
|
|
serverCertificate: "rw",
|
|
userCertificate: "rw"
|
|
};
|
|
|
|
// Note: We never use ScanResult.prototype, so the fact that it's unrelated to
|
|
// Network.prototype is OK.
|
|
function ScanResult(ssid, bssid, frequency, flags, signal) {
|
|
Network.call(this, ssid, getMode(flags), frequency,
|
|
getKeyManagement(flags), undefined, getCapabilities(flags));
|
|
this.bssid = bssid;
|
|
this.signalStrength = signal;
|
|
this.relSignalStrength = calculateSignal(Number(signal));
|
|
|
|
this.__exposedProps__ = ScanResult.api;
|
|
}
|
|
|
|
// XXX This should probably live in the DOM-facing side, but it's hard to do
|
|
// there, so we stick this here.
|
|
ScanResult.api = {
|
|
bssid: "r",
|
|
signalStrength: "r",
|
|
relSignalStrength: "r",
|
|
connected: "r"
|
|
};
|
|
|
|
for (let i in Network.api) {
|
|
ScanResult.api[i] = Network.api[i];
|
|
}
|
|
|
|
function quote(s) {
|
|
return '"' + s + '"';
|
|
}
|
|
|
|
function dequote(s) {
|
|
if (s[0] != '"' || s[s.length - 1] != '"')
|
|
throw "Invalid argument, not a quoted string: " + s;
|
|
return s.substr(1, s.length - 2);
|
|
}
|
|
|
|
function isWepHexKey(s) {
|
|
if (s.length != 10 && s.length != 26 && s.length != 58)
|
|
return false;
|
|
return !/[^a-fA-F0-9]/.test(s);
|
|
}
|
|
|
|
|
|
var WifiNetworkInterface = {
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
|
|
|
|
registered: false,
|
|
|
|
// nsINetworkInterface
|
|
|
|
NETWORK_STATE_UNKNOWN: Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN,
|
|
NETWORK_STATE_CONNECTING: Ci.nsINetworkInfo.CONNECTING,
|
|
NETWORK_STATE_CONNECTED: Ci.nsINetworkInfo.CONNECTED,
|
|
NETWORK_STATE_DISCONNECTING: Ci.nsINetworkInfo.DISCONNECTING,
|
|
NETWORK_STATE_DISCONNECTED: Ci.nsINetworkInfo.DISCONNECTED,
|
|
|
|
NETWORK_TYPE_WIFI: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
|
|
NETWORK_TYPE_MOBILE: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE,
|
|
NETWORK_TYPE_MOBILE_MMS: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS,
|
|
NETWORK_TYPE_MOBILE_SUPL: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL,
|
|
|
|
info: {
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
|
|
|
|
state: Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN,
|
|
|
|
type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
|
|
|
|
name: null,
|
|
|
|
ips: [],
|
|
|
|
prefixLengths: [],
|
|
|
|
dnses: [],
|
|
|
|
gateways: [],
|
|
|
|
getAddresses: function (ips, prefixLengths) {
|
|
ips.value = this.ips.slice();
|
|
prefixLengths.value = this.prefixLengths.slice();
|
|
|
|
return this.ips.length;
|
|
},
|
|
|
|
getGateways: function (count) {
|
|
if (count) {
|
|
count.value = this.gateways.length;
|
|
}
|
|
return this.gateways.slice();
|
|
},
|
|
|
|
getDnses: function (count) {
|
|
if (count) {
|
|
count.value = this.dnses.length;
|
|
}
|
|
return this.dnses.slice();
|
|
}
|
|
},
|
|
|
|
httpProxyHost: null,
|
|
|
|
httpProxyPort: null
|
|
};
|
|
|
|
function WifiScanResult() {}
|
|
|
|
// TODO Make the difference between a DOM-based network object and our
|
|
// networks objects much clearer.
|
|
var netToDOM;
|
|
var netFromDOM;
|
|
|
|
function WifiWorker() {
|
|
var self = this;
|
|
|
|
this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
|
|
.getService(Ci.nsIMessageListenerManager);
|
|
const messages = ["WifiManager:getNetworks", "WifiManager:getKnownNetworks",
|
|
"WifiManager:associate", "WifiManager:forget",
|
|
"WifiManager:wps", "WifiManager:getState",
|
|
"WifiManager:setPowerSavingMode",
|
|
"WifiManager:setHttpProxy",
|
|
"WifiManager:setStaticIpMode",
|
|
"WifiManager:importCert",
|
|
"WifiManager:getImportedCerts",
|
|
"WifiManager:deleteCert",
|
|
"WifiManager:setWifiEnabled",
|
|
"WifiManager:setWifiTethering",
|
|
"child-process-shutdown"];
|
|
|
|
messages.forEach((function(msgName) {
|
|
this._mm.addMessageListener(msgName, this);
|
|
}).bind(this));
|
|
|
|
Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
|
|
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
|
|
|
this.wantScanResults = [];
|
|
|
|
this._needToEnableNetworks = false;
|
|
this._highestPriority = -1;
|
|
|
|
// Networks is a map from SSID -> a scan result.
|
|
this.networks = Object.create(null);
|
|
|
|
// ConfiguredNetworks is a map from SSID -> our view of a network. It only
|
|
// lists networks known to the wpa_supplicant. The SSID field (and other
|
|
// fields) are quoted for ease of use with WifiManager commands.
|
|
// Note that we don't have to worry about escaping embedded quotes since in
|
|
// all cases, the supplicant will take the last quotation that we pass it as
|
|
// the end of the string.
|
|
this.configuredNetworks = Object.create(null);
|
|
this._addingNetworks = Object.create(null);
|
|
|
|
this.currentNetwork = null;
|
|
this.ipAddress = "";
|
|
this.macAddress = null;
|
|
|
|
this._lastConnectionInfo = null;
|
|
this._connectionInfoTimer = null;
|
|
this._reconnectOnDisconnect = false;
|
|
|
|
// Create p2pObserver and assign to p2pManager.
|
|
if (WifiManager.p2pSupported()) {
|
|
this._p2pObserver = WifiP2pWorkerObserver(WifiManager.getP2pManager());
|
|
WifiManager.getP2pManager().setObserver(this._p2pObserver);
|
|
|
|
// Add DOM message observerd by p2pObserver to the message listener as well.
|
|
this._p2pObserver.getObservedDOMMessages().forEach((function(msgName) {
|
|
this._mm.addMessageListener(msgName, this);
|
|
}).bind(this));
|
|
}
|
|
|
|
// Users of instances of nsITimer should keep a reference to the timer until
|
|
// it is no longer needed in order to assure the timer is fired.
|
|
this._callbackTimer = null;
|
|
|
|
// XXX On some phones (Otoro and Unagi) the wifi driver doesn't play nicely
|
|
// with the automatic scans that wpa_supplicant does (it appears that the
|
|
// driver forgets that it's returned scan results and then refuses to try to
|
|
// rescan. In order to detect this case we start a timer when we enter the
|
|
// SCANNING state and reset it whenever we either get scan results or leave
|
|
// the SCANNING state. If the timer fires, we assume that we are stuck and
|
|
// forceably try to unstick the supplican, also turning on background
|
|
// scanning to avoid having to constantly poke the supplicant.
|
|
|
|
// How long we wait is controlled by the SCAN_STUCK_WAIT constant.
|
|
const SCAN_STUCK_WAIT = 12000;
|
|
this._scanStuckTimer = null;
|
|
this._turnOnBackgroundScan = false;
|
|
|
|
function startScanStuckTimer() {
|
|
if (WifiManager.schedScanRecovery) {
|
|
self._scanStuckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
|
|
Ci.nsITimer.TYPE_ONE_SHOT);
|
|
}
|
|
}
|
|
|
|
function scanIsStuck() {
|
|
// Uh-oh, we've waited too long for scan results. Disconnect (which
|
|
// guarantees that we leave the SCANNING state and tells wpa_supplicant to
|
|
// wait for our next command) ensure that background scanning is on and
|
|
// then try again.
|
|
debug("Determined that scanning is stuck, turning on background scanning!");
|
|
WifiManager.handlePostWifiScan();
|
|
WifiManager.disconnect(function(ok) {});
|
|
self._turnOnBackgroundScan = true;
|
|
}
|
|
|
|
// A list of requests to turn wifi on or off.
|
|
this._stateRequests = [];
|
|
|
|
// Given a connection status network, takes a network from
|
|
// self.configuredNetworks and prepares it for the DOM.
|
|
netToDOM = function(net) {
|
|
if (!net) {
|
|
return null;
|
|
}
|
|
var ssid = dequote(net.ssid);
|
|
var mode = net.mode;
|
|
var frequency = net.frequency;
|
|
var security = (net.key_mgmt === "NONE" && net.wep_key0) ? ["WEP"] :
|
|
(net.key_mgmt && net.key_mgmt !== "NONE") ? [net.key_mgmt.split(" ")[0]] :
|
|
[];
|
|
var password;
|
|
if (("psk" in net && net.psk) ||
|
|
("password" in net && net.password) ||
|
|
("wep_key0" in net && net.wep_key0)) {
|
|
password = "*";
|
|
}
|
|
|
|
var pub = new Network(ssid, mode, frequency, security, password);
|
|
if (net.identity)
|
|
pub.identity = dequote(net.identity);
|
|
if ("netId" in net)
|
|
pub.known = true;
|
|
if (net.scan_ssid === 1)
|
|
pub.hidden = true;
|
|
if ("ca_cert" in net && net.ca_cert &&
|
|
net.ca_cert.indexOf("keystore://WIFI_SERVERCERT_" === 0)) {
|
|
pub.serverCertificate = net.ca_cert.substr(27);
|
|
}
|
|
if(net.subject_match) {
|
|
pub.subjectMatch = net.subject_match;
|
|
}
|
|
if ("client_cert" in net && net.client_cert &&
|
|
net.client_cert.indexOf("keystore://WIFI_USERCERT_" === 0)) {
|
|
pub.userCertificate = net.client_cert.substr(25);
|
|
}
|
|
return pub;
|
|
};
|
|
|
|
netFromDOM = function(net, configured) {
|
|
// Takes a network from the DOM and makes it suitable for insertion into
|
|
// self.configuredNetworks (that is calling addNetwork will do the right
|
|
// thing).
|
|
// NB: Modifies net in place: safe since we don't share objects between
|
|
// the dom and the chrome code.
|
|
|
|
// Things that are useful for the UI but not to us.
|
|
delete net.bssid;
|
|
delete net.signalStrength;
|
|
delete net.relSignalStrength;
|
|
delete net.security;
|
|
delete net.capabilities;
|
|
|
|
if (!configured)
|
|
configured = {};
|
|
|
|
net.ssid = quote(net.ssid);
|
|
|
|
let wep = false;
|
|
if ("keyManagement" in net) {
|
|
if (net.keyManagement === "WEP") {
|
|
wep = true;
|
|
net.keyManagement = "NONE";
|
|
} else if (net.keyManagement === "WPA-EAP") {
|
|
net.keyManagement += " IEEE8021X";
|
|
}
|
|
|
|
configured.key_mgmt = net.key_mgmt = net.keyManagement; // WPA2-PSK, WPA-PSK, etc.
|
|
delete net.keyManagement;
|
|
} else {
|
|
configured.key_mgmt = net.key_mgmt = "NONE";
|
|
}
|
|
|
|
if (net.hidden) {
|
|
configured.scan_ssid = net.scan_ssid = 1;
|
|
delete net.hidden;
|
|
}
|
|
|
|
function checkAssign(name, checkStar) {
|
|
if (name in net) {
|
|
let value = net[name];
|
|
if (!value || (checkStar && value === '*')) {
|
|
if (name in configured)
|
|
net[name] = configured[name];
|
|
else
|
|
delete net[name];
|
|
} else {
|
|
configured[name] = net[name] = quote(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
checkAssign("psk", true);
|
|
checkAssign("identity", false);
|
|
checkAssign("password", true);
|
|
if (wep && net.wep && net.wep != '*') {
|
|
configured.wep_key0 = net.wep_key0 = isWepHexKey(net.wep) ? net.wep : quote(net.wep);
|
|
configured.auth_alg = net.auth_alg = "OPEN SHARED";
|
|
}
|
|
|
|
function hasValidProperty(name) {
|
|
return ((name in net) && net[name] != null);
|
|
}
|
|
|
|
if (hasValidProperty("eap")) {
|
|
if (hasValidProperty("pin")) {
|
|
net.pin = quote(net.pin);
|
|
}
|
|
|
|
if (hasValidProperty("phase1"))
|
|
net.phase1 = quote(net.phase1);
|
|
|
|
if (hasValidProperty("phase2")) {
|
|
if (net.phase2 === "TLS") {
|
|
net.phase2 = quote("autheap=" + net.phase2);
|
|
} else { // PAP, MSCHAPV2, etc.
|
|
net.phase2 = quote("auth=" + net.phase2);
|
|
}
|
|
}
|
|
|
|
if (hasValidProperty("serverCertificate"))
|
|
net.ca_cert = quote("keystore://WIFI_SERVERCERT_" + net.serverCertificate);
|
|
|
|
if (hasValidProperty("subjectMatch"))
|
|
net.subject_match = quote(net.subjectMatch);
|
|
|
|
if (hasValidProperty("userCertificate")) {
|
|
let userCertName = "WIFI_USERCERT_" + net.userCertificate;
|
|
net.client_cert = quote("keystore://" + userCertName);
|
|
|
|
let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].
|
|
getService(Ci.nsIWifiCertService);
|
|
if (wifiCertService.hasPrivateKey(userCertName)) {
|
|
if (WifiManager.sdkVersion() >= 19) {
|
|
// Use openssol engine instead of keystore protocol after Kitkat.
|
|
net.engine = 1;
|
|
net.engine_id = quote("keystore");
|
|
net.key_id = quote("WIFI_USERKEY_" + net.userCertificate);
|
|
} else {
|
|
net.private_key = quote("keystore://WIFI_USERKEY_" + net.userCertificate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return net;
|
|
};
|
|
|
|
WifiManager.onsupplicantconnection = function() {
|
|
debug("Connected to supplicant");
|
|
// Give it a state other than UNINITIALIZED, INITIALIZING or DISABLING
|
|
// defined in getter function of WifiManager.enabled. It implies that
|
|
// we are ready to accept dom request from applications.
|
|
WifiManager.state = "DISCONNECTED";
|
|
self._reloadConfiguredNetworks(function(ok) {
|
|
// Prime this.networks.
|
|
if (!ok)
|
|
return;
|
|
|
|
// The select network command we used in associate() disables others networks.
|
|
// Enable them here to make sure wpa_supplicant helps to connect to known
|
|
// network automatically.
|
|
self._enableAllNetworks();
|
|
WifiManager.saveConfig(function() {})
|
|
});
|
|
|
|
// Notify everybody, even if they didn't ask us to come up.
|
|
WifiManager.getMacAddress(function (mac) {
|
|
self.macAddress = mac;
|
|
debug("Got mac: " + mac);
|
|
self._fireEvent("wifiUp", { macAddress: mac });
|
|
self.requestDone();
|
|
});
|
|
|
|
if (WifiManager.state === "SCANNING")
|
|
startScanStuckTimer();
|
|
};
|
|
|
|
WifiManager.onsupplicantlost = function() {
|
|
WifiManager.supplicantStarted = false;
|
|
WifiManager.state = "UNINITIALIZED";
|
|
debug("Supplicant died!");
|
|
|
|
// Notify everybody, even if they didn't ask us to come up.
|
|
self._fireEvent("wifiDown", {});
|
|
self.requestDone();
|
|
};
|
|
|
|
WifiManager.onpasswordmaybeincorrect = function() {
|
|
WifiManager.authenticationFailuresCount++;
|
|
};
|
|
|
|
WifiManager.ondisconnected = function() {
|
|
// We may fail to establish the connection, re-enable the
|
|
// rest of our networks.
|
|
if (self._needToEnableNetworks) {
|
|
self._enableAllNetworks();
|
|
self._needToEnableNetworks = false;
|
|
}
|
|
|
|
let connectionInfo = this.connectionInfo;
|
|
WifiManager.getNetworkId(connectionInfo.ssid, function(netId) {
|
|
// Trying to get netId from current network.
|
|
if (!netId &&
|
|
self.currentNetwork && self.currentNetwork.ssid &&
|
|
dequote(self.currentNetwork.ssid) == connectionInfo.ssid &&
|
|
typeof self.currentNetwork.netId !== "undefined") {
|
|
netId = self.currentNetwork.netId;
|
|
}
|
|
if (netId) {
|
|
WifiManager.disableNetwork(netId, function() {});
|
|
}
|
|
});
|
|
self._fireEvent("onconnectingfailed", {network: netToDOM(self.currentNetwork)});
|
|
}
|
|
|
|
WifiManager.onstatechange = function() {
|
|
debug("State change: " + this.prevState + " -> " + this.state);
|
|
|
|
if (self._connectionInfoTimer &&
|
|
this.state !== "CONNECTED" &&
|
|
this.state !== "COMPLETED") {
|
|
self._stopConnectionInfoTimer();
|
|
}
|
|
|
|
if (this.state !== "SCANNING" &&
|
|
self._scanStuckTimer) {
|
|
self._scanStuckTimer.cancel();
|
|
self._scanStuckTimer = null;
|
|
}
|
|
|
|
switch (this.state) {
|
|
case "DORMANT":
|
|
// The dormant state is a bad state to be in since we won't
|
|
// automatically connect. Try to knock us out of it. We only
|
|
// hit this state when we've failed to run DHCP, so trying
|
|
// again isn't the worst thing we can do. Eventually, we'll
|
|
// need to detect if we're looping in this state and bail out.
|
|
WifiManager.reconnect(function(){});
|
|
break;
|
|
case "ASSOCIATING":
|
|
// id has not yet been filled in, so we can only report the ssid and
|
|
// bssid. mode and frequency are simply made up.
|
|
self.currentNetwork =
|
|
{ bssid: WifiManager.connectionInfo.bssid,
|
|
ssid: quote(WifiManager.connectionInfo.ssid),
|
|
mode: MODE_ESS,
|
|
frequency: 0};
|
|
WifiManager.getNetworkConfiguration(self.currentNetwork, function (){
|
|
// Notify again because we get complete network information.
|
|
self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
|
|
});
|
|
break;
|
|
case "ASSOCIATED":
|
|
// set to full power mode when ready to do 4 way handsharke.
|
|
WifiManager.setPowerSavingMode(false);
|
|
if (!self.currentNetwork) {
|
|
self.currentNetwork =
|
|
{ bssid: WifiManager.connectionInfo.bssid,
|
|
ssid: quote(WifiManager.connectionInfo.ssid) };
|
|
}
|
|
self.currentNetwork.netId = this.id;
|
|
WifiManager.getNetworkConfiguration(self.currentNetwork, function (){
|
|
// Notify again because we get complete network information.
|
|
self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
|
|
});
|
|
break;
|
|
case "COMPLETED":
|
|
// Now that we've successfully completed the connection, re-enable the
|
|
// rest of our networks.
|
|
// XXX Need to do this eventually if the user entered an incorrect
|
|
// password. For now, we require user interaction to break the loop and
|
|
// select a better network!
|
|
if (self._needToEnableNetworks) {
|
|
self._enableAllNetworks();
|
|
self._needToEnableNetworks = false;
|
|
}
|
|
|
|
var _oncompleted = function() {
|
|
// The full authentication process is completed, reset the count.
|
|
WifiManager.authenticationFailuresCount = 0;
|
|
WifiManager.loopDetectionCount = 0;
|
|
self._startConnectionInfoTimer();
|
|
self._fireEvent("onassociate", { network: netToDOM(self.currentNetwork) });
|
|
};
|
|
|
|
// We get the ASSOCIATED event when we've associated but not connected, so
|
|
// wait until the handshake is complete.
|
|
if (this.fromStatus || !self.currentNetwork) {
|
|
// In this case, we connected to an already-connected wpa_supplicant,
|
|
// because of that we need to gather information about the current
|
|
// network here.
|
|
self.currentNetwork = { bssid: WifiManager.connectionInfo.bssid,
|
|
ssid: quote(WifiManager.connectionInfo.ssid),
|
|
netId: WifiManager.connectionInfo.id };
|
|
WifiManager.getNetworkConfiguration(self.currentNetwork, _oncompleted);
|
|
} else {
|
|
_oncompleted();
|
|
}
|
|
break;
|
|
case "CONNECTED":
|
|
// wifi connection complete, turn on the power saving mode.
|
|
WifiManager.setPowerSavingMode(true);
|
|
// BSSID is read after connected, update it.
|
|
self.currentNetwork.bssid = WifiManager.connectionInfo.bssid;
|
|
break;
|
|
case "DISCONNECTED":
|
|
// wpa_supplicant may give us a "DISCONNECTED" event even if
|
|
// we are already in "DISCONNECTED" state.
|
|
if ((WifiNetworkInterface.info.state ===
|
|
Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED) &&
|
|
(this.prevState === "INITIALIZING" ||
|
|
this.prevState === "DISCONNECTED" ||
|
|
this.prevState === "INTERFACE_DISABLED" ||
|
|
this.prevState === "INACTIVE" ||
|
|
this.prevState === "UNINITIALIZED")) {
|
|
// When in disconnected mode, need to turn on wifi power saving mode.
|
|
WifiManager.setPowerSavingMode(true);
|
|
return;
|
|
}
|
|
|
|
self._fireEvent("ondisconnect", {network: netToDOM(self.currentNetwork)});
|
|
|
|
self.currentNetwork = null;
|
|
self.ipAddress = "";
|
|
|
|
if (self._turnOnBackgroundScan) {
|
|
self._turnOnBackgroundScan = false;
|
|
WifiManager.setBackgroundScan("ON", function(did_something, ok) {
|
|
WifiManager.reassociate(function() {});
|
|
});
|
|
}
|
|
|
|
WifiManager.connectionDropped(function() {
|
|
// We've disconnected from a network because of a call to forgetNetwork.
|
|
// Reconnect to the next available network (if any).
|
|
if (self._reconnectOnDisconnect) {
|
|
self._reconnectOnDisconnect = false;
|
|
WifiManager.reconnect(function(){});
|
|
}
|
|
});
|
|
|
|
WifiNetworkInterface.info.state =
|
|
Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
|
|
|
|
// Update network infterface first then clear properties.
|
|
gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
|
|
WifiNetworkInterface.info.ips = [];
|
|
WifiNetworkInterface.info.prefixLengths = [];
|
|
WifiNetworkInterface.info.gateways = [];
|
|
WifiNetworkInterface.info.dnses = [];
|
|
|
|
|
|
break;
|
|
case "WPS_TIMEOUT":
|
|
self._fireEvent("onwpstimeout", {});
|
|
break;
|
|
case "WPS_FAIL":
|
|
self._fireEvent("onwpsfail", {});
|
|
break;
|
|
case "WPS_OVERLAP_DETECTED":
|
|
self._fireEvent("onwpsoverlap", {});
|
|
break;
|
|
case "AUTHENTICATING":
|
|
self._fireEvent("onauthenticating", {network: netToDOM(self.currentNetwork)});
|
|
break;
|
|
case "SCANNING":
|
|
// If we're already scanning in the background, we don't need to worry
|
|
// about getting stuck while scanning.
|
|
if (!WifiManager.backgroundScanEnabled && WifiManager.enabled)
|
|
startScanStuckTimer();
|
|
break;
|
|
}
|
|
};
|
|
|
|
WifiManager.onnetworkconnected = function() {
|
|
if (!this.info || !this.info.ipaddr_str) {
|
|
debug("Network information is invalid.");
|
|
return;
|
|
}
|
|
|
|
let maskLength =
|
|
netHelpers.getMaskLength(netHelpers.stringToIP(this.info.mask_str));
|
|
if (!maskLength) {
|
|
maskLength = 32; // max prefix for IPv4.
|
|
}
|
|
|
|
let netConnect = WifiManager.getHttpProxyNetwork(self.currentNetwork);
|
|
if (netConnect) {
|
|
WifiNetworkInterface.httpProxyHost = netConnect.httpProxyHost;
|
|
WifiNetworkInterface.httpProxyPort = netConnect.httpProxyPort;
|
|
}
|
|
|
|
WifiNetworkInterface.info.state =
|
|
Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED;
|
|
WifiNetworkInterface.info.ips = [this.info.ipaddr_str];
|
|
WifiNetworkInterface.info.prefixLengths = [maskLength];
|
|
WifiNetworkInterface.info.gateways = [this.info.gateway_str];
|
|
if (typeof this.info.dns1_str == "string" &&
|
|
this.info.dns1_str.length) {
|
|
WifiNetworkInterface.info.dnses.push(this.info.dns1_str);
|
|
}
|
|
if (typeof this.info.dns2_str == "string" &&
|
|
this.info.dns2_str.length) {
|
|
WifiNetworkInterface.info.dnses.push(this.info.dns2_str);
|
|
}
|
|
gNetworkManager.updateNetworkInterface(WifiNetworkInterface);
|
|
|
|
self.ipAddress = this.info.ipaddr_str;
|
|
|
|
// We start the connection information timer when we associate, but
|
|
// don't have our IP address until here. Make sure that we fire a new
|
|
// connectionInformation event with the IP address the next time the
|
|
// timer fires.
|
|
self._lastConnectionInfo = null;
|
|
self._fireEvent("onconnect", { network: netToDOM(self.currentNetwork) });
|
|
};
|
|
|
|
WifiManager.onscanresultsavailable = function() {
|
|
if (self._scanStuckTimer) {
|
|
// We got scan results! We must not be stuck for now, try again.
|
|
self._scanStuckTimer.cancel();
|
|
self._scanStuckTimer.initWithCallback(scanIsStuck, SCAN_STUCK_WAIT,
|
|
Ci.nsITimer.TYPE_ONE_SHOT);
|
|
}
|
|
|
|
if (self.wantScanResults.length === 0) {
|
|
debug("Scan results available, but we don't need them");
|
|
return;
|
|
}
|
|
|
|
debug("Scan results are available! Asking for them.");
|
|
WifiManager.getScanResults(function(r) {
|
|
// Failure.
|
|
if (!r) {
|
|
self.wantScanResults.forEach(function(callback) { callback(null) });
|
|
self.wantScanResults = [];
|
|
return;
|
|
}
|
|
|
|
let capabilities = WifiManager.getCapabilities();
|
|
|
|
// Now that we have scan results, there's no more need to continue
|
|
// scanning. Ignore any errors from this command.
|
|
WifiManager.setScanMode("inactive", function() {});
|
|
let lines = r.split("\n");
|
|
// NB: Skip the header line.
|
|
self.networksArray = [];
|
|
for (let i = 1; i < lines.length; ++i) {
|
|
// bssid / frequency / signal level / flags / ssid
|
|
var match = /([\S]+)\s+([\S]+)\s+([\S]+)\s+(\[[\S]+\])?\s(.*)/.exec(lines[i]);
|
|
|
|
if (match && match[5]) {
|
|
let ssid = match[5],
|
|
bssid = match[1],
|
|
frequency = match[2],
|
|
signalLevel = match[3],
|
|
flags = match[4];
|
|
|
|
/* Skip networks with unknown or unsupported modes. */
|
|
if (capabilities.mode.indexOf(getMode(flags)) == -1)
|
|
continue;
|
|
|
|
// If this is the first time that we've seen this SSID in the scan
|
|
// results, add it to the list along with any other information.
|
|
// Also, we use the highest signal strength that we see.
|
|
let network = new ScanResult(ssid, bssid, frequency, flags, signalLevel);
|
|
|
|
let networkKey = getNetworkKey(network);
|
|
let eapIndex = -1;
|
|
if (networkKey in self.configuredNetworks) {
|
|
let known = self.configuredNetworks[networkKey];
|
|
network.known = true;
|
|
|
|
if ("identity" in known && known.identity)
|
|
network.identity = dequote(known.identity);
|
|
|
|
// Note: we don't hand out passwords here! The * marks that there
|
|
// is a password that we're hiding.
|
|
if (("psk" in known && known.psk) ||
|
|
("password" in known && known.password) ||
|
|
("wep_key0" in known && known.wep_key0)) {
|
|
network.password = "*";
|
|
}
|
|
}
|
|
|
|
self.networksArray.push(network);
|
|
if (network.bssid === WifiManager.connectionInfo.bssid)
|
|
network.connected = true;
|
|
|
|
let signal = calculateSignal(Number(match[3]));
|
|
if (signal > network.relSignalStrength)
|
|
network.relSignalStrength = signal;
|
|
} else if (!match) {
|
|
debug("Match didn't find anything for: " + lines[i]);
|
|
}
|
|
}
|
|
|
|
self.wantScanResults.forEach(function(callback) { callback(self.networksArray) });
|
|
self.wantScanResults = [];
|
|
});
|
|
};
|
|
|
|
WifiManager.onstationinfoupdate = function() {
|
|
self._fireEvent("stationinfoupdate", { station: this.station });
|
|
};
|
|
|
|
WifiManager.onstopconnectioninfotimer = function() {
|
|
self._stopConnectionInfoTimer();
|
|
};
|
|
|
|
// Read the 'wifi.enabled' setting in order to start with a known
|
|
// value at boot time. The handle() will be called after reading.
|
|
//
|
|
// nsISettingsServiceCallback implementation.
|
|
var initWifiEnabledCb = {
|
|
handle: function handle(aName, aResult) {
|
|
if (aName !== SETTINGS_WIFI_ENABLED)
|
|
return;
|
|
if (aResult === null)
|
|
aResult = true;
|
|
self.handleWifiEnabled(aResult);
|
|
},
|
|
handleError: function handleError(aErrorMessage) {
|
|
debug("Error reading the 'wifi.enabled' setting. Default to wifi on.");
|
|
self.handleWifiEnabled(true);
|
|
}
|
|
};
|
|
|
|
var initWifiDebuggingEnabledCb = {
|
|
handle: function handle(aName, aResult) {
|
|
if (aName !== SETTINGS_WIFI_DEBUG_ENABLED)
|
|
return;
|
|
if (aResult === null)
|
|
aResult = false;
|
|
DEBUG = aResult;
|
|
updateDebug();
|
|
},
|
|
handleError: function handleError(aErrorMessage) {
|
|
debug("Error reading the 'wifi.debugging.enabled' setting. Default to debugging off.");
|
|
DEBUG = false;
|
|
updateDebug();
|
|
}
|
|
};
|
|
|
|
this.initTetheringSettings();
|
|
|
|
let lock = gSettingsService.createLock();
|
|
lock.get(SETTINGS_WIFI_ENABLED, initWifiEnabledCb);
|
|
lock.get(SETTINGS_WIFI_DEBUG_ENABLED, initWifiDebuggingEnabledCb);
|
|
|
|
lock.get(SETTINGS_WIFI_SSID, this);
|
|
lock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
|
|
lock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
|
|
lock.get(SETTINGS_WIFI_IP, this);
|
|
lock.get(SETTINGS_WIFI_PREFIX, this);
|
|
lock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
|
|
lock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
|
|
lock.get(SETTINGS_WIFI_DNS1, this);
|
|
lock.get(SETTINGS_WIFI_DNS2, this);
|
|
lock.get(SETTINGS_WIFI_TETHERING_ENABLED, this);
|
|
|
|
lock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
|
|
lock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
|
|
|
|
this._wifiTetheringSettingsToRead = [SETTINGS_WIFI_SSID,
|
|
SETTINGS_WIFI_SECURITY_TYPE,
|
|
SETTINGS_WIFI_SECURITY_PASSWORD,
|
|
SETTINGS_WIFI_IP,
|
|
SETTINGS_WIFI_PREFIX,
|
|
SETTINGS_WIFI_DHCPSERVER_STARTIP,
|
|
SETTINGS_WIFI_DHCPSERVER_ENDIP,
|
|
SETTINGS_WIFI_DNS1,
|
|
SETTINGS_WIFI_DNS2,
|
|
SETTINGS_WIFI_TETHERING_ENABLED,
|
|
SETTINGS_USB_DHCPSERVER_STARTIP,
|
|
SETTINGS_USB_DHCPSERVER_ENDIP];
|
|
}
|
|
|
|
function translateState(state) {
|
|
switch (state) {
|
|
case "INTERFACE_DISABLED":
|
|
case "INACTIVE":
|
|
case "SCANNING":
|
|
case "DISCONNECTED":
|
|
default:
|
|
return "disconnected";
|
|
|
|
case "AUTHENTICATING":
|
|
case "ASSOCIATING":
|
|
case "ASSOCIATED":
|
|
case "FOUR_WAY_HANDSHAKE":
|
|
case "GROUP_HANDSHAKE":
|
|
return "connecting";
|
|
|
|
case "COMPLETED":
|
|
return WifiManager.getDhcpInfo() ? "connected" : "associated";
|
|
}
|
|
}
|
|
|
|
WifiWorker.prototype = {
|
|
classID: WIFIWORKER_CID,
|
|
classInfo: XPCOMUtils.generateCI({classID: WIFIWORKER_CID,
|
|
contractID: WIFIWORKER_CONTRACTID,
|
|
classDescription: "WifiWorker",
|
|
interfaces: [Ci.nsIWorkerHolder,
|
|
Ci.nsIWifi,
|
|
Ci.nsIObserver]}),
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
|
|
Ci.nsIWifi,
|
|
Ci.nsIObserver,
|
|
Ci.nsISettingsServiceCallback]),
|
|
|
|
disconnectedByWifi: false,
|
|
|
|
disconnectedByWifiTethering: false,
|
|
|
|
_wifiTetheringSettingsToRead: [],
|
|
|
|
_oldWifiTetheringEnabledState: null,
|
|
|
|
tetheringSettings: {},
|
|
|
|
initTetheringSettings: function initTetheringSettings() {
|
|
this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = null;
|
|
this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
|
|
this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
|
|
this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
|
|
this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
|
|
this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
|
|
this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
|
|
this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
|
|
this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
|
|
this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
|
|
|
|
this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
|
|
this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
|
|
},
|
|
|
|
// Internal methods.
|
|
waitForScan: function(callback) {
|
|
this.wantScanResults.push(callback);
|
|
},
|
|
|
|
// In order to select a specific network, we disable the rest of the
|
|
// networks known to us. However, in general, we want the supplicant to
|
|
// connect to which ever network it thinks is best, so when we select the
|
|
// proper network (or fail to), we need to re-enable the rest.
|
|
_enableAllNetworks: function() {
|
|
for (let key in this.configuredNetworks) {
|
|
let net = this.configuredNetworks[key];
|
|
WifiManager.enableNetwork(net.netId, false, function(ok) {
|
|
net.disabled = ok ? 1 : 0;
|
|
});
|
|
}
|
|
},
|
|
|
|
_startConnectionInfoTimer: function() {
|
|
if (this._connectionInfoTimer)
|
|
return;
|
|
|
|
var self = this;
|
|
function getConnectionInformation() {
|
|
WifiManager.getConnectionInfo(function(connInfo) {
|
|
// See comments in calculateSignal for information about this.
|
|
if (!connInfo) {
|
|
self._lastConnectionInfo = null;
|
|
return;
|
|
}
|
|
|
|
let { rssi, linkspeed } = connInfo;
|
|
if (rssi > 0)
|
|
rssi -= 256;
|
|
if (rssi <= MIN_RSSI)
|
|
rssi = MIN_RSSI;
|
|
else if (rssi >= MAX_RSSI)
|
|
rssi = MAX_RSSI;
|
|
|
|
let info = { signalStrength: rssi,
|
|
relSignalStrength: calculateSignal(rssi),
|
|
linkSpeed: linkspeed,
|
|
ipAddress: self.ipAddress };
|
|
let last = self._lastConnectionInfo;
|
|
|
|
// Only fire the event if the link speed changed or the signal
|
|
// strength changed by more than 10%.
|
|
function tensPlace(percent) {
|
|
return (percent / 10) | 0;
|
|
}
|
|
|
|
if (last && last.linkSpeed === info.linkSpeed &&
|
|
last.ipAddress === info.ipAddress &&
|
|
tensPlace(last.relSignalStrength) === tensPlace(info.relSignalStrength)) {
|
|
return;
|
|
}
|
|
|
|
self._lastConnectionInfo = info;
|
|
debug("Firing connectioninfoupdate: " + uneval(info));
|
|
self._fireEvent("connectioninfoupdate", info);
|
|
});
|
|
}
|
|
|
|
// Prime our _lastConnectionInfo immediately and fire the event at the
|
|
// same time.
|
|
getConnectionInformation();
|
|
|
|
// Now, set up the timer for regular updates.
|
|
this._connectionInfoTimer =
|
|
Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
this._connectionInfoTimer.init(getConnectionInformation, 5000,
|
|
Ci.nsITimer.TYPE_REPEATING_SLACK);
|
|
},
|
|
|
|
_stopConnectionInfoTimer: function() {
|
|
if (!this._connectionInfoTimer)
|
|
return;
|
|
|
|
this._connectionInfoTimer.cancel();
|
|
this._connectionInfoTimer = null;
|
|
this._lastConnectionInfo = null;
|
|
},
|
|
|
|
_reloadConfiguredNetworks: function(callback) {
|
|
WifiManager.getConfiguredNetworks((function(networks) {
|
|
if (!networks) {
|
|
debug("Unable to get configured networks");
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
this._highestPriority = -1;
|
|
|
|
// Convert between netId-based and ssid-based indexing.
|
|
for (let net in networks) {
|
|
let network = networks[net];
|
|
delete networks[net];
|
|
|
|
if (!network.ssid) {
|
|
WifiManager.removeNetwork(network.netId, function() {});
|
|
continue;
|
|
}
|
|
|
|
if (network.hasOwnProperty("priority") &&
|
|
network.priority > this._highestPriority) {
|
|
this._highestPriority = network.priority;
|
|
}
|
|
|
|
let networkKey = getNetworkKey(network);
|
|
// Accept latest config of same network(same SSID and same security).
|
|
if (networks[networkKey]) {
|
|
WifiManager.removeNetwork(networks[networkKey].netId, function() {});
|
|
}
|
|
networks[networkKey] = network;
|
|
}
|
|
|
|
this.configuredNetworks = networks;
|
|
callback(true);
|
|
}).bind(this));
|
|
},
|
|
|
|
// Important side effect: calls WifiManager.saveConfig.
|
|
_reprioritizeNetworks: function(callback) {
|
|
// First, sort the networks in orer of their priority.
|
|
var ordered = Object.getOwnPropertyNames(this.configuredNetworks);
|
|
let self = this;
|
|
ordered.sort(function(a, b) {
|
|
var neta = self.configuredNetworks[a],
|
|
netb = self.configuredNetworks[b];
|
|
|
|
// Sort unsorted networks to the end of the list.
|
|
if (isNaN(neta.priority))
|
|
return isNaN(netb.priority) ? 0 : 1;
|
|
if (isNaN(netb.priority))
|
|
return -1;
|
|
return netb.priority - neta.priority;
|
|
});
|
|
|
|
// Skip unsorted networks.
|
|
let newPriority = 0, i;
|
|
for (i = ordered.length - 1; i >= 0; --i) {
|
|
if (!isNaN(this.configuredNetworks[ordered[i]].priority))
|
|
break;
|
|
}
|
|
|
|
// No networks we care about?
|
|
if (i < 0) {
|
|
WifiManager.saveConfig(callback);
|
|
return;
|
|
}
|
|
|
|
// Now assign priorities from 0 to length, starting with the smallest
|
|
// priority and heading towards the highest (note the dependency between
|
|
// total and i here).
|
|
let done = 0, errors = 0, total = i + 1;
|
|
for (; i >= 0; --i) {
|
|
let network = this.configuredNetworks[ordered[i]];
|
|
network.priority = newPriority++;
|
|
|
|
// Note: networkUpdated declared below since it happens logically after
|
|
// this loop.
|
|
WifiManager.updateNetwork(network, networkUpdated);
|
|
}
|
|
|
|
function networkUpdated(ok) {
|
|
if (!ok)
|
|
++errors;
|
|
if (++done === total) {
|
|
if (errors > 0) {
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
WifiManager.saveConfig(function(ok) {
|
|
if (!ok) {
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
self._reloadConfiguredNetworks(function(ok) {
|
|
callback(ok);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
// nsIWifi
|
|
|
|
_domManagers: [],
|
|
_fireEvent: function(message, data) {
|
|
this._domManagers.forEach(function(manager) {
|
|
// Note: We should never have a dead message manager here because we
|
|
// observe our child message managers shutting down, below.
|
|
manager.sendAsyncMessage("WifiManager:" + message, data);
|
|
});
|
|
},
|
|
|
|
_sendMessage: function(message, success, data, msg) {
|
|
try {
|
|
msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"),
|
|
{ data: data, rid: msg.rid, mid: msg.mid });
|
|
} catch (e) {
|
|
debug("sendAsyncMessage error : " + e);
|
|
}
|
|
this._splicePendingRequest(msg);
|
|
},
|
|
|
|
_domRequest: [],
|
|
|
|
_splicePendingRequest: function(msg) {
|
|
for (let i = 0; i < this._domRequest.length; i++) {
|
|
if (this._domRequest[i].msg === msg) {
|
|
this._domRequest.splice(i, 1);
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
_clearPendingRequest: function() {
|
|
if (this._domRequest.length === 0) return;
|
|
this._domRequest.forEach((function(req) {
|
|
this._sendMessage(req.name + ":Return", false, "Wifi is disabled", req.msg);
|
|
}).bind(this));
|
|
},
|
|
|
|
receiveMessage: function MessageManager_receiveMessage(aMessage) {
|
|
let msg = aMessage.data || {};
|
|
msg.manager = aMessage.target;
|
|
|
|
if (WifiManager.p2pSupported()) {
|
|
// If p2pObserver returns something truthy, return it!
|
|
// Otherwise, continue to do the rest of tasks.
|
|
var p2pRet = this._p2pObserver.onDOMMessage(aMessage);
|
|
if (p2pRet) {
|
|
return p2pRet;
|
|
}
|
|
}
|
|
|
|
// Note: By the time we receive child-process-shutdown, the child process
|
|
// has already forgotten its permissions so we do this before the
|
|
// permissions check.
|
|
if (aMessage.name === "child-process-shutdown") {
|
|
let i;
|
|
if ((i = this._domManagers.indexOf(msg.manager)) != -1) {
|
|
this._domManagers.splice(i, 1);
|
|
}
|
|
for (i = this._domRequest.length - 1; i >= 0; i--) {
|
|
if (this._domRequest[i].msg.manager === msg.manager) {
|
|
this._domRequest.splice(i, 1);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!aMessage.target.assertPermission("wifi-manage")) {
|
|
return;
|
|
}
|
|
|
|
// We are interested in DOMRequests only.
|
|
if (aMessage.name != "WifiManager:getState") {
|
|
this._domRequest.push({name: aMessage.name, msg:msg});
|
|
}
|
|
|
|
switch (aMessage.name) {
|
|
case "WifiManager:setWifiEnabled":
|
|
this.setWifiEnabled(msg);
|
|
break;
|
|
case "WifiManager:getNetworks":
|
|
this.getNetworks(msg);
|
|
break;
|
|
case "WifiManager:getKnownNetworks":
|
|
this.getKnownNetworks(msg);
|
|
break;
|
|
case "WifiManager:associate":
|
|
this.associate(msg);
|
|
break;
|
|
case "WifiManager:forget":
|
|
this.forget(msg);
|
|
break;
|
|
case "WifiManager:wps":
|
|
this.wps(msg);
|
|
break;
|
|
case "WifiManager:setPowerSavingMode":
|
|
this.setPowerSavingMode(msg);
|
|
break;
|
|
case "WifiManager:setHttpProxy":
|
|
this.setHttpProxy(msg);
|
|
break;
|
|
case "WifiManager:setStaticIpMode":
|
|
this.setStaticIpMode(msg);
|
|
break;
|
|
case "WifiManager:importCert":
|
|
this.importCert(msg);
|
|
break;
|
|
case "WifiManager:getImportedCerts":
|
|
this.getImportedCerts(msg);
|
|
break;
|
|
case "WifiManager:deleteCert":
|
|
this.deleteCert(msg);
|
|
break;
|
|
case "WifiManager:setWifiTethering":
|
|
this.setWifiTethering(msg);
|
|
break;
|
|
case "WifiManager:getState": {
|
|
let i;
|
|
if ((i = this._domManagers.indexOf(msg.manager)) === -1) {
|
|
this._domManagers.push(msg.manager);
|
|
}
|
|
|
|
let net = this.currentNetwork ? netToDOM(this.currentNetwork) : null;
|
|
return { network: net,
|
|
connectionInfo: this._lastConnectionInfo,
|
|
enabled: WifiManager.enabled,
|
|
status: translateState(WifiManager.state),
|
|
macAddress: this.macAddress,
|
|
capabilities: WifiManager.getCapabilities()};
|
|
}
|
|
}
|
|
},
|
|
|
|
getNetworks: function(msg) {
|
|
const message = "WifiManager:getNetworks:Return";
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
let sent = false;
|
|
let callback = (function (networks) {
|
|
if (sent)
|
|
return;
|
|
sent = true;
|
|
this._sendMessage(message, networks !== null, networks, msg);
|
|
}).bind(this);
|
|
this.waitForScan(callback);
|
|
|
|
WifiManager.scan(true, (function(ok) {
|
|
// If the scan command succeeded, we're done.
|
|
if (ok)
|
|
return;
|
|
|
|
// Avoid sending multiple responses.
|
|
if (sent)
|
|
return;
|
|
|
|
// Otherwise, let the client know that it failed, it's responsible for
|
|
// trying again in a few seconds.
|
|
sent = true;
|
|
this._sendMessage(message, false, "ScanFailed", msg);
|
|
}).bind(this));
|
|
},
|
|
|
|
getWifiScanResults: function(callback) {
|
|
var count = 0;
|
|
var timer = null;
|
|
var self = this;
|
|
|
|
if (!WifiManager.enabled) {
|
|
callback.onfailure();
|
|
return;
|
|
}
|
|
|
|
self.waitForScan(waitForScanCallback);
|
|
doScan();
|
|
function doScan() {
|
|
WifiManager.scan(true, (function (ok) {
|
|
if (!ok) {
|
|
if (!timer) {
|
|
count = 0;
|
|
timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
}
|
|
|
|
if (count++ >= 3) {
|
|
timer = null;
|
|
self.wantScanResults.splice(self.wantScanResults.indexOf(waitForScanCallback), 1);
|
|
callback.onfailure();
|
|
return;
|
|
}
|
|
|
|
// Else it's still running, continue waiting.
|
|
timer.initWithCallback(doScan, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
|
|
return;
|
|
}
|
|
}).bind(this));
|
|
}
|
|
|
|
function waitForScanCallback(networks) {
|
|
if (networks === null) {
|
|
callback.onfailure();
|
|
return;
|
|
}
|
|
|
|
var wifiScanResults = new Array();
|
|
var net;
|
|
for (let net in networks) {
|
|
let value = networks[net];
|
|
wifiScanResults.push(transformResult(value));
|
|
}
|
|
callback.onready(wifiScanResults.length, wifiScanResults);
|
|
}
|
|
|
|
function transformResult(element) {
|
|
var result = new WifiScanResult();
|
|
result.connected = false;
|
|
for (let id in element) {
|
|
if (id === "__exposedProps__") {
|
|
continue;
|
|
}
|
|
if (id === "security") {
|
|
result[id] = 0;
|
|
var security = element[id];
|
|
for (let j = 0; j < security.length; j++) {
|
|
if (security[j] === "WPA-PSK") {
|
|
result[id] |= Ci.nsIWifiScanResult.WPA_PSK;
|
|
} else if (security[j] === "WPA-EAP") {
|
|
result[id] |= Ci.nsIWifiScanResult.WPA_EAP;
|
|
} else if (security[j] === "WEP") {
|
|
result[id] |= Ci.nsIWifiScanResult.WEP;
|
|
} else {
|
|
result[id] = 0;
|
|
}
|
|
}
|
|
} else {
|
|
result[id] = element[id];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
},
|
|
|
|
getKnownNetworks: function(msg) {
|
|
const message = "WifiManager:getKnownNetworks:Return";
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
this._reloadConfiguredNetworks((function(ok) {
|
|
if (!ok) {
|
|
this._sendMessage(message, false, "Failed", msg);
|
|
return;
|
|
}
|
|
|
|
var networks = [];
|
|
for (let networkKey in this.configuredNetworks) {
|
|
networks.push(netToDOM(this.configuredNetworks[networkKey]));
|
|
}
|
|
|
|
this._sendMessage(message, true, networks, msg);
|
|
}).bind(this));
|
|
},
|
|
|
|
_setWifiEnabledCallback: function(status) {
|
|
if (status !== 0) {
|
|
this.requestDone();
|
|
return;
|
|
}
|
|
|
|
// If we're enabling ourselves, then wait until we've connected to the
|
|
// supplicant to notify. If we're disabling, we take care of this in
|
|
// supplicantlost.
|
|
if (WifiManager.supplicantStarted)
|
|
WifiManager.start();
|
|
},
|
|
|
|
/**
|
|
* Compatibility flags for detecting if Gaia is controlling wifi by settings
|
|
* or API, once API is called, gecko will no longer accept wifi enable
|
|
* control from settings.
|
|
* This is used to deal with compatibility issue while Gaia adopted to use
|
|
* API but gecko doesn't remove the settings code in time.
|
|
* TODO: Remove this flag in Bug 1050147
|
|
*/
|
|
ignoreWifiEnabledFromSettings: false,
|
|
setWifiEnabled: function(msg) {
|
|
const message = "WifiManager:setWifiEnabled:Return";
|
|
let self = this;
|
|
let enabled = msg.data;
|
|
|
|
self.ignoreWifiEnabledFromSettings = true;
|
|
// No change.
|
|
if (enabled === WifiManager.enabled) {
|
|
this._sendMessage(message, true, true, msg);
|
|
return;
|
|
}
|
|
|
|
// Can't enable wifi while hotspot mode is enabled.
|
|
if (enabled && (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
|
|
WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState))) {
|
|
self._sendMessage(message, false, "Can't enable Wifi while hotspot mode is enabled", msg);
|
|
return;
|
|
}
|
|
|
|
WifiManager.setWifiEnabled(enabled, function(ok) {
|
|
if (ok === 0 || ok === "no change") {
|
|
self._sendMessage(message, true, true, msg);
|
|
|
|
// Reply error to pending requests.
|
|
if (!enabled) {
|
|
self._clearPendingRequest();
|
|
} else {
|
|
WifiManager.start();
|
|
}
|
|
} else {
|
|
self._sendMessage(message, false, "Set wifi enabled failed", msg);
|
|
}
|
|
});
|
|
},
|
|
|
|
_setWifiEnabled: function(enabled, callback) {
|
|
// Reply error to pending requests.
|
|
if (!enabled) {
|
|
this._clearPendingRequest();
|
|
}
|
|
|
|
WifiManager.setWifiEnabled(enabled, callback);
|
|
},
|
|
|
|
// requestDone() must be called to before callback complete(or error)
|
|
// so next queue in the request quene can be executed.
|
|
// TODO: Remove command queue in Bug 1050147
|
|
queueRequest: function(data, callback) {
|
|
if (!callback) {
|
|
throw "Try to enqueue a request without callback";
|
|
}
|
|
|
|
let optimizeCommandList = ["setWifiEnabled", "setWifiApEnabled"];
|
|
if (optimizeCommandList.indexOf(data.command) != -1) {
|
|
this._stateRequests = this._stateRequests.filter(function(element) {
|
|
return element.data.command !== data.command;
|
|
});
|
|
}
|
|
|
|
this._stateRequests.push({
|
|
data: data,
|
|
callback: callback
|
|
});
|
|
|
|
this.nextRequest();
|
|
},
|
|
|
|
getWifiTetheringParameters: function getWifiTetheringParameters(enable) {
|
|
if (this.useTetheringAPI) {
|
|
return this.getWifiTetheringConfiguration(enable);
|
|
} else {
|
|
return this.getWifiTetheringParametersBySetting(enable);
|
|
}
|
|
},
|
|
|
|
getWifiTetheringConfiguration: function getWifiTetheringConfiguration(enable) {
|
|
let config = {};
|
|
let params = this.tetheringConfig;
|
|
|
|
let check = function(field, _default) {
|
|
config[field] = field in params ? params[field] : _default;
|
|
};
|
|
|
|
check("ssid", DEFAULT_WIFI_SSID);
|
|
check("security", DEFAULT_WIFI_SECURITY_TYPE);
|
|
check("key", DEFAULT_WIFI_SECURITY_PASSWORD);
|
|
check("ip", DEFAULT_WIFI_IP);
|
|
check("prefix", DEFAULT_WIFI_PREFIX);
|
|
check("wifiStartIp", DEFAULT_WIFI_DHCPSERVER_STARTIP);
|
|
check("wifiEndIp", DEFAULT_WIFI_DHCPSERVER_ENDIP);
|
|
check("usbStartIp", DEFAULT_USB_DHCPSERVER_STARTIP);
|
|
check("usbEndIp", DEFAULT_USB_DHCPSERVER_ENDIP);
|
|
check("dns1", DEFAULT_DNS1);
|
|
check("dns2", DEFAULT_DNS2);
|
|
|
|
config.enable = enable;
|
|
config.mode = enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION;
|
|
config.link = enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN;
|
|
|
|
// Check the format to prevent netd from crash.
|
|
if (enable && (!config.ssid || config.ssid == "")) {
|
|
debug("Invalid SSID value.");
|
|
return null;
|
|
}
|
|
|
|
if (enable && (config.security != WIFI_SECURITY_TYPE_NONE && !config.key)) {
|
|
debug("Invalid security password.");
|
|
return null;
|
|
}
|
|
|
|
// Using the default values here until application supports these settings.
|
|
if (config.ip == "" || config.prefix == "" ||
|
|
config.wifiStartIp == "" || config.wifiEndIp == "" ||
|
|
config.usbStartIp == "" || config.usbEndIp == "") {
|
|
debug("Invalid subnet information.");
|
|
return null;
|
|
}
|
|
|
|
return config;
|
|
},
|
|
|
|
getWifiTetheringParametersBySetting: function getWifiTetheringParametersBySetting(enable) {
|
|
let ssid;
|
|
let securityType;
|
|
let securityId;
|
|
let interfaceIp;
|
|
let prefix;
|
|
let wifiDhcpStartIp;
|
|
let wifiDhcpEndIp;
|
|
let usbDhcpStartIp;
|
|
let usbDhcpEndIp;
|
|
let dns1;
|
|
let dns2;
|
|
|
|
ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
|
|
securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
|
|
securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
|
|
interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
|
|
prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
|
|
wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
|
|
wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
|
|
usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP];
|
|
usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP];
|
|
dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
|
|
dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
|
|
|
|
// Check the format to prevent netd from crash.
|
|
if (!ssid || ssid == "") {
|
|
debug("Invalid SSID value.");
|
|
return null;
|
|
}
|
|
// Truncate ssid if its length of encoded to utf8 is longer than 32.
|
|
while (unescape(encodeURIComponent(ssid)).length > 32)
|
|
{
|
|
ssid = ssid.substring(0, ssid.length-1);
|
|
}
|
|
|
|
if (securityType != WIFI_SECURITY_TYPE_NONE &&
|
|
securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
|
|
securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
|
|
|
|
debug("Invalid security type.");
|
|
return null;
|
|
}
|
|
if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
|
|
debug("Invalid security password.");
|
|
return null;
|
|
}
|
|
// Using the default values here until application supports these settings.
|
|
if (interfaceIp == "" || prefix == "" ||
|
|
wifiDhcpStartIp == "" || wifiDhcpEndIp == "" ||
|
|
usbDhcpStartIp == "" || usbDhcpEndIp == "") {
|
|
debug("Invalid subnet information.");
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
ssid: ssid,
|
|
security: securityType,
|
|
key: securityId,
|
|
ip: interfaceIp,
|
|
prefix: prefix,
|
|
wifiStartIp: wifiDhcpStartIp,
|
|
wifiEndIp: wifiDhcpEndIp,
|
|
usbStartIp: usbDhcpStartIp,
|
|
usbEndIp: usbDhcpEndIp,
|
|
dns1: dns1,
|
|
dns2: dns2,
|
|
enable: enable,
|
|
mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
|
|
link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
|
|
};
|
|
},
|
|
|
|
setWifiApEnabled: function(enabled, callback) {
|
|
let configuration = this.getWifiTetheringParameters(enabled);
|
|
|
|
if (!configuration) {
|
|
this.requestDone();
|
|
debug("Invalid Wifi Tethering configuration.");
|
|
return;
|
|
}
|
|
|
|
WifiManager.setWifiApEnabled(enabled, configuration, callback);
|
|
},
|
|
|
|
associate: function(msg) {
|
|
const MAX_PRIORITY = 9999;
|
|
const message = "WifiManager:associate:Return";
|
|
let network = msg.data;
|
|
|
|
let privnet = network;
|
|
let dontConnect = privnet.dontConnect;
|
|
delete privnet.dontConnect;
|
|
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
let self = this;
|
|
function networkReady() {
|
|
// saveConfig now before we disable most of the other networks.
|
|
function selectAndConnect() {
|
|
WifiManager.enableNetwork(privnet.netId, true, function (ok) {
|
|
if (ok)
|
|
self._needToEnableNetworks = true;
|
|
if (WifiManager.state === "DISCONNECTED" ||
|
|
WifiManager.state === "SCANNING") {
|
|
WifiManager.reconnect(function (ok) {
|
|
self._sendMessage(message, ok, ok, msg);
|
|
});
|
|
} else {
|
|
self._sendMessage(message, ok, ok, msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
var selectAndConnectOrReturn = dontConnect ?
|
|
function() {
|
|
self._sendMessage(message, true, "Wifi has been recorded", msg);
|
|
} : selectAndConnect;
|
|
if (self._highestPriority >= MAX_PRIORITY) {
|
|
self._reprioritizeNetworks(selectAndConnectOrReturn);
|
|
} else {
|
|
WifiManager.saveConfig(selectAndConnectOrReturn);
|
|
}
|
|
}
|
|
|
|
function connectToNetwork() {
|
|
WifiManager.updateNetwork(privnet, (function(ok) {
|
|
if (!ok) {
|
|
self._sendMessage(message, false, "Network is misconfigured", msg);
|
|
return;
|
|
}
|
|
|
|
networkReady();
|
|
}));
|
|
}
|
|
|
|
let ssid = privnet.ssid;
|
|
let networkKey = getNetworkKey(privnet);
|
|
let configured;
|
|
|
|
if (networkKey in this._addingNetworks) {
|
|
this._sendMessage(message, false, "Racing associates");
|
|
return;
|
|
}
|
|
|
|
if (networkKey in this.configuredNetworks)
|
|
configured = this.configuredNetworks[networkKey];
|
|
|
|
netFromDOM(privnet, configured);
|
|
|
|
privnet.priority = ++this._highestPriority;
|
|
if (configured) {
|
|
privnet.netId = configured.netId;
|
|
// Sync priority back to configured so if priority reaches MAX_PRIORITY,
|
|
// it can be sorted correctly in _reprioritizeNetworks() because the
|
|
// function sort network based on priority in configure list.
|
|
configured.priority = privnet.priority;
|
|
|
|
// When investigating Bug 1123680, we observed that gaia may unexpectedly
|
|
// request to associate with incorrect password before successfully
|
|
// forgetting the network. It would cause the network unable to connect
|
|
// subsequently. Aside from preventing the racing forget/associate, we
|
|
// also try to disable network prior to updating the network.
|
|
WifiManager.getNetworkId(dequote(configured.ssid), function(netId) {
|
|
if (netId) {
|
|
WifiManager.disableNetwork(netId, function() {
|
|
connectToNetwork();
|
|
});
|
|
}
|
|
else {
|
|
connectToNetwork();
|
|
}
|
|
});
|
|
} else {
|
|
// networkReady, above, calls saveConfig. We want to remember the new
|
|
// network as being enabled, which isn't the default, so we explicitly
|
|
// set it to being "enabled" before we add it and save the
|
|
// configuration.
|
|
privnet.disabled = 0;
|
|
this._addingNetworks[networkKey] = privnet;
|
|
WifiManager.addNetwork(privnet, (function(ok) {
|
|
delete this._addingNetworks[networkKey];
|
|
|
|
if (!ok) {
|
|
this._sendMessage(message, false, "Network is misconfigured", msg);
|
|
return;
|
|
}
|
|
|
|
this.configuredNetworks[networkKey] = privnet;
|
|
networkReady();
|
|
}).bind(this));
|
|
}
|
|
},
|
|
|
|
forget: function(msg) {
|
|
const message = "WifiManager:forget:Return";
|
|
let network = msg.data;
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
this._reloadConfiguredNetworks((function(ok) {
|
|
// Give it a chance to remove the network even if reload is failed.
|
|
if (!ok) {
|
|
debug("Warning !!! Failed to reload the configured networks");
|
|
}
|
|
|
|
let ssid = network.ssid;
|
|
let networkKey = getNetworkKey(network);
|
|
if (!(networkKey in this.configuredNetworks)) {
|
|
this._sendMessage(message, false, "Trying to forget an unknown network", msg);
|
|
return;
|
|
}
|
|
|
|
let self = this;
|
|
let configured = this.configuredNetworks[networkKey];
|
|
this._reconnectOnDisconnect = (this.currentNetwork &&
|
|
(this.currentNetwork.ssid === ssid));
|
|
WifiManager.removeNetwork(configured.netId, function(ok) {
|
|
if (self._needToEnableNetworks) {
|
|
self._enableAllNetworks();
|
|
self._needToEnableNetworks = false;
|
|
}
|
|
|
|
if (!ok) {
|
|
self._sendMessage(message, false, "Unable to remove the network", msg);
|
|
self._reconnectOnDisconnect = false;
|
|
return;
|
|
}
|
|
|
|
WifiManager.saveConfig(function() {
|
|
self._reloadConfiguredNetworks(function() {
|
|
self._sendMessage(message, true, true, msg);
|
|
});
|
|
});
|
|
});
|
|
}).bind(this));
|
|
},
|
|
|
|
wps: function(msg) {
|
|
const message = "WifiManager:wps:Return";
|
|
let self = this;
|
|
let detail = msg.data;
|
|
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
if (detail.method === "pbc") {
|
|
WifiManager.wpsPbc(function(ok) {
|
|
if (ok)
|
|
self._sendMessage(message, true, true, msg);
|
|
else
|
|
self._sendMessage(message, false, "WPS PBC failed", msg);
|
|
});
|
|
} else if (detail.method === "pin") {
|
|
WifiManager.wpsPin(detail, function(pin) {
|
|
if (pin)
|
|
self._sendMessage(message, true, pin, msg);
|
|
else
|
|
self._sendMessage(message, false, "WPS PIN failed", msg);
|
|
});
|
|
} else if (detail.method === "cancel") {
|
|
WifiManager.wpsCancel(function(ok) {
|
|
if (ok)
|
|
self._sendMessage(message, true, true, msg);
|
|
else
|
|
self._sendMessage(message, false, "WPS Cancel failed", msg);
|
|
});
|
|
} else {
|
|
self._sendMessage(message, false, "Invalid WPS method=" + detail.method,
|
|
msg);
|
|
}
|
|
},
|
|
|
|
setPowerSavingMode: function(msg) {
|
|
const message = "WifiManager:setPowerSavingMode:Return";
|
|
let self = this;
|
|
let enabled = msg.data;
|
|
let mode = enabled ? "AUTO" : "ACTIVE";
|
|
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
// Some wifi drivers may not implement this command. Set power mode
|
|
// even if suspend optimization command failed.
|
|
WifiManager.setSuspendOptimizations(enabled, function(ok) {
|
|
WifiManager.setPowerMode(mode, function(ok) {
|
|
if (ok) {
|
|
self._sendMessage(message, true, true, msg);
|
|
} else {
|
|
self._sendMessage(message, false, "Set power saving mode failed", msg);
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
setHttpProxy: function(msg) {
|
|
const message = "WifiManager:setHttpProxy:Return";
|
|
let self = this;
|
|
let network = msg.data.network;
|
|
let info = msg.data.info;
|
|
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
WifiManager.configureHttpProxy(network, info, function(ok) {
|
|
if (ok) {
|
|
// If configured network is current connected network
|
|
// need update http proxy immediately.
|
|
let setNetworkKey = getNetworkKey(network);
|
|
let curNetworkKey = self.currentNetwork ? getNetworkKey(self.currentNetwork) : null;
|
|
if (setNetworkKey === curNetworkKey)
|
|
WifiManager.setHttpProxy(network);
|
|
|
|
self._sendMessage(message, true, true, msg);
|
|
} else {
|
|
self._sendMessage(message, false, "Set http proxy failed", msg);
|
|
}
|
|
});
|
|
},
|
|
|
|
setStaticIpMode: function(msg) {
|
|
const message = "WifiManager:setStaticIpMode:Return";
|
|
let self = this;
|
|
let network = msg.data.network;
|
|
let info = msg.data.info;
|
|
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
// To compatiable with DHCP returned info structure, do translation here
|
|
info.ipaddr_str = info.ipaddr;
|
|
info.proxy_str = info.proxy;
|
|
info.gateway_str = info.gateway;
|
|
info.dns1_str = info.dns1;
|
|
info.dns2_str = info.dns2;
|
|
|
|
WifiManager.setStaticIpMode(network, info, function(ok) {
|
|
if (ok) {
|
|
self._sendMessage(message, true, true, msg);
|
|
} else {
|
|
self._sendMessage(message, false, "Set static ip mode failed", msg);
|
|
}
|
|
});
|
|
},
|
|
|
|
importCert: function importCert(msg) {
|
|
const message = "WifiManager:importCert:Return";
|
|
let self = this;
|
|
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
WifiManager.importCert(msg.data, function(data) {
|
|
if (data.status === 0) {
|
|
let usageString = ["ServerCert", "UserCert"];
|
|
let usageArray = [];
|
|
for (let i = 0; i < usageString.length; i++) {
|
|
if (data.usageFlag & (0x01 << i)) {
|
|
usageArray.push(usageString[i]);
|
|
}
|
|
}
|
|
|
|
self._sendMessage(message, true, {
|
|
nickname: data.nickname,
|
|
usage: usageArray
|
|
}, msg);
|
|
} else {
|
|
self._sendMessage(message, false, "Import Cert failed", msg);
|
|
}
|
|
});
|
|
},
|
|
|
|
getImportedCerts: function getImportedCerts(msg) {
|
|
const message = "WifiManager:getImportedCerts:Return";
|
|
let self = this;
|
|
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
let certDB = Cc["@mozilla.org/security/x509certdb;1"]
|
|
.getService(Ci.nsIX509CertDB);
|
|
if (!certDB) {
|
|
self._sendMessage(message, false, "Failed to query NSS DB service", msg);
|
|
}
|
|
|
|
let certList = certDB.getCerts();
|
|
if (!certList) {
|
|
self._sendMessage(message, false, "Failed to get certificate List", msg);
|
|
}
|
|
|
|
let certListEnum = certList.getEnumerator();
|
|
if (!certListEnum) {
|
|
self._sendMessage(message, false, "Failed to get certificate List enumerator", msg);
|
|
}
|
|
let importedCerts = {
|
|
ServerCert: [],
|
|
UserCert: [],
|
|
};
|
|
let UsageMapping = {
|
|
SERVERCERT: "ServerCert",
|
|
USERCERT: "UserCert",
|
|
};
|
|
|
|
while (certListEnum.hasMoreElements()) {
|
|
let certInfo = certListEnum.getNext().QueryInterface(Ci.nsIX509Cert);
|
|
let certNicknameInfo = /WIFI\_([A-Z]*)\_(.*)/.exec(certInfo.nickname);
|
|
if (!certNicknameInfo) {
|
|
continue;
|
|
}
|
|
importedCerts[UsageMapping[certNicknameInfo[1]]].push(certNicknameInfo[2]);
|
|
}
|
|
|
|
self._sendMessage(message, true, importedCerts, msg);
|
|
},
|
|
|
|
deleteCert: function deleteCert(msg) {
|
|
const message = "WifiManager:deleteCert:Return";
|
|
let self = this;
|
|
|
|
if (!WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is disabled", msg);
|
|
return;
|
|
}
|
|
|
|
WifiManager.deleteCert(msg.data, function(data) {
|
|
self._sendMessage(message, data.status === 0, "Delete Cert failed", msg);
|
|
});
|
|
},
|
|
|
|
// TODO : These two variables should be removed once GAIA uses tethering API.
|
|
useTetheringAPI : false,
|
|
tetheringConfig : {},
|
|
|
|
setWifiTethering: function setWifiTethering(msg) {
|
|
const message = "WifiManager:setWifiTethering:Return";
|
|
let self = this;
|
|
let enabled = msg.data.enabled;
|
|
|
|
this.useTetheringAPI = true;
|
|
this.tetheringConfig = msg.data.config;
|
|
|
|
if (WifiManager.enabled) {
|
|
this._sendMessage(message, false, "Wifi is enabled", msg);
|
|
return;
|
|
}
|
|
|
|
this.setWifiApEnabled(enabled, function() {
|
|
if ((enabled && WifiManager.tetheringState == "COMPLETED") ||
|
|
(!enabled && WifiManager.tetheringState == "UNINITIALIZED")) {
|
|
self._sendMessage(message, true, msg.data, msg);
|
|
} else {
|
|
msg.data.reason = enabled ?
|
|
"Enable WIFI tethering faild" : "Disable WIFI tethering faild";
|
|
self._sendMessage(message, false, msg.data, msg);
|
|
}
|
|
});
|
|
},
|
|
|
|
// This is a bit ugly, but works. In particular, this depends on the fact
|
|
// that RadioManager never actually tries to get the worker from us.
|
|
get worker() { throw "Not implemented"; },
|
|
|
|
shutdown: function() {
|
|
debug("shutting down ...");
|
|
this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
|
|
this._setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
|
|
}.bind(this));
|
|
},
|
|
|
|
// TODO: Remove command queue in Bug 1050147.
|
|
requestProcessing: false, // Hold while dequeue and execution a request.
|
|
// Released upon the request is fully executed,
|
|
// i.e, mostly after callback is done.
|
|
requestDone: function requestDone() {
|
|
this.requestProcessing = false;
|
|
this.nextRequest();
|
|
},
|
|
|
|
nextRequest: function nextRequest() {
|
|
// No request to process
|
|
if (this._stateRequests.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Handling request, wait for it.
|
|
if (this.requestProcessing) {
|
|
return;
|
|
}
|
|
|
|
// Hold processing lock
|
|
this.requestProcessing = true;
|
|
|
|
// Find next valid request
|
|
let request = this._stateRequests.shift();
|
|
|
|
request.callback(request.data);
|
|
},
|
|
|
|
notifyTetheringOn: function notifyTetheringOn() {
|
|
// It's really sad that we don't have an API to notify the wifi
|
|
// hotspot status. Toggle settings to let gaia know that wifi hotspot
|
|
// is enabled.
|
|
let self = this;
|
|
this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = true;
|
|
this._oldWifiTetheringEnabledState = true;
|
|
gSettingsService.createLock().set(
|
|
SETTINGS_WIFI_TETHERING_ENABLED,
|
|
true,
|
|
{
|
|
handle: function(aName, aResult) {
|
|
self.requestDone();
|
|
},
|
|
handleError: function(aErrorMessage) {
|
|
self.requestDone();
|
|
}
|
|
});
|
|
},
|
|
|
|
notifyTetheringOff: function notifyTetheringOff() {
|
|
// It's really sad that we don't have an API to notify the wifi
|
|
// hotspot status. Toggle settings to let gaia know that wifi hotspot
|
|
// is disabled.
|
|
let self = this;
|
|
this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
|
|
this._oldWifiTetheringEnabledState = false;
|
|
gSettingsService.createLock().set(
|
|
SETTINGS_WIFI_TETHERING_ENABLED,
|
|
false,
|
|
{
|
|
handle: function(aName, aResult) {
|
|
self.requestDone();
|
|
},
|
|
handleError: function(aErrorMessage) {
|
|
self.requestDone();
|
|
}
|
|
});
|
|
},
|
|
|
|
handleWifiEnabled: function(enabled) {
|
|
if (this.ignoreWifiEnabledFromSettings) {
|
|
return;
|
|
}
|
|
|
|
// Make sure Wifi hotspot is idle before switching to Wifi mode.
|
|
if (enabled) {
|
|
this.queueRequest({command: "setWifiApEnabled", value: false}, function(data) {
|
|
if (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
|
|
WifiManager.isWifiTetheringEnabled(WifiManager.tetheringState)) {
|
|
this.disconnectedByWifi = true;
|
|
this.setWifiApEnabled(false, this.notifyTetheringOff.bind(this));
|
|
} else {
|
|
this.requestDone();
|
|
}
|
|
}.bind(this));
|
|
}
|
|
|
|
this.queueRequest({command: "setWifiEnabled", value: enabled}, function(data) {
|
|
this._setWifiEnabled(enabled, this._setWifiEnabledCallback.bind(this));
|
|
}.bind(this));
|
|
|
|
if (!enabled) {
|
|
this.queueRequest({command: "setWifiApEnabled", value: true}, function(data) {
|
|
if (this.disconnectedByWifi) {
|
|
this.setWifiApEnabled(true, this.notifyTetheringOn.bind(this));
|
|
} else {
|
|
this.requestDone();
|
|
}
|
|
this.disconnectedByWifi = false;
|
|
}.bind(this));
|
|
}
|
|
},
|
|
|
|
handleWifiTetheringEnabled: function(enabled) {
|
|
// Make sure Wifi is idle before switching to Wifi hotspot mode.
|
|
if (enabled) {
|
|
this.queueRequest({command: "setWifiEnabled", value: false}, function(data) {
|
|
if (WifiManager.isWifiEnabled(WifiManager.state)) {
|
|
this.disconnectedByWifiTethering = true;
|
|
this._setWifiEnabled(false, this._setWifiEnabledCallback.bind(this));
|
|
} else {
|
|
this.requestDone();
|
|
}
|
|
}.bind(this));
|
|
}
|
|
|
|
this.queueRequest({command: "setWifiApEnabled", value: enabled}, function(data) {
|
|
this.setWifiApEnabled(enabled, this.requestDone.bind(this));
|
|
}.bind(this));
|
|
|
|
if (!enabled) {
|
|
this.queueRequest({command: "setWifiEnabled", value: true}, function(data) {
|
|
if (this.disconnectedByWifiTethering) {
|
|
this._setWifiEnabled(true, this._setWifiEnabledCallback.bind(this));
|
|
} else {
|
|
this.requestDone();
|
|
}
|
|
this.disconnectedByWifiTethering = false;
|
|
}.bind(this));
|
|
}
|
|
},
|
|
|
|
// nsIObserver implementation
|
|
observe: function observe(subject, topic, data) {
|
|
switch (topic) {
|
|
case kMozSettingsChangedObserverTopic:
|
|
// To avoid WifiWorker setting the wifi again, don't need to deal with
|
|
// the "mozsettings-changed" event fired from internal setting.
|
|
if ("wrappedJSObject" in subject) {
|
|
subject = subject.wrappedJSObject;
|
|
}
|
|
if (subject.isInternalChange) {
|
|
return;
|
|
}
|
|
|
|
this.handle(subject.key, subject.value);
|
|
break;
|
|
|
|
case "xpcom-shutdown":
|
|
// Ensure the supplicant is detached from B2G to avoid XPCOM shutdown
|
|
// blocks forever.
|
|
WifiManager.ensureSupplicantDetached(() => {
|
|
let wifiService = Cc["@mozilla.org/wifi/service;1"].getService(Ci.nsIWifiProxyService);
|
|
wifiService.shutdown();
|
|
let wifiCertService = Cc["@mozilla.org/wifi/certservice;1"].getService(Ci.nsIWifiCertService);
|
|
wifiCertService.shutdown();
|
|
});
|
|
break;
|
|
}
|
|
},
|
|
|
|
handle: function handle(aName, aResult) {
|
|
switch(aName) {
|
|
// TODO: Remove function call in Bug 1050147.
|
|
case SETTINGS_WIFI_ENABLED:
|
|
this.handleWifiEnabled(aResult)
|
|
break;
|
|
case SETTINGS_WIFI_DEBUG_ENABLED:
|
|
if (aResult === null)
|
|
aResult = false;
|
|
DEBUG = aResult;
|
|
updateDebug();
|
|
break;
|
|
case SETTINGS_WIFI_TETHERING_ENABLED:
|
|
this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
|
|
// Fall through!
|
|
case SETTINGS_WIFI_SSID:
|
|
case SETTINGS_WIFI_SECURITY_TYPE:
|
|
case SETTINGS_WIFI_SECURITY_PASSWORD:
|
|
case SETTINGS_WIFI_IP:
|
|
case SETTINGS_WIFI_PREFIX:
|
|
case SETTINGS_WIFI_DHCPSERVER_STARTIP:
|
|
case SETTINGS_WIFI_DHCPSERVER_ENDIP:
|
|
case SETTINGS_WIFI_DNS1:
|
|
case SETTINGS_WIFI_DNS2:
|
|
case SETTINGS_USB_DHCPSERVER_STARTIP:
|
|
case SETTINGS_USB_DHCPSERVER_ENDIP:
|
|
// TODO: code related to wifi-tethering setting should be removed after GAIA
|
|
// use tethering API
|
|
if (this.useTetheringAPI) {
|
|
break;
|
|
}
|
|
|
|
if (aResult !== null) {
|
|
this.tetheringSettings[aName] = aResult;
|
|
}
|
|
debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
|
|
let index = this._wifiTetheringSettingsToRead.indexOf(aName);
|
|
|
|
if (index != -1) {
|
|
this._wifiTetheringSettingsToRead.splice(index, 1);
|
|
}
|
|
|
|
if (this._wifiTetheringSettingsToRead.length) {
|
|
debug("We haven't read completely the wifi Tethering data from settings db.");
|
|
break;
|
|
}
|
|
|
|
if (this._oldWifiTetheringEnabledState === this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
|
|
debug("No changes for SETTINGS_WIFI_TETHERING_ENABLED flag. Nothing to do.");
|
|
break;
|
|
}
|
|
|
|
if (this._oldWifiTetheringEnabledState === null &&
|
|
!this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
|
|
debug("Do nothing when initial settings for SETTINGS_WIFI_TETHERING_ENABLED flag is false.");
|
|
break;
|
|
}
|
|
|
|
this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
|
|
this.handleWifiTetheringEnabled(aResult)
|
|
break;
|
|
};
|
|
},
|
|
|
|
handleError: function handleError(aErrorMessage) {
|
|
debug("There was an error while reading Tethering settings.");
|
|
this.tetheringSettings = {};
|
|
this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
|
|
},
|
|
};
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiWorker]);
|
|
|
|
var debug;
|
|
function updateDebug() {
|
|
if (DEBUG) {
|
|
debug = function (s) {
|
|
dump("-*- WifiWorker component: " + s + "\n");
|
|
};
|
|
} else {
|
|
debug = function (s) {};
|
|
}
|
|
WifiManager.syncDebug();
|
|
}
|
|
updateDebug();
|