michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["WifiCommand"]; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/systemlibs.js"); michael@0: michael@0: const SUPP_PROP = "init.svc.wpa_supplicant"; michael@0: const WPA_SUPPLICANT = "wpa_supplicant"; michael@0: const DEBUG = false; michael@0: michael@0: this.WifiCommand = function(aControlMessage, aInterface) { michael@0: function debug(msg) { michael@0: if (DEBUG) { michael@0: dump('-------------- WifiCommand: ' + msg); michael@0: } michael@0: } michael@0: michael@0: var command = {}; michael@0: michael@0: //------------------------------------------------- michael@0: // General commands. michael@0: //------------------------------------------------- michael@0: michael@0: command.loadDriver = function (callback) { michael@0: voidControlMessage("load_driver", function(status) { michael@0: callback(status); michael@0: }); michael@0: }; michael@0: michael@0: command.unloadDriver = function (callback) { michael@0: voidControlMessage("unload_driver", function(status) { michael@0: callback(status); michael@0: }); michael@0: }; michael@0: michael@0: command.startSupplicant = function (callback) { michael@0: voidControlMessage("start_supplicant", callback); michael@0: }; michael@0: michael@0: command.killSupplicant = function (callback) { michael@0: // It is interesting to note that this function does exactly what michael@0: // wifi_stop_supplicant does. Unforunately, on the Galaxy S2, Samsung michael@0: // changed that function in a way that means that it doesn't recognize michael@0: // wpa_supplicant as already running. Therefore, we have to roll our own michael@0: // version here. michael@0: stopProcess(SUPP_PROP, WPA_SUPPLICANT, callback); michael@0: }; michael@0: michael@0: command.terminateSupplicant = function (callback) { michael@0: doBooleanCommand("TERMINATE", "OK", callback); michael@0: }; michael@0: michael@0: command.stopSupplicant = function (callback) { michael@0: voidControlMessage("stop_supplicant", callback); michael@0: }; michael@0: michael@0: command.listNetworks = function (callback) { michael@0: doStringCommand("LIST_NETWORKS", callback); michael@0: }; michael@0: michael@0: command.addNetwork = function (callback) { michael@0: doIntCommand("ADD_NETWORK", callback); michael@0: }; michael@0: michael@0: command.setNetworkVariable = function (netId, name, value, callback) { michael@0: doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + michael@0: value, "OK", callback); michael@0: }; michael@0: michael@0: command.getNetworkVariable = function (netId, name, callback) { michael@0: doStringCommand("GET_NETWORK " + netId + " " + name, callback); michael@0: }; michael@0: michael@0: command.removeNetwork = function (netId, callback) { michael@0: doBooleanCommand("REMOVE_NETWORK " + netId, "OK", callback); michael@0: }; michael@0: michael@0: command.enableNetwork = function (netId, disableOthers, callback) { michael@0: doBooleanCommand((disableOthers ? "SELECT_NETWORK " : "ENABLE_NETWORK ") + michael@0: netId, "OK", callback); michael@0: }; michael@0: michael@0: command.disableNetwork = function (netId, callback) { michael@0: doBooleanCommand("DISABLE_NETWORK " + netId, "OK", callback); michael@0: }; michael@0: michael@0: command.status = function (callback) { michael@0: doStringCommand("STATUS", callback); michael@0: }; michael@0: michael@0: command.ping = function (callback) { michael@0: doBooleanCommand("PING", "PONG", callback); michael@0: }; michael@0: michael@0: command.scanResults = function (callback) { michael@0: doStringCommand("SCAN_RESULTS", callback); michael@0: }; michael@0: michael@0: command.disconnect = function (callback) { michael@0: doBooleanCommand("DISCONNECT", "OK", callback); michael@0: }; michael@0: michael@0: command.reconnect = function (callback) { michael@0: doBooleanCommand("RECONNECT", "OK", callback); michael@0: }; michael@0: michael@0: command.reassociate = function (callback) { michael@0: doBooleanCommand("REASSOCIATE", "OK", callback); michael@0: }; michael@0: michael@0: command.setBackgroundScan = function (enable, callback) { michael@0: doBooleanCommand("SET pno " + (enable ? "1" : "0"), michael@0: "OK", michael@0: function(ok) { michael@0: callback(true, ok); michael@0: }); michael@0: }; michael@0: michael@0: command.doSetScanMode = function (setActive, callback) { michael@0: doBooleanCommand(setActive ? michael@0: "DRIVER SCAN-ACTIVE" : michael@0: "DRIVER SCAN-PASSIVE", "OK", callback); michael@0: }; michael@0: michael@0: command.scan = function (callback) { michael@0: doBooleanCommand("SCAN", "OK", callback); michael@0: }; michael@0: michael@0: command.setLogLevel = function (level, callback) { michael@0: doBooleanCommand("LOG_LEVEL " + level, "OK", callback); michael@0: }; michael@0: michael@0: command.getLogLevel = function (callback) { michael@0: doStringCommand("LOG_LEVEL", callback); michael@0: }; michael@0: michael@0: command.wpsPbc = function (iface, callback) { michael@0: doBooleanCommand("WPS_PBC" + (iface ? (" interface=" + iface) : ""), michael@0: "OK", callback); michael@0: }; michael@0: michael@0: command.wpsPin = function (detail, callback) { michael@0: doStringCommand("WPS_PIN " + michael@0: (detail.bssid === undefined ? "any" : detail.bssid) + michael@0: (detail.pin === undefined ? "" : (" " + detail.pin)) + michael@0: (detail.iface ? (" interface=" + detail.iface) : ""), michael@0: callback); michael@0: }; michael@0: michael@0: command.wpsCancel = function (callback) { michael@0: doBooleanCommand("WPS_CANCEL", "OK", callback); michael@0: }; michael@0: michael@0: command.startDriver = function (callback) { michael@0: doBooleanCommand("DRIVER START", "OK"); michael@0: }; michael@0: michael@0: command.stopDriver = function (callback) { michael@0: doBooleanCommand("DRIVER STOP", "OK"); michael@0: }; michael@0: michael@0: command.startPacketFiltering = function (callback) { michael@0: var commandChain = ["DRIVER RXFILTER-ADD 0", michael@0: "DRIVER RXFILTER-ADD 1", michael@0: "DRIVER RXFILTER-ADD 3", michael@0: "DRIVER RXFILTER-START"]; michael@0: michael@0: doBooleanCommandChain(commandChain, callback); michael@0: }; michael@0: michael@0: command.stopPacketFiltering = function (callback) { michael@0: var commandChain = ["DRIVER RXFILTER-STOP", michael@0: "DRIVER RXFILTER-REMOVE 3", michael@0: "DRIVER RXFILTER-REMOVE 1", michael@0: "DRIVER RXFILTER-REMOVE 0"]; michael@0: michael@0: doBooleanCommandChain(commandChain, callback); michael@0: }; michael@0: michael@0: command.doGetRssi = function (cmd, callback) { michael@0: doCommand(cmd, function(data) { michael@0: var rssi = -200; michael@0: michael@0: if (!data.status) { michael@0: // If we are associating, the reply is "OK". michael@0: var reply = data.reply; michael@0: if (reply !== "OK") { michael@0: // Format is: rssi XX". SSID can contain spaces. michael@0: var offset = reply.lastIndexOf("rssi "); michael@0: if (offset !== -1) { michael@0: rssi = reply.substr(offset + 5) | 0; michael@0: } michael@0: } michael@0: } michael@0: callback(rssi); michael@0: }); michael@0: }; michael@0: michael@0: command.getRssi = function (callback) { michael@0: command.doGetRssi("DRIVER RSSI", callback); michael@0: }; michael@0: michael@0: command.getRssiApprox = function (callback) { michael@0: command.doGetRssi("DRIVER RSSI-APPROX", callback); michael@0: }; michael@0: michael@0: command.getLinkSpeed = function (callback) { michael@0: doStringCommand("DRIVER LINKSPEED", function(reply) { michael@0: if (reply) { michael@0: reply = reply.split(" ")[1] | 0; // Format: LinkSpeed XX michael@0: } michael@0: callback(reply); michael@0: }); michael@0: }; michael@0: michael@0: command.getConnectionInfoICS = function (callback) { michael@0: doStringCommand("SIGNAL_POLL", function(reply) { michael@0: if (!reply) { michael@0: callback(null); michael@0: return; michael@0: } michael@0: michael@0: let rval = {}; michael@0: var lines = reply.split("\n"); michael@0: for (let i = 0; i < lines.length; ++i) { michael@0: let [key, value] = lines[i].split("="); michael@0: switch (key.toUpperCase()) { michael@0: case "RSSI": michael@0: rval.rssi = value | 0; michael@0: break; michael@0: case "LINKSPEED": michael@0: rval.linkspeed = value | 0; michael@0: break; michael@0: default: michael@0: // Ignore. michael@0: } michael@0: } michael@0: michael@0: callback(rval); michael@0: }); michael@0: }; michael@0: michael@0: command.getMacAddress = function (callback) { michael@0: doStringCommand("DRIVER MACADDR", function(reply) { michael@0: if (reply) { michael@0: reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX michael@0: } michael@0: callback(reply); michael@0: }); michael@0: }; michael@0: michael@0: command.setPowerModeICS = function (mode, callback) { michael@0: doBooleanCommand("DRIVER POWERMODE " + (mode === "AUTO" ? 0 : 1), "OK", callback); michael@0: }; michael@0: michael@0: command.setPowerModeJB = function (mode, callback) { michael@0: doBooleanCommand("SET ps " + (mode === "AUTO" ? 1 : 0), "OK", callback); michael@0: }; michael@0: michael@0: command.getPowerMode = function (callback) { michael@0: doStringCommand("DRIVER GETPOWER", function(reply) { michael@0: if (reply) { michael@0: reply = (reply.split()[2]|0); // Format: powermode = XX michael@0: } michael@0: callback(reply); michael@0: }); michael@0: }; michael@0: michael@0: command.setNumAllowedChannels = function (numChannels, callback) { michael@0: doBooleanCommand("DRIVER SCAN-CHANNELS " + numChannels, "OK", callback); michael@0: }; michael@0: michael@0: command.getNumAllowedChannels = function (callback) { michael@0: doStringCommand("DRIVER SCAN-CHANNELS", function(reply) { michael@0: if (reply) { michael@0: reply = (reply.split()[2]|0); // Format: Scan-Channels = X michael@0: } michael@0: callback(reply); michael@0: }); michael@0: }; michael@0: michael@0: command.setBluetoothCoexistenceMode = function (mode, callback) { michael@0: doBooleanCommand("DRIVER BTCOEXMODE " + mode, "OK", callback); michael@0: }; michael@0: michael@0: command.setBluetoothCoexistenceScanMode = function (mode, callback) { michael@0: doBooleanCommand("DRIVER BTCOEXSCAN-" + (mode ? "START" : "STOP"), michael@0: "OK", callback); michael@0: }; michael@0: michael@0: command.saveConfig = function (callback) { michael@0: // Make sure we never write out a value for AP_SCAN other than 1. michael@0: doBooleanCommand("AP_SCAN 1", "OK", function(ok) { michael@0: doBooleanCommand("SAVE_CONFIG", "OK", callback); michael@0: }); michael@0: }; michael@0: michael@0: command.reloadConfig = function (callback) { michael@0: doBooleanCommand("RECONFIGURE", "OK", callback); michael@0: }; michael@0: michael@0: command.setScanResultHandling = function (mode, callback) { michael@0: doBooleanCommand("AP_SCAN " + mode, "OK", callback); michael@0: }; michael@0: michael@0: command.addToBlacklist = function (bssid, callback) { michael@0: doBooleanCommand("BLACKLIST " + bssid, "OK", callback); michael@0: }; michael@0: michael@0: command.clearBlacklist = function (callback) { michael@0: doBooleanCommand("BLACKLIST clear", "OK", callback); michael@0: }; michael@0: michael@0: command.setSuspendOptimizationsICS = function (enabled, callback) { michael@0: doBooleanCommand("DRIVER SETSUSPENDOPT " + (enabled ? 0 : 1), michael@0: "OK", callback); michael@0: }; michael@0: michael@0: command.setSuspendOptimizationsJB = function (enabled, callback) { michael@0: doBooleanCommand("DRIVER SETSUSPENDMODE " + (enabled ? 1 : 0), michael@0: "OK", callback); michael@0: }; michael@0: michael@0: command.connectToSupplicant = function(callback) { michael@0: voidControlMessage("connect_to_supplicant", callback); michael@0: }; michael@0: michael@0: command.closeSupplicantConnection = function(callback) { michael@0: voidControlMessage("close_supplicant_connection", callback); michael@0: }; michael@0: michael@0: command.getMacAddress = function(callback) { michael@0: doStringCommand("DRIVER MACADDR", function(reply) { michael@0: if (reply) { michael@0: reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX michael@0: } michael@0: callback(reply); michael@0: }); michael@0: }; michael@0: michael@0: command.setDeviceName = function(deviceName, callback) { michael@0: doBooleanCommand("SET device_name " + deviceName, "OK", callback); michael@0: }; michael@0: michael@0: //------------------------------------------------- michael@0: // P2P commands. michael@0: //------------------------------------------------- michael@0: michael@0: command.p2pProvDiscovery = function(address, wpsMethod, callback) { michael@0: var command = "P2P_PROV_DISC " + address + " " + wpsMethod; michael@0: doBooleanCommand(command, "OK", callback); michael@0: }; michael@0: michael@0: command.p2pConnect = function(config, callback) { michael@0: var command = "P2P_CONNECT " + config.address + " " + config.wpsMethodWithPin + " "; michael@0: if (config.joinExistingGroup) { michael@0: command += "join"; michael@0: } else { michael@0: command += "go_intent=" + config.goIntent; michael@0: } michael@0: michael@0: debug('P2P connect command: ' + command); michael@0: doBooleanCommand(command, "OK", callback); michael@0: }; michael@0: michael@0: command.p2pGroupRemove = function(iface, callback) { michael@0: debug("groupRemove()"); michael@0: doBooleanCommand("P2P_GROUP_REMOVE " + iface, "OK", callback); michael@0: }; michael@0: michael@0: command.p2pEnable = function(detail, callback) { michael@0: var commandChain = ["SET device_name " + detail.deviceName, michael@0: "SET device_type " + detail.deviceType, michael@0: "SET config_methods " + detail.wpsMethods, michael@0: "P2P_SET conc_pref sta", michael@0: "P2P_FLUSH"]; michael@0: michael@0: doBooleanCommandChain(commandChain, callback); michael@0: }; michael@0: michael@0: command.p2pDisable = function(callback) { michael@0: doBooleanCommand("P2P_SET disabled 1", "OK", callback); michael@0: }; michael@0: michael@0: command.p2pEnableScan = function(timeout, callback) { michael@0: doBooleanCommand("P2P_FIND " + timeout, "OK", callback); michael@0: }; michael@0: michael@0: command.p2pDisableScan = function(callback) { michael@0: doBooleanCommand("P2P_STOP_FIND", "OK", callback); michael@0: }; michael@0: michael@0: command.p2pGetGroupCapab = function(address, callback) { michael@0: command.p2pPeer(address, function(reply) { michael@0: debug('p2p_peer reply: ' + reply); michael@0: if (!reply) { michael@0: callback(0); michael@0: return; michael@0: } michael@0: var capab = /group_capab=0x([0-9a-fA-F]+)/.exec(reply)[1]; michael@0: if (!capab) { michael@0: callback(0); michael@0: } else { michael@0: callback(parseInt(capab, 16)); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: command.p2pPeer = function(address, callback) { michael@0: doStringCommand("P2P_PEER " + address, callback); michael@0: }; michael@0: michael@0: command.p2pGroupAdd = function(netId, callback) { michael@0: doBooleanCommand("P2P_GROUP_ADD persistent=" + netId, callback); michael@0: }; michael@0: michael@0: command.p2pReinvoke = function(netId, address, callback) { michael@0: doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + address, "OK", callback); michael@0: }; michael@0: michael@0: //---------------------------------------------------------- michael@0: // Private stuff. michael@0: //---------------------------------------------------------- michael@0: michael@0: function voidControlMessage(cmd, callback) { michael@0: aControlMessage({ cmd: cmd, iface: aInterface }, function (data) { michael@0: callback(data.status); michael@0: }); michael@0: } michael@0: michael@0: function doCommand(request, callback) { michael@0: var msg = { cmd: "command", michael@0: request: request, michael@0: iface: aInterface }; michael@0: michael@0: aControlMessage(msg, callback); michael@0: } michael@0: michael@0: function doIntCommand(request, callback) { michael@0: doCommand(request, function(data) { michael@0: callback(data.status ? -1 : (data.reply|0)); michael@0: }); michael@0: } michael@0: michael@0: function doBooleanCommand(request, expected, callback) { michael@0: doCommand(request, function(data) { michael@0: callback(data.status ? false : (data.reply === expected)); michael@0: }); michael@0: } michael@0: michael@0: function doStringCommand(request, callback) { michael@0: doCommand(request, function(data) { michael@0: callback(data.status ? null : data.reply); michael@0: }); michael@0: } michael@0: michael@0: function doBooleanCommandChain(commandChain, callback, i) { michael@0: if (undefined === i) { michael@0: i = 0; michael@0: } michael@0: michael@0: doBooleanCommand(commandChain[i], "OK", function(ok) { michael@0: if (!ok) { michael@0: return callback(false); michael@0: } michael@0: i++; michael@0: if (i === commandChain.length || !commandChain[i]) { michael@0: // Reach the end or empty command. michael@0: return callback(true); michael@0: } michael@0: doBooleanCommandChain(commandChain, callback, i); michael@0: }); michael@0: } michael@0: michael@0: //-------------------------------------------------- michael@0: // Helper functions. michael@0: //-------------------------------------------------- michael@0: michael@0: function stopProcess(service, process, callback) { michael@0: var count = 0; michael@0: var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: function tick() { michael@0: let result = libcutils.property_get(service); michael@0: if (result === null) { michael@0: callback(); michael@0: return; michael@0: } michael@0: if (result === "stopped" || ++count >= 5) { michael@0: // Either we succeeded or ran out of time. michael@0: timer = null; michael@0: callback(); michael@0: return; michael@0: } michael@0: michael@0: // Else it's still running, continue waiting. michael@0: timer.initWithCallback(tick, 1000, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: setProperty("ctl.stop", process, tick); michael@0: } michael@0: michael@0: // Wrapper around libcutils.property_set that returns true if setting the michael@0: // value was successful. michael@0: // Note that the callback is not called asynchronously. michael@0: function setProperty(key, value, callback) { michael@0: let ok = true; michael@0: try { michael@0: libcutils.property_set(key, value); michael@0: } catch(e) { michael@0: ok = false; michael@0: } michael@0: callback(ok); michael@0: } michael@0: michael@0: return command; michael@0: };