Bug 774582 - Unable to know if there is a user connected to Wifi tethering network. r=mrbkap
authorVincent Chang <vchang@mozilla.com>
Wed, 22 Jan 2014 16:37:40 +0800
changeset 205891 11696ba7ddcb552a247e85bef78f0ee5b81f8ad3
parent 205890 57dda9b3e56fc13ce972e0510605e72e434e3fe0
child 205892 9fc43c5c8ee1717d4da9e766378a882c4c22d39a
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs774582
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 774582 - Unable to know if there is a user connected to Wifi tethering network. r=mrbkap
dom/events/test/test_all_synthetic_events.html
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/MozWifiManager.webidl
dom/webidl/MozWifiStationInfoEvent.webidl
dom/webidl/moz.build
dom/wifi/DOMWifiManager.js
dom/wifi/WifiCommand.jsm
dom/wifi/WifiHotspotUtils.cpp
dom/wifi/WifiHotspotUtils.h
dom/wifi/WifiUtils.cpp
dom/wifi/WifiUtils.h
dom/wifi/WifiWorker.js
dom/wifi/moz.build
dom/wifi/nsIDOMMozWifiStationInfoEvent.idl
js/xpconnect/src/event_impl_gen.conf.in
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -280,16 +280,20 @@ const kEventConstructors = {
   MozWifiConnectionInfoEvent:                { create: function (aName, aProps) {
                                                          return new MozWifiConnectionInfoEvent(aName, aProps);
                                                        },
                                              },
   MozWifiStatusChangeEvent:                  { create: function (aName, aProps) {
                                                           return new MozWifiStatusChangeEvent(aName, aProps);
                                                        },
                                              },
+  MozWifiStationInfoEvent:                   { create: function (aName, aProps) {
+                                                          return new MozWifiStationInfoEvent(aName, aProps);
+                                                       },
+                                             },
   MutationEvent:                             { create: function (aName, aProps) {
                                                          var e = document.createEvent("mutationevent");
                                                          e.initMutationEvent(aName, aProps.bubbles, aProps.cancelable,
                                                                              aProps.relatedNode, aProps.prevValue, aProps.newValue,
                                                                              aProps.attrName, aProps.attrChange);
                                                          return e;
                                                        },
                                              },
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -708,17 +708,19 @@ var interfaceNamesInGlobalScope =
     {name: "MozVoicemailEvent", b2g: true, pref: "dom.voicemail.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWakeLock", b2g: true, pref: "dom.wakelock.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiCapabilities", b2g: true, permission: "wifi-manage"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiConnectionInfoEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "MozWifiManager", b2g: true, permission: "wifi-manage"},
+    {name: "MozWifiStationInfoEvent", b2g: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+   {name: "MozWifiManager", b2g: true, permission: "wifi-manage"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiNetwork", b2g: true, permission: "wifi-manage"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiStatusChangeEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiP2pGroupOwner", b2g: true, permission: "wifi-manage"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiP2pManager", b2g: true, permission: "wifi-manage"},
--- a/dom/webidl/MozWifiManager.webidl
+++ b/dom/webidl/MozWifiManager.webidl
@@ -316,9 +316,15 @@ interface MozWifiManager : EventTarget {
   attribute EventHandler onconnectionInfoUpdate;
 
   /**
    * These two events fire when the wifi system is brought online or taken
    * offline.
    */
   attribute EventHandler onenabled;
   attribute EventHandler ondisabled;
+
+  /**
+   * An event listener that is called with information about the number
+   * of wifi stations connected to wifi hotspot every 5 seconds.
+   */
+  attribute EventHandler onstationInfoUpdate;
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozWifiStationInfoEvent.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+[Constructor(DOMString type, optional MozWifiStationInfoEventInit eventInitDict), HeaderFile="GeneratedEventClasses.h"]
+interface MozWifiStationInfoEvent : Event
+{
+  /**
+   * The number of wifi stations connected to wifi hotspot.
+   */
+  readonly attribute short station;
+};
+
+dictionary MozWifiStationInfoEventInit : EventInit
+{
+  short station = 0;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -590,16 +590,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk
         'MozNetworkStatsData.webidl',
         'MozNetworkStatsInterface.webidl',
         'MozSpeakerManager.webidl',
         'MozWifiCapabilities.webidl',
         'MozWifiConnectionInfoEvent.webidl',
         'MozWifiManager.webidl',
         'MozWifiP2pManager.webidl',
         'MozWifiP2pStatusChangeEvent.webidl',
+        'MozWifiStationInfoEvent.webidl',
         'MozWifiStatusChangeEvent.webidl',
     ]
 else:
     WEBIDL_FILES += [
         'InstallTrigger.webidl',
     ]
 
 if CONFIG['MOZ_WEBSPEECH']:
--- a/dom/wifi/DOMWifiManager.js
+++ b/dom/wifi/DOMWifiManager.js
@@ -75,16 +75,17 @@ MozWifiCapabilities.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
 }
 
 function DOMWifiManager() {
   this.defineEventHandlerGetterSetter("onstatuschange");
   this.defineEventHandlerGetterSetter("onconnectionInfoUpdate");
   this.defineEventHandlerGetterSetter("onenabled");
   this.defineEventHandlerGetterSetter("ondisabled");
+  this.defineEventHandlerGetterSetter("onstationInfoUpdate");
 }
 
 DOMWifiManager.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
   classDescription: "DOMWifiManager",
   classID: DOMWIFIMANAGER_CID,
   contractID: DOMWIFIMANAGER_CONTRACTID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
@@ -94,16 +95,17 @@ DOMWifiManager.prototype = {
   // nsIDOMGlobalPropertyInitializer implementation
   init: function(aWindow) {
     // Maintain this state for synchronous APIs.
     this._currentNetwork = null;
     this._connectionStatus = "disconnected";
     this._enabled = false;
     this._lastConnectionInfo = null;
     this._capabilities = null;
+    this._stationNumber = 0;
 
     const messages = ["WifiManager:getNetworks:Return:OK", "WifiManager:getNetworks:Return:NO",
                       "WifiManager:getKnownNetworks:Return:OK", "WifiManager:getKnownNetworks:Return:NO",
                       "WifiManager:associate:Return:OK", "WifiManager:associate:Return:NO",
                       "WifiManager:forget:Return:OK", "WifiManager:forget:Return:NO",
                       "WifiManager:wps:Return:OK", "WifiManager:wps:Return:NO",
                       "WifiManager:setPowerSavingMode:Return:OK", "WifiManager:setPowerSavingMode:Return:NO",
                       "WifiManager:setHttpProxy:Return:OK", "WifiManager:setHttpProxy:Return:NO",
@@ -111,18 +113,18 @@ DOMWifiManager.prototype = {
                       "WifiManager:importCert:Return:OK", "WifiManager:importCert:Return:NO",
                       "WifiManager:getImportedCerts:Return:OK", "WifiManager:getImportedCerts:Return:NO",
                       "WifiManager:deleteCert:Return:OK", "WifiManager:deleteCert:Return:NO",
                       "WifiManager:wifiDown", "WifiManager:wifiUp",
                       "WifiManager:onconnecting", "WifiManager:onassociate",
                       "WifiManager:onconnect", "WifiManager:ondisconnect",
                       "WifiManager:onwpstimeout", "WifiManager:onwpsfail",
                       "WifiManager:onwpsoverlap", "WifiManager:connectionInfoUpdate",
-                      "WifiManager:onauthenticating",
-                      "WifiManager:onconnectingfailed"];
+                      "WifiManager:onauthenticating", "WifiManager:onconnectingfailed",
+                      "WifiManager:stationInfoUpdate"];
     this.initDOMRequestHelper(aWindow, messages);
     this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
 
     var state = this._mm.sendSyncMessage("WifiManager:getState")[0];
     if (state) {
       this._currentNetwork = this._convertWifiNetwork(state.network);
       this._lastConnectionInfo = this._convertConnectionInfo(state.connectionInfo);
       this._enabled = state.enabled;
@@ -387,16 +389,20 @@ DOMWifiManager.prototype = {
         this._lastConnectionInfo = null;
         this._fireStatusChangeEvent();
         break;
       case "WifiManager:onauthenticating":
         this._currentNetwork = msg.network;
         this._connectionStatus = "authenticating";
         this._fireStatusChangeEvent();
         break;
+      case "WifiManager:stationInfoUpdate":
+        this._stationNumber = msg.station;
+        this._fireStationInfoUpdate(msg);
+        break;
     }
   },
 
   _fireStatusChangeEvent: function StatusChangeEvent() {
     var event = new this._window.MozWifiStatusChangeEvent("statuschange",
                                                           { network: this._currentNetwork,
                                                             status: this._connectionStatus
                                                           });
@@ -414,16 +420,23 @@ DOMWifiManager.prototype = {
     this.__DOM_IMPL__.dispatchEvent(evt);
   },
 
   _fireEnabledOrDisabled: function enabledDisabled(enabled) {
     var evt = new this._window.Event(enabled ? "enabled" : "disabled");
     this.__DOM_IMPL__.dispatchEvent(evt);
   },
 
+  _fireStationInfoUpdate: function onStationInfoUpdate(info) {
+    var evt = new this._window.MozWifiStationInfoEvent("stationInfoUpdate",
+                                                       { station: this._stationNumber}
+                                                      );
+    this.__DOM_IMPL__.dispatchEvent(evt);
+  },
+
   getNetworks: function getNetworks() {
     var request = this.createRequest();
     this._sendMessageForRequest("WifiManager:getNetworks", null, request);
     return request;
   },
 
   getKnownNetworks: function getKnownNetworks() {
     var request = this.createRequest();
--- a/dom/wifi/WifiCommand.jsm
+++ b/dom/wifi/WifiCommand.jsm
@@ -271,16 +271,43 @@ this.WifiCommand = function(aControlMess
     doStringCommand("DRIVER MACADDR", function(reply) {
       if (reply) {
         reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX
       }
       callback(reply);
     });
   };
 
+  command.connectToHostapd = function(callback) {
+    voidControlMessage("connect_to_hostapd", callback);
+  };
+
+  command.closeHostapdConnection = function(callback) {
+    voidControlMessage("close_hostapd_connection", callback);
+  };
+
+  command.hostapdCommand = function (callback, request) {
+    var msg = { cmd:     "hostapd_command",
+                request: request,
+                iface:   aInterface };
+
+    aControlMessage(msg, function(data) {
+      callback(data.status ? null : data.reply);
+    });
+  };
+
+  command.hostapdGetStations = function (callback) {
+    var msg = { cmd:     "hostapd_get_stations",
+                iface:   aInterface };
+
+    aControlMessage(msg, function(data) {
+      callback(data.status);
+    });
+  };
+
   command.setPowerModeICS = function (mode, callback) {
     doBooleanCommand("DRIVER POWERMODE " + (mode === "AUTO" ? 0 : 1), "OK", callback);
   };
 
   command.setPowerModeJB = function (mode, callback) {
     doBooleanCommand("SET ps " + (mode === "AUTO" ? 1 : 0), "OK", callback);
   };
 
new file mode 100644
--- /dev/null
+++ b/dom/wifi/WifiHotspotUtils.cpp
@@ -0,0 +1,176 @@
+/* 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/. */
+
+#include "WifiHotspotUtils.h"
+#include <dlfcn.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <cutils/properties.h>
+
+#include "prinit.h"
+#include "mozilla/Assertions.h"
+#include "nsDebug.h"
+#include "nsPrintfCString.h"
+
+static void* sWifiHotspotUtilsLib;
+static PRCallOnceType sInitWifiHotspotUtilsLib;
+// Socket pair used to exit from a blocking read.
+static struct wpa_ctrl* ctrl_conn;
+static const char *ctrl_iface_dir = "/data/misc/wifi/hostapd";
+static char *ctrl_ifname = nullptr;
+
+DEFINE_DLFUNC(wpa_ctrl_open, struct wpa_ctrl*, const char*)
+DEFINE_DLFUNC(wpa_ctrl_close, void, struct wpa_ctrl*)
+DEFINE_DLFUNC(wpa_ctrl_attach, int32_t, struct wpa_ctrl*)
+DEFINE_DLFUNC(wpa_ctrl_detach, int32_t, struct wpa_ctrl*)
+DEFINE_DLFUNC(wpa_ctrl_request, int32_t, struct wpa_ctrl*,
+              const char*, size_t cmd_len, char *reply,
+              size_t *reply_len, void (*msg_cb)(char *msg, size_t len))
+
+
+static PRStatus
+InitWifiHotspotUtilsLib()
+{
+  sWifiHotspotUtilsLib = dlopen("/system/lib/libwpa_client.so", RTLD_LAZY);
+  // We might fail to open the hardware lib. That's OK.
+  return PR_SUCCESS;
+}
+
+static void*
+GetWifiHotspotLibHandle()
+{
+  PR_CallOnce(&sInitWifiHotspotUtilsLib, InitWifiHotspotUtilsLib);
+  return sWifiHotspotUtilsLib;
+}
+
+struct wpa_ctrl *
+WifiHotspotUtils::openConnection(const char *ifname)
+{
+  if (!ifname) {
+    return nullptr;
+  }
+
+  USE_DLFUNC(wpa_ctrl_open)
+  ctrl_conn = wpa_ctrl_open(nsPrintfCString("%s/%s", ctrl_iface_dir, ifname).get());
+  return ctrl_conn;
+}
+
+int32_t
+WifiHotspotUtils::sendCommand(struct wpa_ctrl *ctrl, const char *cmd,
+                              char *reply, size_t *reply_len)
+{
+  int32_t ret;
+
+  if (!ctrl_conn) {
+    NS_WARNING(nsPrintfCString("Not connected to hostapd - \"%s\" command dropped.\n", cmd).get());
+    return -1;
+  }
+
+  USE_DLFUNC(wpa_ctrl_request)
+  ret = wpa_ctrl_request(ctrl, cmd, strlen(cmd), reply, reply_len, nullptr);
+  if (ret == -2) {
+    NS_WARNING(nsPrintfCString("'%s' command timed out.\n", cmd).get());
+    return -2;
+  } else if (ret < 0 || strncmp(reply, "FAIL", 4) == 0) {
+    return -1;
+  }
+  if (strncmp(cmd, "PING", 4) == 0) {
+    reply[*reply_len] = '\0';
+  }
+  return 0;
+}
+
+
+// static
+void*
+WifiHotspotUtils::GetSharedLibrary()
+{
+  void* wpaClientLib = GetWifiHotspotLibHandle();
+  if (!wpaClientLib) {
+    NS_WARNING("No /system/lib/libwpa_client.so");
+  }
+  return wpaClientLib;
+}
+
+int32_t WifiHotspotUtils::do_wifi_connect_to_hostapd()
+{
+  struct dirent *dent;
+
+  DIR *dir = opendir(ctrl_iface_dir);
+  if (dir) {
+    while ((dent = readdir(dir))) {
+      if (strcmp(dent->d_name, ".") == 0 ||
+          strcmp(dent->d_name, "..") == 0) {
+        continue;
+      }
+      ctrl_ifname = strdup(dent->d_name);
+      break;
+    }
+    closedir(dir);
+  }
+
+  ctrl_conn = openConnection(ctrl_ifname);
+  if (!ctrl_conn) {
+    NS_WARNING(nsPrintfCString("Unable to open connection to hostapd on \"%s\": %s",
+               ctrl_ifname, strerror(errno)).get());
+    return -1;
+  }
+
+  USE_DLFUNC(wpa_ctrl_attach)
+  if (wpa_ctrl_attach(ctrl_conn) != 0) {
+    USE_DLFUNC(wpa_ctrl_close)
+    wpa_ctrl_close(ctrl_conn);
+    ctrl_conn = nullptr;
+    return -1;
+  }
+
+  return 0;
+}
+
+int32_t WifiHotspotUtils::do_wifi_close_hostapd_connection()
+{
+  if (!ctrl_conn) {
+    NS_WARNING("Invalid ctrl_conn.");
+    return -1;
+  }
+
+  USE_DLFUNC(wpa_ctrl_detach)
+  if (wpa_ctrl_detach(ctrl_conn) < 0) {
+    NS_WARNING("Failed to detach wpa_ctrl.");
+  }
+
+  USE_DLFUNC(wpa_ctrl_close)
+  wpa_ctrl_close(ctrl_conn);
+  ctrl_conn = nullptr;
+  return 0;
+}
+
+int32_t WifiHotspotUtils::do_wifi_hostapd_command(const char *command,
+                                                  char *reply,
+                                                  size_t *reply_len)
+{
+  return sendCommand(ctrl_conn, command, reply, reply_len);
+}
+
+int32_t WifiHotspotUtils::do_wifi_hostapd_get_stations()
+{
+  char addr[32], cmd[64];
+  int stations = 0;
+  size_t addrLen = sizeof(addr);
+
+  if (sendCommand(ctrl_conn, "STA-FIRST", addr, &addrLen)) {
+    return 0;
+  }
+  stations++;
+
+  sprintf(cmd, "STA-NEXT %s", addr);
+  while (sendCommand(ctrl_conn, cmd, addr, &addrLen) == 0) {
+    stations++;
+    sprintf(cmd, "STA-NEXT %s", addr);
+  }
+
+  return stations;
+}
new file mode 100644
--- /dev/null
+++ b/dom/wifi/WifiHotspotUtils.h
@@ -0,0 +1,44 @@
+/* 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/. */
+
+/**
+ * Abstraction on top of the network support from libnetutils that we
+ * use to set up network connections.
+ */
+
+#ifndef WifiHotspotUtils_h
+#define WifiHotspotUtils_h
+
+// Forward declaration.
+struct wpa_ctrl;
+
+class WifiHotspotUtils
+{
+public:
+  static void* GetSharedLibrary();
+
+  int32_t do_wifi_connect_to_hostapd();
+  int32_t do_wifi_close_hostapd_connection();
+  int32_t do_wifi_hostapd_command(const char *command,
+                                  char *reply,
+                                  size_t *reply_len);
+  int32_t do_wifi_hostapd_get_stations();
+
+private:
+  struct wpa_ctrl * openConnection(const char *ifname);
+  int32_t sendCommand(struct wpa_ctrl *ctrl, const char *cmd,
+                      char *reply, size_t *reply_len);
+};
+
+// Defines a function type with the right arguments and return type.
+#define DEFINE_DLFUNC(name, ret, args...) typedef ret (*FUNC##name)(args);
+
+// Set up a dlsymed function ready to use.
+#define USE_DLFUNC(name)                                                      \
+  FUNC##name name = (FUNC##name) dlsym(GetSharedLibrary(), #name);            \
+  if (!name) {                                                                \
+    MOZ_ASSUME_UNREACHABLE("Symbol not found in shared library : " #name);    \
+  }
+
+#endif // WifiHotspotUtils_h
--- a/dom/wifi/WifiUtils.cpp
+++ b/dom/wifi/WifiUtils.cpp
@@ -376,16 +376,17 @@ WpaSupplicant::WpaSupplicant()
   if (NetUtils::SdkVersion() < 16) {
     mImpl = new ICSWpaSupplicantImpl();
   } else if (NetUtils::SdkVersion() < 19) {
     mImpl = new JBWpaSupplicantImpl();
   } else {
     mImpl = new KKWpaSupplicantImpl();
   }
   mNetUtils = new NetUtils();
+  mWifiHotspotUtils = new WifiHotspotUtils();
 };
 
 void WpaSupplicant::WaitForEvent(nsAString& aEvent, const nsCString& aInterface)
 {
   CHECK_HWLIB()
 
   char buffer[BUFFER_SIZE];
   int32_t ret = mImpl->do_wifi_wait_for_event(aInterface.get(), buffer, BUFFER_SIZE);
@@ -409,16 +410,20 @@ bool WpaSupplicant::ExecuteCommand(Comma
                                    WifiResultOptions& aResult,
                                    const nsCString& aInterface)
 {
   CHECK_HWLIB(false)
   if (!mNetUtils->GetSharedLibrary()) {
     return false;
   }
 
+  if (!mWifiHotspotUtils->GetSharedLibrary()) {
+    return false;
+  }
+
   // Always correlate the opaque ids.
   aResult.mId = aOptions.mId;
 
   if (aOptions.mCmd.EqualsLiteral("command")) {
     size_t len = BUFFER_SIZE - 1;
     char buffer[BUFFER_SIZE];
     NS_ConvertUTF16toUTF8 request(aOptions.mRequest);
     aResult.mStatus = mImpl->do_wifi_command(aInterface.get(), request.get(), buffer, &len);
@@ -513,16 +518,61 @@ bool WpaSupplicant::ExecuteCommand(Comma
 
     INET_PTON(server, mServer);
 
     //aResult.mask_str = netHelpers.ipToString(obj.mask);
     char inet_str[64];
     if (inet_ntop(AF_INET, &aResult.mMask, inet_str, sizeof(inet_str))) {
       aResult.mMask_str = NS_ConvertUTF8toUTF16(inet_str);
     }
+  } else if (aOptions.mCmd.EqualsLiteral("hostapd_command")) {
+    size_t len = BUFFER_SIZE - 1;
+    char buffer[BUFFER_SIZE];
+    NS_ConvertUTF16toUTF8 request(aOptions.mRequest);
+    aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_command(request.get(),
+                                                                 buffer,
+                                                                 &len);
+    nsString value;
+    if (aResult.mStatus == 0) {
+      if (buffer[len - 1] == '\n') { // remove trailing new lines.
+        len--;
+      }
+      buffer[len] = '\0';
+      CheckBuffer(buffer, len, value);
+    }
+    aResult.mReply = value;
+  } else if (aOptions.mCmd.EqualsLiteral("hostapd_get_stations")) {
+    aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_get_stations();
+  } else if (aOptions.mCmd.EqualsLiteral("connect_to_hostapd")) {
+    aResult.mStatus = mWifiHotspotUtils->do_wifi_connect_to_hostapd();
+  } else if (aOptions.mCmd.EqualsLiteral("close_hostapd_connection")) {
+    aResult.mStatus = mWifiHotspotUtils->do_wifi_close_hostapd_connection();
+  } else if (aOptions.mCmd.EqualsLiteral("hostapd_command")) {
+    size_t len = BUFFER_SIZE - 1;
+    char buffer[BUFFER_SIZE];
+    NS_ConvertUTF16toUTF8 request(aOptions.mRequest);
+    aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_command(request.get(),
+                                                                 buffer,
+                                                                 &len);
+    nsString value;
+    if (aResult.mStatus == 0) {
+      if (buffer[len - 1] == '\n') { // remove trailing new lines.
+        len--;
+      }
+      buffer[len] = '\0';
+      CheckBuffer(buffer, len, value);
+    }
+    aResult.mReply = value;
+  } else if (aOptions.mCmd.EqualsLiteral("hostapd_get_stations")) {
+    aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_get_stations();
+  } else if (aOptions.mCmd.EqualsLiteral("connect_to_hostapd")) {
+    aResult.mStatus = mWifiHotspotUtils->do_wifi_connect_to_hostapd();
+  } else if (aOptions.mCmd.EqualsLiteral("close_hostapd_connection")) {
+    aResult.mStatus = mWifiHotspotUtils->do_wifi_close_hostapd_connection();
+
   } else {
     NS_WARNING("WpaSupplicant::ExecuteCommand : Unknown command");
     printf_stderr("WpaSupplicant::ExecuteCommand : Unknown command: %s",
       NS_ConvertUTF16toUTF8(aOptions.mCmd).get());
     return false;
   }
 
   return true;
--- a/dom/wifi/WifiUtils.h
+++ b/dom/wifi/WifiUtils.h
@@ -9,16 +9,17 @@
 
 #ifndef WifiUtils_h
 #define WifiUtils_h
 
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "mozilla/dom/WifiOptionsBinding.h"
 #include "mozilla/dom/network/NetUtils.h"
+#include "WifiHotspotUtils.h"
 #include "nsCxPusher.h"
 
 // Needed to add a copy constructor to WifiCommandOptions.
 struct CommandOptions
 {
 public:
   CommandOptions(const CommandOptions& aOther) {
     mId = aOther.mId;
@@ -126,15 +127,16 @@ public:
   void WaitForEvent(nsAString& aEvent, const nsCString& aInterface);
   bool ExecuteCommand(CommandOptions aOptions,
                       mozilla::dom::WifiResultOptions& result,
                       const nsCString& aInterface);
 
 private:
   nsAutoPtr<WpaSupplicantImpl> mImpl;
   nsAutoPtr<NetUtils> mNetUtils;
+  nsAutoPtr<WifiHotspotUtils> mWifiHotspotUtils;
 
 protected:
   void CheckBuffer(char* buffer, int32_t length, nsAString& aEvent);
   uint32_t MakeMask(uint32_t len);
 };
 
 #endif // WifiUtils_h
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -1054,41 +1054,72 @@ var WifiManager = (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.name = manager.ifname;
           gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
                                            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"));
           });
         }
 
@@ -2277,16 +2308,20 @@ function WifiWorker() {
         }
       }
 
       self.wantScanResults.forEach(function(callback) { callback(self.networksArray) });
       self.wantScanResults = [];
     });
   };
 
+  WifiManager.onstationInfoUpdate = function() {
+    self._fireEvent("stationInfoUpdate", { station: this.station });
+  };
+
   // 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;
--- a/dom/wifi/moz.build
+++ b/dom/wifi/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIDOMMozWifiConnectionInfoEvent.idl',
     'nsIDOMMozWifiP2pStatusChangeEvent.idl',
+    'nsIDOMMozWifiStationInfoEvent.idl',
     'nsIDOMMozWifiStatusChangeEvent.idl',
     'nsIWifi.idl',
     'nsIWifiCertService.idl',
     'nsIWifiService.idl',
 ]
 
 XPIDL_MODULE = 'dom_wifi'
 
@@ -30,13 +31,14 @@ EXTRA_JS_MODULES += [
     'WifiNetUtil.jsm',
     'WifiP2pManager.jsm',
     'WifiP2pWorkerObserver.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     SOURCES = [
         'WifiCertService.cpp',
+        'WifiHotspotUtils.cpp',
         'WifiProxyService.cpp',
         'WifiUtils.cpp',
     ]
 
 FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/dom/wifi/nsIDOMMozWifiStationInfoEvent.idl
@@ -0,0 +1,19 @@
+/* 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/. */
+
+#include "nsIDOMEvent.idl"
+
+[scriptable, builtinclass, uuid(97dc8040-d5c9-11e3-9c1a-0800200c9a66)]
+interface nsIDOMMozWifiStationInfoEvent : nsIDOMEvent
+{
+    /**
+     * The number of wifi stations connected to wifi hotspot.
+     */
+    readonly attribute short station;
+
+    [noscript] void initMozWifiStationInfoEvent(in DOMString aType,
+                                                in boolean aCanBubble,
+                                                in boolean aCancelable,
+                                                in short station);
+};
--- a/js/xpconnect/src/event_impl_gen.conf.in
+++ b/js/xpconnect/src/event_impl_gen.conf.in
@@ -21,16 +21,17 @@ simple_events = [
     'SmartCardEvent',
     'StyleRuleChangeEvent',
     'StyleSheetChangeEvent',
     'StyleSheetApplicableStateChangeEvent',
 #ifdef MOZ_WIDGET_GONK
     'MozWifiP2pStatusChangeEvent',
     'MozWifiStatusChangeEvent',
     'MozWifiConnectionInfoEvent',
+    'MozWifiStationInfoEvent',
 #endif
 #ifdef MOZ_B2G_RIL
     'MozCellBroadcastEvent',
     'MozVoicemailEvent',
 #endif
     'ElementReplaceEvent',
     'MozSmsEvent',
     'MozMmsEvent',