Bug 811635 - Part 4: Wifi Direct core implementation. r=vchang
☠☠ backed out by 4e8b08358660 ☠ ☠
authorHenry Chang <hchang@mozilla.com>
Thu, 28 Nov 2013 16:02:44 +0800
changeset 173267 4acc0f02807674137abf5f59442c5a75da47ebd8
parent 173266 b695cdc91edc57a464505c1810d6cd1355c3b16e
child 173268 ad72f0b9e16af506926f5bb66b4833f4becef640
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvchang
bugs811635
milestone28.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 811635 - Part 4: Wifi Direct core implementation. r=vchang
b2g/installer/package-manifest.in
dom/system/gonk/nsINetworkManager.idl
dom/wifi/StateMachine.jsm
dom/wifi/WifiCommand.jsm
dom/wifi/WifiNetUtil.jsm
dom/wifi/WifiP2pManager.jsm
dom/wifi/WifiP2pWorkerObserver.jsm
dom/wifi/WifiWorker.js
dom/wifi/moz.build
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -397,16 +397,18 @@
 @BINPATH@/components/DownloadLegacy.js
 @BINPATH@/components/nsSidebar.manifest
 @BINPATH@/components/nsSidebar.js
 
 ; WiFi, NetworkManager, NetworkStats
 #ifdef MOZ_WIDGET_GONK
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
+@BINPATH@/components/DOMWifiP2pManager.js
+@BINPATH@/components/DOMWifiP2pManager.manifest
 @BINPATH@/components/NetworkInterfaceListService.js
 @BINPATH@/components/NetworkInterfaceListService.manifest
 @BINPATH@/components/NetworkManager.js
 @BINPATH@/components/NetworkManager.manifest
 @BINPATH@/components/NetworkService.js
 @BINPATH@/components/NetworkService.manifest
 @BINPATH@/components/NetworkStatsManager.js
 @BINPATH@/components/NetworkStatsManager.manifest
--- a/dom/system/gonk/nsINetworkManager.idl
+++ b/dom/system/gonk/nsINetworkManager.idl
@@ -4,17 +4,17 @@
 
 #include "nsISupports.idl"
 
 interface nsIWifiTetheringCallback;
 
 /**
  * Information about networks that is exposed to network manager API consumers.
  */
-[scriptable, uuid(f4cf9d88-f962-4d29-9baa-fb295dad387b)]
+[scriptable, uuid(e2f5c6e0-4203-11e3-aa6e-0800200c9a66)]
 interface nsINetworkInterface : nsISupports
 {
   const long NETWORK_STATE_UNKNOWN = -1;
   const long NETWORK_STATE_CONNECTING = 0;
   const long NETWORK_STATE_CONNECTED = 1;
   const long NETWORK_STATE_DISCONNECTING = 2;
   const long NETWORK_STATE_DISCONNECTED = 3;
 
@@ -26,16 +26,17 @@ interface nsINetworkInterface : nsISuppo
    */
   readonly attribute long state;
 
   const long NETWORK_TYPE_UNKNOWN     = -1;
   const long NETWORK_TYPE_WIFI        = 0;
   const long NETWORK_TYPE_MOBILE      = 1;
   const long NETWORK_TYPE_MOBILE_MMS  = 2;
   const long NETWORK_TYPE_MOBILE_SUPL = 3;
+  const long NETWORK_TYPE_WIFI_P2P    = 4;
 
   /**
    * Network type. One of the NETWORK_TYPE_* constants.
    */
   readonly attribute long type;
 
   /**
    * Name of the network interface. This identifier is unique.
new file mode 100644
--- /dev/null
+++ b/dom/wifi/StateMachine.jsm
@@ -0,0 +1,205 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/Services.jsm");
+
+this.EXPORTED_SYMBOLS = ["StateMachine"];
+
+const DEBUG = true;
+
+this.StateMachine = function(aDebugTag) {
+  function debug(aMsg) {
+    dump('-------------- StateMachine:' + aDebugTag + ': ' + aMsg);
+  }
+
+  var sm = {};
+
+  var _initialState;
+  var _curState;
+  var _prevState;
+  var _paused;
+  var _eventQueue = [];
+  var _deferredEventQueue = [];
+  var _defaultEventHandler;
+
+  // Public interfaces.
+
+  sm.setDefaultEventHandler = function(aDefaultEventHandler) {
+    _defaultEventHandler = aDefaultEventHandler;
+  };
+
+  sm.start = function(aInitialState) {
+    _initialState = aInitialState;
+    sm.gotoState(_initialState);
+  };
+
+  sm.sendEvent = function (aEvent) {
+    if (!_initialState) {
+      if (DEBUG) {
+        debug('StateMachine is not running. Call StateMachine.start() first.');
+      }
+      return;
+    }
+    _eventQueue.push(aEvent);
+    asyncCall(handleFirstEvent);
+  };
+
+  sm.getPreviousState = function() {
+    return _prevState;
+  };
+
+  sm.getCurrentState = function() {
+    return _curState;
+  };
+
+  // State object maker.
+  // @param aName string for this state's name.
+  // @param aDelegate object:
+  //    .handleEvent: required.
+  //    .enter: called before entering this state (optional).
+  //    .exit: called before exiting this state (optional).
+  sm.makeState = function (aName, aDelegate) {
+    if (!aDelegate.handleEvent) {
+      throw "handleEvent is a required delegate function.";
+    }
+    var nop = function() {};
+    return {
+      name: aName,
+      enter: (aDelegate.enter || nop),
+      exit: (aDelegate.exit || nop),
+      handleEvent: aDelegate.handleEvent
+    };
+  };
+
+  sm.deferEvent = function (aEvent) {
+    // The definition of a 'deferred event' is:
+    //     We are not able to handle this event now but after receiving
+    //     certain event or entering a new state, we might be able to handle
+    //     it. For example, we couldn't handle CONNECT_EVENT in the
+    //     diconnecting state. But once we finish doing "disconnecting", we
+    //     could then handle CONNECT_EVENT!
+    //
+    // So, the deferred event may be handled in the following cases:
+    //     1. Once we entered a new state.
+    //     2. Once we handled a regular event.
+    if (DEBUG) {
+      debug('Deferring event: ' + JSON.stringify(aEvent));
+    }
+    _deferredEventQueue.push(aEvent);
+  };
+
+  // Goto the new state. If the current state is null, the exit
+  // function won't be called.
+  sm.gotoState = function (aNewState) {
+    if (_curState) {
+      if (DEBUG) {
+        debug("exiting state: " + _curState.name);
+      }
+      _curState.exit();
+    }
+
+    _prevState = _curState;
+    _curState = aNewState;
+
+    if (DEBUG) {
+      debug("entering state: " + _curState.name);
+    }
+    _curState.enter();
+
+    // We are in the new state now. We got a chance to handle the
+    // deferred events.
+    handleDeferredEvents();
+
+    sm.resume();
+  };
+
+  // No incoming event will be handled after you call pause().
+  // (But they will be queued.)
+  sm.pause = function() {
+    _paused = true;
+  };
+
+  // Continue to handle incoming events.
+  sm.resume = function() {
+    _paused = false;
+    asyncCall(handleFirstEvent);
+  };
+
+  //----------------------------------------------------------
+  // Private stuff
+  //----------------------------------------------------------
+
+  function asyncCall(f) {
+    Services.tm.currentThread.dispatch(f, Ci.nsIThread.DISPATCH_NORMAL);
+  }
+
+  function handleFirstEvent() {
+    var hadDeferredEvents;
+
+    if (0 === _eventQueue.length) {
+      return;
+    }
+
+    if (_paused) {
+      return; // The state machine is paused now.
+    }
+
+    hadDeferredEvents = _deferredEventQueue.length > 0;
+
+    handleOneEvent(_eventQueue.shift()); // The handler may defer this event.
+
+    // We've handled one event. If we had deferred events before, now is
+    // a good chance to handle them.
+    if (hadDeferredEvents) {
+      handleDeferredEvents();
+    }
+
+    // Continue to handle the next regular event.
+    handleFirstEvent();
+  }
+
+  function handleDeferredEvents() {
+    if (_deferredEventQueue.length && DEBUG) {
+      debug('Handle deferred events: ' + _deferredEventQueue.length);
+    }
+    for (let i = 0; i < _deferredEventQueue.length; i++) {
+      handleOneEvent(_deferredEventQueue.shift());
+    }
+  }
+
+  function handleOneEvent(aEvent)
+  {
+    if (DEBUG) {
+      debug('Handling event: ' + JSON.stringify(aEvent));
+    }
+
+    var handled = _curState.handleEvent(aEvent);
+
+    if (undefined === handled) {
+      throw "handleEvent returns undefined: " + _curState.name;
+    }
+    if (!handled) {
+      // Event is not handled in the current state. Try handleEventCommon().
+      handled = (_defaultEventHandler ? _defaultEventHandler(aEvent) : handled);
+    }
+    if (undefined === handled) {
+      throw "handleEventCommon returns undefined: " + _curState.name;
+    }
+    if (!handled) {
+      if (DEBUG) {
+        debug('!!!!!!!!! FIXME !!!!!!!!! Event not handled: ' + JSON.stringify(aEvent));
+      }
+    }
+
+    return handled;
+  }
+
+  return sm;
+};
--- a/dom/wifi/WifiCommand.jsm
+++ b/dom/wifi/WifiCommand.jsm
@@ -9,18 +9,25 @@
 this.EXPORTED_SYMBOLS = ["WifiCommand"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/systemlibs.js");
 
 const SUPP_PROP = "init.svc.wpa_supplicant";
 const WPA_SUPPLICANT = "wpa_supplicant";
+const DEBUG = false;
 
 this.WifiCommand = function(aControlMessage, aInterface) {
+  function debug(msg) {
+    if (DEBUG) {
+      dump('-------------- WifiCommand: ' + msg);
+    }
+  }
+
   var command = {};
 
   //-------------------------------------------------
   // General commands.
   //-------------------------------------------------
 
   command.loadDriver = function (callback) {
     voidControlMessage("load_driver", function(status) {
@@ -130,24 +137,26 @@ this.WifiCommand = function(aControlMess
   command.setLogLevel = function (level, callback) {
     doBooleanCommand("LOG_LEVEL " + level, "OK", callback);
   };
 
   command.getLogLevel = function (callback) {
     doStringCommand("LOG_LEVEL", callback);
   };
 
-  command.wpsPbc = function (callback) {
-    doBooleanCommand("WPS_PBC", "OK", callback);
+  command.wpsPbc = function (iface, callback) {
+    doBooleanCommand("WPS_PBC" + (iface ? (" interface=" + iface) : ""),
+                     "OK", callback);
   };
 
   command.wpsPin = function (detail, callback) {
     doStringCommand("WPS_PIN " +
                     (detail.bssid === undefined ? "any" : detail.bssid) +
-                    (detail.pin === undefined ? "" : (" " + detail.pin)),
+                    (detail.pin === undefined ? "" : (" " + detail.pin)) +
+                    (detail.iface ? (" interface=" + detail.iface) : ""),
                     callback);
   };
 
   command.wpsCancel = function (callback) {
     doBooleanCommand("WPS_CANCEL", "OK", callback);
   };
 
   command.startDriver = function (callback) {
@@ -327,19 +336,99 @@ 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);
     });
   };
 
-  //--------------------------------------------------
-  // Helper functions.
-  //--------------------------------------------------
+  command.setDeviceName = function(deviceName, callback) {
+    doBooleanCommand("SET device_name " + deviceName, "OK", callback);
+  };
+
+  //-------------------------------------------------
+  // P2P commands.
+  //-------------------------------------------------
+
+  command.p2pProvDiscovery = function(address, wpsMethod, callback) {
+    var command = "P2P_PROV_DISC " + address + " " + wpsMethod;
+    doBooleanCommand(command, "OK", callback);
+  };
+
+  command.p2pConnect = function(config, callback) {
+    var command = "P2P_CONNECT " + config.address + " " + config.wpsMethodWithPin + " ";
+    if (config.joinExistingGroup) {
+      command += "join";
+    } else {
+      command += "go_intent=" + config.goIntent;
+    }
+
+    debug('P2P connect command: ' + command);
+    doBooleanCommand(command, "OK", callback);
+  };
+
+  command.p2pGroupRemove = function(iface, callback) {
+    debug("groupRemove()");
+    doBooleanCommand("P2P_GROUP_REMOVE " + iface, "OK", callback);
+  };
+
+  command.p2pEnable = function(detail, callback) {
+    var commandChain = ["SET device_name "    + detail.deviceName,
+                        "SET device_type "    + detail.deviceType,
+                        "SET config_methods " + detail.wpsMethods,
+                        "P2P_SET conc_pref sta",
+                        "P2P_FLUSH"];
+
+    doBooleanCommandChain(commandChain, callback);
+  };
+
+  command.p2pDisable = function(callback) {
+    doBooleanCommand("P2P_SET disabled 1", "OK", callback);
+  };
+
+  command.p2pEnableScan = function(timeout, callback) {
+    doBooleanCommand("P2P_FIND " + timeout, "OK", callback);
+  };
+
+  command.p2pDisableScan = function(callback) {
+    doBooleanCommand("P2P_STOP_FIND", "OK", callback);
+  };
+
+  command.p2pGetGroupCapab = function(address, callback) {
+    command.p2pPeer(address, function(reply) {
+      debug('p2p_peer reply: ' + reply);
+      if (!reply) {
+        callback(0);
+        return;
+      }
+      var capab = /group_capab=0x([0-9a-fA-F]+)/.exec(reply)[1];
+      if (!capab) {
+        callback(0);
+      } else {
+        callback(parseInt(capab, 16));
+      }
+    });
+  };
+
+  command.p2pPeer = function(address, callback) {
+    doStringCommand("P2P_PEER " + address, callback);
+  };
+
+  command.p2pGroupAdd = function(netId, callback) {
+    doBooleanCommand("P2P_GROUP_ADD persistent=" + netId, callback);
+  };
+
+  command.p2pReinvoke = function(netId, address, callback) {
+    doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + address, "OK", callback);
+  };
+
+  //----------------------------------------------------------
+  // Private stuff.
+  //----------------------------------------------------------
 
   function voidControlMessage(cmd, callback) {
     aControlMessage({ cmd: cmd, iface: aInterface }, function (data) {
       callback(data.status);
     });
   }
 
   function doCommand(request, callback) {
@@ -381,16 +470,20 @@ this.WifiCommand = function(aControlMess
       if (i === commandChain.length || !commandChain[i]) {
         // Reach the end or empty command.
         return callback(true);
       }
       doBooleanCommandChain(commandChain, callback, i);
     });
   }
 
+  //--------------------------------------------------
+  // Helper functions.
+  //--------------------------------------------------
+
   function stopProcess(service, process, callback) {
     var count = 0;
     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     function tick() {
       let result = libcutils.property_get(service);
       if (result === null) {
         callback();
         return;
--- a/dom/wifi/WifiNetUtil.jsm
+++ b/dom/wifi/WifiNetUtil.jsm
@@ -6,26 +6,33 @@
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/systemlibs.js");
 
-XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
-                                   "@mozilla.org/network/manager;1",
-                                   "nsINetworkManager");
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+                                   "@mozilla.org/network/service;1",
+                                   "nsINetworkService");
 
 this.EXPORTED_SYMBOLS = ["WifiNetUtil"];
 
 const DHCP_PROP = "init.svc.dhcpcd";
 const DHCP      = "dhcpcd";
+const DEBUG     = false;
 
 this.WifiNetUtil = function(controlMessage) {
+  function debug(msg) {
+    if (DEBUG) {
+      dump('-------------- NetUtil: ' + msg);
+    }
+  }
+
   var util = {};
 
   util.configureInterface = function(cfg, callback) {
     let message = { cmd:     "ifc_configure",
                     ifname:  cfg.ifname,
                     ipaddr:  cfg.ipaddr,
                     mask:    cfg.mask,
                     gateway: cfg.gateway,
@@ -62,24 +69,24 @@ this.WifiNetUtil = function(controlMessa
   };
 
   util.disableInterface = function (ifname, callback) {
     controlMessage({ cmd: "ifc_disable", ifname: ifname }, function (data) {
       callback(!data.status);
     });
   };
 
-  util.startDhcpServer = function (range, callback) {
-    gNetworkManager.setDhcpServer(true, range, function (error) {
+  util.startDhcpServer = function (config, callback) {
+    gNetworkService.setDhcpServer(true, config, function (error) {
       callback(!error);
     });
   };
 
   util.stopDhcpServer = function (callback) {
-    gNetworkManager.setDhcpServer(false, null, function (error) {
+    gNetworkService.setDhcpServer(false, null, function (error) {
       callback(!error);
     });
   };
 
   util.addHostRoute = function (ifname, route, callback) {
     controlMessage({ cmd: "ifc_add_host_route", ifname: ifname, route: route }, function(data) {
       callback(!data.status);
     });
@@ -130,33 +137,37 @@ this.WifiNetUtil = function(controlMessa
   util.runDhcpRenew = function (ifname, callback) {
     controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) {
       callback(data.status ? null : data);
     });
   };
 
   util.runIpConfig = function (name, data, callback) {
     if (!data) {
+      debug("IP config failed to run");
       callback({ info: data });
       return;
     }
 
     setProperty("net." + name + ".dns1", ipToString(data.dns1),
                 function(ok) {
       if (!ok) {
+        debug("Unable to set net.<ifname>.dns1");
         return;
       }
       setProperty("net." + name + ".dns2", ipToString(data.dns2),
                   function(ok) {
         if (!ok) {
+          debug("Unable to set net.<ifname>.dns2");
           return;
         }
         setProperty("net." + name + ".gw", ipToString(data.gateway),
                     function(ok) {
           if (!ok) {
+            debug("Unable to set net.<ifname>.gw");
             return;
           }
           callback({ info: data });
         });
       });
     });
   };
 
new file mode 100644
--- /dev/null
+++ b/dom/wifi/WifiP2pManager.jsm
@@ -0,0 +1,1597 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/StateMachine.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSysMsgr",
+                                   "@mozilla.org/system-message-internal;1",
+                                   "nsISystemMessagesInternal");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+                                   "@mozilla.org/network/manager;1",
+                                   "nsINetworkManager");
+
+const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
+
+this.EXPORTED_SYMBOLS = ["WifiP2pManager"];
+
+const EVENT_IGNORED                      = -1;
+const EVENT_UNKNOWN                      = -2;
+
+// Events from supplicant for p2p.
+const EVENT_P2P_DEVICE_FOUND             = 0;
+const EVENT_P2P_DEVICE_LOST              = 1;
+const EVENT_P2P_GROUP_STARTED            = 2;
+const EVENT_P2P_GROUP_REMOVED            = 3;
+const EVENT_P2P_PROV_DISC_PBC_REQ        = 4;
+const EVENT_P2P_PROV_DISC_PBC_RESP       = 5;
+const EVENT_P2P_PROV_DISC_SHOW_PIN       = 6;
+const EVENT_P2P_PROV_DISC_ENTER_PIN      = 7;
+const EVENT_P2P_GO_NEG_REQUEST           = 8;
+const EVENT_P2P_GO_NEG_SUCCESS           = 9;
+const EVENT_P2P_GO_NEG_FAILURE           = 10;
+const EVENT_P2P_GROUP_FORMATION_SUCCESS  = 11;
+const EVENT_P2P_GROUP_FORMATION_FAILURE  = 12;
+const EVENT_P2P_FIND_STOPPED             = 13;
+const EVENT_P2P_INVITATION_RESULT        = 14;
+const EVENT_P2P_INVITATION_RECEIVED      = 15;
+const EVENT_P2P_PROV_DISC_FAILURE        = 16;
+
+// Events from supplicant but not p2p specific.
+const EVENT_AP_STA_DISCONNECTED          = 100;
+const EVENT_AP_STA_CONNECTED             = 101;
+
+// Events from DOM.
+const EVENT_P2P_SET_PAIRING_CONFIRMATION = 1000;
+const EVENT_P2P_CMD_CONNECT              = 1001;
+const EVENT_P2P_CMD_DISCONNECT           = 1002;
+const EVENT_P2P_CMD_ENABLE               = 1003;
+const EVENT_P2P_CMD_DISABLE              = 1004;
+const EVENT_P2P_CMD_ENABLE_SCAN          = 1005;
+const EVENT_P2P_CMD_DISABLE_SCAN         = 1006;
+const EVENT_P2P_CMD_BLOCK_SCAN           = 1007;
+const EVENT_P2P_CMD_UNBLOCK_SCAN         = 1008;
+
+// Internal events.
+const EVENT_TIMEOUT_PAIRING_CONFIRMATION = 10000;
+const EVENT_TIMEOUT_NEG_REQ              = 10001;
+const EVENT_TIMEOUT_CONNECTING           = 10002;
+const EVENT_P2P_ENABLE_SUCCESS           = 10003;
+const EVENT_P2P_ENABLE_FAILED            = 10004;
+const EVENT_P2P_DISABLE_SUCCESS          = 10005;
+
+// WPS method string.
+const WPS_METHOD_PBC     = "pbc";
+const WPS_METHOD_DISPLAY = "display";
+const WPS_METHOD_KEYPAD  = "keypad";
+
+// Role string.
+const P2P_ROLE_GO     = "GO";
+const P2P_ROLE_CLIENT = "client";
+
+// System message for pairing request.
+const PAIRING_REQUEST_SYS_MSG = "wifip2p-pairing-request";
+
+// Configuration.
+const P2P_INTERFACE_NAME = "p2p0";
+const DEFAULT_GO_INTENT = 15;
+const DEFAULT_P2P_DEVICE_NAME = "FirefoxPhone";
+const P2P_SCAN_TIMEOUT_SEC = 120;
+const DEFAULT_P2P_WPS_METHODS = "virtual_push_button physical_display keypad"; // For wpa_supplicant.
+const DEFAULT_P2P_DEVICE_TYPE = "10-0050F204-5"; // For wpa_supplicant.
+
+const GO_NETWORK_INTERFACE = {
+  ip:         "192.168.2.1",
+  netmask:    "255.255.255.0", // Should be consistent with |maskLenth|.
+  maskLength: 24,              // Should be consistent with |netmask|.
+  broadcast:  "192.168.2.255",
+  gateway:    "192.168.2.1",
+  dns1:       "0.0.0.0",
+  dns2:       "0.0.0.0",
+  dhcpServer: "192.168.2.1"
+};
+
+const GO_DHCP_SERVER_IP_RANGE = {
+  startIp: "192.168.2.10",
+  endIp:   "192.168.2.30"
+};
+
+let gDebug = false;
+
+// Device Capability bitmap
+const DEVICE_CAPAB_SERVICE_DISCOVERY         = 1;
+const DEVICE_CAPAB_CLIENT_DISCOVERABILITY    = 1<<1;
+const DEVICE_CAPAB_CONCURRENT_OPER           = 1<<2;
+const DEVICE_CAPAB_INFRA_MANAGED             = 1<<3;
+const DEVICE_CAPAB_DEVICE_LIMIT              = 1<<4;
+const DEVICE_CAPAB_INVITATION_PROCEDURE      = 1<<5;
+
+// Group Capability bitmap
+const GROUP_CAPAB_GROUP_OWNER                = 1;
+const GROUP_CAPAB_PERSISTENT_GROUP           = 1<<1;
+const GROUP_CAPAB_GROUP_LIMIT                = 1<<2;
+const GROUP_CAPAB_INTRA_BSS_DIST             = 1<<3;
+const GROUP_CAPAB_CROSS_CONN                 = 1<<4;
+const GROUP_CAPAB_PERSISTENT_RECONN          = 1<<5;
+const GROUP_CAPAB_GROUP_FORMATION            = 1<<6;
+
+// Constants defined in wpa_supplicants.
+const DEV_PW_REGISTRAR_SPECIFIED = 5;
+const DEV_PW_USER_SPECIFIED      = 1;
+const DEV_PW_PUSHBUTTON          = 4;
+
+this.WifiP2pManager = function (aP2pCommand, aNetUtil) {
+  function debug(aMsg) {
+    if (gDebug) {
+      dump('-------------- WifiP2pManager: ' + aMsg);
+    }
+  }
+
+  let manager = {};
+
+  let _stateMachine = P2pStateMachine(aP2pCommand, aNetUtil);
+
+  // Set debug flag to true or false.
+  //
+  // @param aDebug Boolean to indicate enabling or disabling the debug flag.
+  manager.setDebug = function(aDebug) {
+    gDebug = aDebug;
+  };
+
+  // Set observer of observing internal state machine events.
+  //
+  // @param aObserver Used to notify WifiWorker what's happening
+  //        in the internal p2p state machine.
+  manager.setObserver = function(aObserver) {
+    _stateMachine.setObserver(aObserver);
+  };
+
+  // Handle wpa_supplicant events.
+  //
+  // @param aEventString string from wpa_supplicant.
+  manager.handleEvent = function(aEventString) {
+    let event = parseEventString(aEventString);
+    if (EVENT_UNKNOWN === event.id || EVENT_IGNORED === event.id) {
+      debug('Unknow or ignored event: ' + aEventString);
+      return false;
+    }
+    return _stateMachine.sendEvent(event);
+  };
+
+  // Set the confirmation of pairing request.
+  //
+  // @param aResult Object of confirmation result which contains:
+  //    .accepted: user granted.
+  //    .pin: pin code which is displaying or input by user.
+  //    .wpsMethod: string of "pbc" or "display" or "keypad".
+  manager.setPairingConfirmation = function(aResult) {
+    let event = {
+      id: EVENT_P2P_SET_PAIRING_CONFIRMATION,
+      info: {
+        accepted: aResult.accepted,
+        pin: aResult.pin
+      }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Connect to a known peer.
+  //
+  // @param aAddress MAC address of the peer to connect.
+  // @param aWpsMethod String of "pbc" or "display" or "keypad".
+  // @param aGoIntent Number from 0 to 15.
+  // @param aCallback Callback |true| on attempting to connect.
+  //                          |false| on failed to connect.
+  manager.connect = function(aAddress, aWpsMethod, aGoIntent, aCallback) {
+    let event = {
+      id: EVENT_P2P_CMD_CONNECT,
+      info: {
+        wpsMethod: aWpsMethod,
+        address: aAddress,
+        goIntent: aGoIntent,
+        onDoConnect: aCallback
+      }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Disconnect with a known peer.
+  //
+  // @param aAddress The address the user desires to disconect.
+  // @param aCallback Callback |true| on "attempting" to disconnect.
+  //                           |false| on failed to disconnect.
+  manager.disconnect = function(aAddress, aCallback) {
+    let event = {
+      id: EVENT_P2P_CMD_DISCONNECT,
+      info: {
+        address: aAddress,
+        onDoDisconnect: aCallback
+      }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Enable/disable wifi p2p.
+  //
+  // @param aEnabled |true| to enable, |false| to disable.
+  // @param aCallbacks object for callbacks:
+  //   .onEnabled
+  //   .onDisabled
+  //   .onSupplicantConnected
+  manager.setEnabled = function(aEnabled, aCallbacks) {
+    let event = {
+      id: (aEnabled ? EVENT_P2P_CMD_ENABLE : EVENT_P2P_CMD_DISABLE),
+      info: {
+        onEnabled: aCallbacks.onEnabled,
+        onDisabled: aCallbacks.onDisabled,
+        onSupplicantConnected: aCallbacks.onSupplicantConnected
+      }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Enable/disable the wifi p2p scan.
+  //
+  // @param aEnabled |true| to enable scan, |false| to disable scan.
+  // @param aCallback Callback |true| on success to enable/disable scan.
+  //                           |false| on failed to enable/disable scan.
+  manager.setScanEnabled = function(aEnabled, aCallback) {
+    let event = {
+      id: (aEnabled ? EVENT_P2P_CMD_ENABLE_SCAN : EVENT_P2P_CMD_DISABLE_SCAN),
+      info: { callback: aCallback }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Block wifi p2p scan.
+  manager.blockScan = function() {
+    _stateMachine.sendEvent({ id: EVENT_P2P_CMD_BLOCK_SCAN });
+  };
+
+  // Un-block and do the pending scan if any.
+  manager.unblockScan = function() {
+    _stateMachine.sendEvent({ id: EVENT_P2P_CMD_UNBLOCK_SCAN });
+  };
+
+  // Set the p2p device name.
+  manager.setDeviceName = function(newDeivceName, callback) {
+    aP2pCommand.setDeviceName(newDeivceName, callback);
+  };
+
+  // Parse wps_supplicant event string.
+  //
+  // @param aEventString The raw event string from wpa_supplicant.
+  //
+  // @return Object:
+  //   .id: a number to represent an event.
+  //   .info: the additional information carried by this event string.
+  function parseEventString(aEventString) {
+    if (isIgnoredEvent(aEventString)) {
+      return { id: EVENT_IGNORED };
+    }
+
+    let match = RegExp("p2p_dev_addr=([0-9a-fA-F:]+) " +
+                       "pri_dev_type=([0-9a-zA-Z-]+) " +
+                       "name='(.*)' " +
+                       "config_methods=0x([0-9a-fA-F]+) " +
+                       "dev_capab=0x([0-9a-fA-F]+) " +
+                       "group_capab=0x([0-9a-fA-F]+) ").exec(aEventString + ' ');
+
+    let tokens = aEventString.split(" ");
+
+    let id = EVENT_UNKNOWN;
+
+    // general info.
+    let info = {};
+
+    if (match) {
+      info = {
+        address:   match[1] ? match[1] : null,
+        type:      match[2] ? match[2] : null,
+        name:      match[3] ? match[3] : null,
+        wpsFlag:   match[4] ? parseInt(match[4], 16) : null,
+        devFlag:   match[5] ? parseInt(match[5], 16) : null,
+        groupFlag: match[6] ? parseInt(match[6], 16) : null
+      };
+    }
+
+    if (0 === aEventString.indexOf("P2P-DEVICE-FOUND")) {
+      id = EVENT_P2P_DEVICE_FOUND;
+      info.wpsCapabilities = wpsFlagToCapabilities(info.wpsFlag);
+      info.isGroupOwner = isPeerGroupOwner(info.groupFlag);
+    } else if (0 === aEventString.indexOf("P2P-DEVICE-LOST")) {
+      // e.g. "P2P-DEVICE-LOST p2p_dev_addr=5e:0a:5b:15:1f:80".
+      id = EVENT_P2P_DEVICE_LOST;
+      info.address = /p2p_dev_addr=([0-9a-f:]+)/.exec(aEventString)[1];
+    } else if (0 === aEventString.indexOf("P2P-GROUP-STARTED")) {
+      // e.g. "P2P-GROUP-STARTED wlan0-p2p-0 GO ssid="DIRECT-3F Testing
+      //       passphrase="12345678" go_dev_addr=02:40:61:c2:f3:b7 [PERSISTENT]".
+
+      id = EVENT_P2P_GROUP_STARTED;
+      let groupMatch = RegExp('ssid="(.*)" ' +
+                              'freq=([0-9]*) ' +
+                              '(passphrase|psk)=([^ ]+) ' +
+                              'go_dev_addr=([0-9a-f:]+)').exec(aEventString);
+      info.ssid = groupMatch[1];
+      info.freq = groupMatch[2];
+      if ('passphrase' === groupMatch[3]) {
+        let s = groupMatch[4]; // e.g. "G7jHkkz9".
+        info.passphrase = s.substring(1, s.length-1); // Trim the double quote.
+      } else { // psk
+        info.psk = groupMatch[4];
+      }
+      info.goAddress = groupMatch[5];
+      info.ifname = tokens[1];
+      info.role = tokens[2];
+    } else if (0 === aEventString.indexOf("P2P-GROUP-REMOVED")) {
+      id = EVENT_P2P_GROUP_REMOVED;
+      // e.g. "P2P-GROUP-REMOVED wlan0-p2p-0 GO".
+      info.ifname = tokens[1];
+      info.role = tokens[2];
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-REQ")) {
+      id = EVENT_P2P_PROV_DISC_PBC_REQ;
+      info.wpsMethod = WPS_METHOD_PBC;
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-RESP")) {
+      id = EVENT_P2P_PROV_DISC_PBC_RESP;
+      // The address is different from the general pattern.
+      info.address = aEventString.split(" ")[1];
+      info.wpsMethod = WPS_METHOD_PBC;
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-SHOW-PIN")) {
+      id = EVENT_P2P_PROV_DISC_SHOW_PIN;
+      // Obtain peer address and pin from tokens.
+      info.address = tokens[1];
+      info.pin     = tokens[2];
+      info.wpsMethod = WPS_METHOD_DISPLAY;
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-ENTER-PIN")) {
+      id = EVENT_P2P_PROV_DISC_ENTER_PIN;
+      // Obtain peer address from tokens.
+      info.address = tokens[1];
+      info.wpsMethod = WPS_METHOD_KEYPAD;
+    } else if (0 === aEventString.indexOf("P2P-GO-NEG-REQUEST")) {
+      id = EVENT_P2P_GO_NEG_REQUEST;
+      info.address = tokens[1];
+      switch (parseInt(tokens[2].split("=")[1], 10)) {
+        case DEV_PW_REGISTRAR_SPECIFIED: // (5) Peer is display.
+          info.wpsMethod = WPS_METHOD_KEYPAD;
+          break;
+        case DEV_PW_USER_SPECIFIED: // (1) Peer is keypad.
+          info.wpsMethod = WPS_METHOD_DISPLAY;
+          break;
+        case DEV_PW_PUSHBUTTON: // (4) Peer is pbc.
+          info.wpsMethod = WPS_METHOD_PBC;
+          break;
+        default:
+          debug('Unknown wps method from event P2P-GO-NEG-REQUEST');
+          break;
+      }
+    } else if (0 === aEventString.indexOf("P2P-GO-NEG-SUCCESS")) {
+      id = EVENT_P2P_GO_NEG_SUCCESS;
+    } else if (0 === aEventString.indexOf("P2P-GO-NEG-FAILURE")) {
+      id = EVENT_P2P_GO_NEG_FAILURE;
+    } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-FAILURE")) {
+      id = EVENT_P2P_GROUP_FORMATION_FAILURE;
+    } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-SUCCESS")) {
+      id = EVENT_P2P_GROUP_FORMATION_SUCCESS;
+    } else if (0 === aEventString.indexOf("P2P-FIND-STOPPED")) {
+      id = EVENT_P2P_FIND_STOPPED;
+    } else if (0 === aEventString.indexOf("P2P-INVITATION-RESULT")) {
+      id = EVENT_P2P_INVITATION_RESULT;
+      info.status = /status=([0-9]+)/.exec(aEventString)[1];
+    } else if (0 === aEventString.indexOf("P2P-INVITATION-RECEIVED")) {
+      // e.g. "P2P-INVITATION-RECEIVED sa=32:85:a9:da:e6:1f persistent=7".
+      id = EVENT_P2P_INVITATION_RECEIVED;
+      info.address = /sa=([0-9a-f:]+)/.exec(aEventString)[1];
+      info.netId = /persistent=([0-9]+)/.exec(aEventString)[1];
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-FAILURE")) {
+      id = EVENT_P2P_PROV_DISC_FAILURE;
+    } else {
+      // Not P2P event but we do receive it. Try to recognize it.
+      if (0 === aEventString.indexOf("AP-STA-DISCONNECTED")) {
+        id = EVENT_AP_STA_DISCONNECTED;
+        info.address = tokens[1];
+      } else if (0 === aEventString.indexOf("AP-STA-CONNECTED")) {
+        id = EVENT_AP_STA_CONNECTED;
+        info.address = tokens[1];
+      } else {
+        // Neither P2P event nor recognized supplicant event.
+        debug('Unknwon event string: ' + aEventString);
+      }
+    }
+
+    let event = {id: id, info: info};
+    debug('Event parsing result: ' + aEventString + ": " + JSON.stringify(event));
+
+    return event;
+  }
+
+  function isIgnoredEvent(aEventString) {
+    const IGNORED_EVENTS = [
+      "CTRL-EVENT-BSS-ADDED",
+      "CTRL-EVENT-BSS-REMOVED",
+      "CTRL-EVENT-SCAN-RESULTS",
+      "CTRL-EVENT-STATE-CHANGE",
+      "WPS-AP-AVAILABLE",
+      "WPS-ENROLLEE-SEEN"
+    ];
+    for(let i = 0; i < IGNORED_EVENTS.length; i++) {
+      if (0 === aEventString.indexOf(IGNORED_EVENTS[i])) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  function isPeerGroupOwner(aGroupFlag) {
+    return (aGroupFlag & GROUP_CAPAB_GROUP_OWNER) !== 0;
+  }
+
+  // Convert flag to a wps capability array.
+  //
+  // @param aWpsFlag Number that represents the wps capabilities.
+  // @return Array of WPS flag.
+  function wpsFlagToCapabilities(aWpsFlag) {
+    let wpsCapabilities = [];
+    if (aWpsFlag & 0x8) {
+      wpsCapabilities.push(WPS_METHOD_DISPLAY);
+    }
+    if (aWpsFlag & 0x80) {
+      wpsCapabilities.push(WPS_METHOD_PBC);
+    }
+    if (aWpsFlag & 0x100) {
+      wpsCapabilities.push(WPS_METHOD_KEYPAD);
+    }
+    return wpsCapabilities;
+  }
+
+  _stateMachine.start();
+  return manager;
+};
+
+function P2pStateMachine(aP2pCommand, aNetUtil) {
+  function debug(aMsg) {
+    if (gDebug) {
+      dump('-------------- WifiP2pStateMachine: ' + aMsg);
+    }
+  }
+
+  let p2pSm = {};  // The state machine to return.
+
+  let _sm = StateMachine('WIFIP2P'); // The general purpose state machine.
+
+  // Information we need to keep track across states.
+  let _observer;
+
+  let _onEnabled;
+  let _onDisabled;
+  let _onSupplicantConnected;
+  let _savedConfig = {}; // Configuration used to do P2P_CONNECT.
+  let _groupInfo = {};   // The information of the group we have formed.
+  let _removedGroupInfo = {}; // Used to store the group info we are going to remove.
+
+  let _scanBlocked = false;
+  let _scanPostponded = false;
+
+  let _localDevice = {
+    address: "",
+    deviceName: DEFAULT_P2P_DEVICE_NAME + "_" + libcutils.property_get("ro.build.product"),
+    wpsCapabilities: [WPS_METHOD_PBC, WPS_METHOD_KEYPAD, WPS_METHOD_DISPLAY]
+  };
+
+  let _p2pNetworkInterface = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
+
+    state: Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
+    type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI_P2P,
+    name: P2P_INTERFACE_NAME,
+    ip: null,
+    netmask: null,
+    broadcast: null,
+    dns1: null,
+    dns2: null,
+    gateway: null,
+    httpProxyHost: null,
+    httpProxyPort: null,
+
+    // help
+    registered: false,
+  };
+
+  //---------------------------------------------------------
+  // State machine APIs.
+  //---------------------------------------------------------
+
+  // Register the observer which is implemented in WifiP2pWorkerObserver.jsm.
+  //
+  // @param aObserver:
+  //   .onEnabled
+  //   .onDisbaled
+  //   .onPeerFound
+  //   .onPeerLost
+  //   .onConnecting
+  //   .onConnected
+  //   .onDisconnected
+  //   .onLocalDeviceChanged
+  p2pSm.setObserver = function(aObserver) {
+    _observer = aObserver;
+  };
+
+  p2pSm.start = function() {
+    _sm.start(stateDisabled);
+  };
+
+  p2pSm.sendEvent = function(aEvent) {
+    let willBeHandled = isInP2pManagedState(_sm.getCurrentState());
+    _sm.sendEvent(aEvent);
+    return willBeHandled;
+  };
+
+  // Initialize internal state machine _sm.
+  _sm.setDefaultEventHandler(handleEventCommon);
+
+  //----------------------------------------------------------
+  // State definition.
+  //----------------------------------------------------------
+
+  // The initial state.
+  var stateDisabled = _sm.makeState("DISABLED", {
+    enter: function() {
+      _onEnabled = null;
+      _onSupplicantConnected = null;
+      _savedConfig = null;
+      _groupInfo = null;
+      _removedGroupInfo = null;
+      _scanBlocked = false;
+      _scanPostponded = false;
+
+      unregisterP2pNetworkInteface();
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_CMD_ENABLE:
+          _onEnabled = aEvent.info.onEnabled;
+          _onSupplicantConnected = aEvent.info.onSupplicantConnected;
+          _sm.gotoState(stateEnabling);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    }
+  });
+
+  // The state where we are trying to enable wifi p2p.
+  var stateEnabling = _sm.makeState("ENABLING", {
+    enter: function() {
+
+      function onFailure()
+      {
+        _onEnabled(false);
+        _sm.gotoState(stateDisabled);
+      }
+
+      function onSuccess()
+      {
+        _onEnabled(true);
+        _sm.gotoState(stateInactive);
+      }
+
+      _sm.pause();
+
+      // Step 1: Connect to p2p0.
+      aP2pCommand.connectToSupplicant(function (status) {
+        let detail;
+
+        if (0 !== status) {
+          debug('Failed to connect to p2p0');
+          onFailure();
+          return;
+        }
+
+        debug('wpa_supplicant p2p0 connected!');
+        _onSupplicantConnected();
+
+        // Step 2: Get MAC address.
+        if (!_localDevice.address) {
+          aP2pCommand.getMacAddress(function (address) {
+            if (!address) {
+              debug('Failed to get MAC address....');
+              onFailure();
+              return;
+            }
+            debug('Got mac address: ' + address);
+            _localDevice.address = address;
+            _observer.onLocalDeviceChanged(_localDevice);
+          });
+        }
+
+        // Step 3: Enable p2p with the device name and wps methods.
+        detail = { deviceName: _localDevice.deviceName,
+                   deviceType: libcutils.property_get("ro.moz.wifi.p2p_device_type") || DEFAULT_P2P_DEVICE_TYPE,
+                   wpsMethods: libcutils.property_get("ro.moz.wifi.p2p_wps_methods") || DEFAULT_P2P_WPS_METHODS };
+
+        aP2pCommand.p2pEnable(detail, function (success) {
+          if (!success) {
+            debug('Failed to enable p2p');
+            onFailure();
+            return;
+          }
+
+          debug('P2P is enabled! Enabling net interface...');
+
+          // Step 4: Enable p2p0 net interface. wpa_supplicant may have
+          //         already done it for us.
+          aNetUtil.enableInterface(P2P_INTERFACE_NAME, function (success) {
+            onSuccess();
+          });
+        });
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      // We won't receive any event since all of them will be blocked.
+      return true;
+    }
+  });
+
+  // The state just after enabling wifi direct.
+  var stateInactive = _sm.makeState("INACTIVE", {
+    enter: function() {
+      registerP2pNetworkInteface();
+
+      if (_sm.getPreviousState() !== stateEnabling) {
+        _observer.onDisconnected(_savedConfig);
+      }
+
+      _savedConfig = null; // Used to connect p2p peer.
+      _groupInfo   = null; // The information of the formed group.
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        // Receiving the following 3 states implies someone is trying to
+        // connect to me.
+        case EVENT_P2P_PROV_DISC_PBC_REQ:
+        case EVENT_P2P_PROV_DISC_SHOW_PIN:
+        case EVENT_P2P_PROV_DISC_ENTER_PIN:
+          debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info));
+
+          _savedConfig = {
+            name:      aEvent.info.name,
+            address:   aEvent.info.address,
+            wpsMethod: aEvent.info.wpsMethod,
+            goIntent:  DEFAULT_GO_INTENT,
+            pin:       aEvent.info.pin // EVENT_P2P_PROV_DISC_SHOW_PIN only.
+          };
+
+          _sm.gotoState(stateWaitingForConfirmation);
+          break;
+
+        // Connect to a peer.
+        case EVENT_P2P_CMD_CONNECT:
+          debug('Trying to connect to peer: ' + JSON.stringify(aEvent.info));
+
+          _savedConfig = {
+            address:   aEvent.info.address,
+            wpsMethod: aEvent.info.wpsMethod,
+            goIntent:  aEvent.info.goIntent
+          };
+
+          _sm.gotoState(stateProvisionDiscovery);
+          aEvent.info.onDoConnect(true);
+          break;
+
+        case EVENT_P2P_INVITATION_RECEIVED:
+          _savedConfig = {
+            address: aEvent.info.address,
+            wpsMethod: WPS_METHOD_PBC,
+            goIntent: DEFAULT_GO_INTENT,
+            netId: aEvent.info.netId
+          };
+          _sm.gotoState(stateWaitingForInvitationConfirmation);
+          break;
+
+        case EVENT_P2P_GROUP_STARTED:
+          // Most likely the peer just reinvoked a peristen group and succeeeded.
+
+          _savedConfig = { address: aEvent.info.goAddress };
+
+          _sm.pause();
+          handleGroupStarted(aEvent.info, function (success) {
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_AP_STA_DISCONNECTED:
+          // We will hit this case when we used to be a group owner and
+          // requested to remove the group we owned.
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    },
+  });
+
+  // Waiting for user's confirmation.
+  var stateWaitingForConfirmation = _sm.makeState("WAITING_FOR_CONFIRMATION", {
+    timeoutTimer: null,
+
+    enter: function() {
+      gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+      this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+          if (!aEvent.info.accepted) {
+            debug('User rejected this request');
+            _sm.gotoState(stateInactive); // Reset to inactive state.
+            break;
+          }
+
+          debug('User accepted this request');
+
+          // The only information we may have to grab from user.
+          _savedConfig.pin = aEvent.info.pin;
+
+          // The case that user requested to form a group ealier on.
+          // Just go to connecting state and do p2p_connect.
+          if (_sm.getPreviousState() === stateProvisionDiscovery) {
+            _sm.gotoState(stateConnecting);
+            break;
+          }
+
+          // Otherwise, wait for EVENT_P2P_GO_NEG_REQUEST.
+          _sm.gotoState(stateWaitingForNegReq);
+          break;
+
+        case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+          debug('Confirmation timeout!');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GO_NEG_REQUEST:
+          _sm.deferEvent(aEvent);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  var stateWaitingForNegReq = _sm.makeState("WAITING_FOR_NEG_REQ", {
+    timeoutTimer: null,
+
+    enter: function() {
+      debug('Wait for EVENT_P2P_GO_NEG_REQUEST');
+      this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_NEG_REQ);
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_GO_NEG_REQUEST:
+          if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) {
+            debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ", " + _savedConfig.wpsMetho);
+          }
+          _sm.gotoState(stateConnecting);
+          break;
+
+        case EVENT_TIMEOUT_NEG_REQ:
+          debug("Waiting for NEG-REQ timeout");
+          _sm.gotoState(stateInactive);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  // Waiting for user's confirmation for invitation.
+  var stateWaitingForInvitationConfirmation = _sm.makeState("WAITING_FOR_INV_CONFIRMATION", {
+    timeoutTimer: null,
+
+    enter: function() {
+      gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+      this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+          if (!aEvent.info.accepted) {
+            debug('User rejected this request');
+            _sm.gotoState(stateInactive); // Reset to inactive state.
+            break;
+          }
+
+          debug('User accepted this request');
+          _sm.pause();
+          aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function (gc) {
+            let isPeeGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER;
+            _sm.gotoState(isPeeGroupOwner ? stateGroupAdding : stateReinvoking);
+          });
+
+          break;
+
+        case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+          debug('Confirmation timeout!');
+          _sm.gotoState(stateInactive);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  var stateGroupAdding = _sm.makeState("GROUP_ADDING", {
+    timeoutTimer: null,
+
+    enter: function() {
+      let self = this;
+
+      _observer.onConnecting(_savedConfig);
+
+      _sm.pause();
+      aP2pCommand.p2pGroupAdd(_savedConfig.netId, function (success) {
+        if (!success) {
+          _sm.gotoState(stateInactive);
+          return;
+        }
+        // Waiting for EVENT_P2P_GROUP_STARTED.
+        self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+        _sm.resume();
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_GROUP_STARTED:
+          _sm.pause();
+          handleGroupStarted(aEvent.info, function (success) {
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_P2P_GO_NEG_FAILURE:
+          debug('Negotiation failure. Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_TIMEOUT_CONNECTING:
+          debug('Connecting timeout! Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+        case EVENT_P2P_GO_NEG_SUCCESS:
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_FAILURE:
+          debug('Group formation failure');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_REMOVED:
+          debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()');
+          _removedGroupInfo = {
+            role:   aEvent.info.role,
+            ifname: aEvent.info.ifname
+          };
+          _sm.gotoState(stateDisconnecting);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  var stateReinvoking = _sm.makeState("REINVOKING", {
+    timeoutTimer: null,
+
+    enter: function() {
+      let self = this;
+
+      _observer.onConnecting(_savedConfig);
+      _sm.pause();
+      aP2pCommand.p2pReinvoke(_savedConfig.netId, _savedConfig.address, function(success) {
+        if (!success) {
+          _sm.gotoState(stateInactive);
+          return;
+        }
+        // Waiting for EVENT_P2P_GROUP_STARTED.
+        self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+        _sm.resume();
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_GROUP_STARTED:
+          _sm.pause();
+          handleGroupStarted(aEvent.info, function(success) {
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_P2P_GO_NEG_FAILURE:
+          debug('Negotiation failure. Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_TIMEOUT_CONNECTING:
+          debug('Connecting timeout! Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+        case EVENT_P2P_GO_NEG_SUCCESS:
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_FAILURE:
+          debug('Group formation failure');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_REMOVED:
+          debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()');
+          _removedGroupInfo = {
+            role:   aEvent.info.role,
+            ifname: aEvent.info.ifname
+          };
+          _sm.gotoState(stateDisconnecting);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+    }
+  });
+
+  var stateProvisionDiscovery = _sm.makeState("PROVISION_DISCOVERY", {
+    enter: function() {
+      function onDiscoveryCommandSent(success) {
+        if (!success) {
+          _sm.gotoState(stateInactive);
+          debug('Failed to send p2p_prov_disc. Go back to inactive state.');
+          return;
+        }
+
+        debug('p2p_prov_disc has been sent.');
+
+        _sm.resume();
+        // Waiting for EVENT_P2P_PROV_DISC_PBC_RESP or
+        //             EVENT_P2P_PROV_DISC_SHOW_PIN or
+        //             EVENT_P2P_PROV_DISC_ENTER_PIN.
+      }
+
+      _sm.pause();
+      aP2pCommand.p2pProvDiscovery(_savedConfig.address,
+                                   toPeerWpsMethod(_savedConfig.wpsMethod),
+                                   onDiscoveryCommandSent);
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_PROV_DISC_PBC_RESP:
+          _sm.gotoState(stateConnecting); // No need for local user grant.
+          break;
+        case EVENT_P2P_PROV_DISC_SHOW_PIN:
+        case EVENT_P2P_PROV_DISC_ENTER_PIN:
+          if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) {
+            debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ":" + _savedConfig.wpsMethod);
+          }
+          if (EVENT_P2P_PROV_DISC_SHOW_PIN === aEvent.id) {
+            _savedConfig.pin = aEvent.info.pin;
+          }
+          _sm.gotoState(stateWaitingForConfirmation);
+          break;
+
+        case EVENT_P2P_PROV_DISC_FAILURE:
+          _sm.gotoState(stateInactive);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    }
+  });
+
+  // We are going to connect to the peer.
+  // |_savedConfig| is supposed to have been filled properly.
+  var stateConnecting = _sm.makeState("CONNECTING", {
+    timeoutTimer: null,
+
+    enter: function() {
+      let self = this;
+
+      if (null === _savedConfig.goIntent) {
+        _savedConfig.goIntent = DEFAULT_GO_INTENT;
+      }
+
+      _observer.onConnecting(_savedConfig);
+
+      let wpsMethodWithPin;
+      if (WPS_METHOD_KEYPAD === _savedConfig.wpsMethod ||
+          WPS_METHOD_DISPLAY === _savedConfig.wpsMethod) {
+        // e.g. '12345678 display or '12345678 keypad'.
+        wpsMethodWithPin = (_savedConfig.pin + ' ' + _savedConfig.wpsMethod);
+      } else {
+        // e.g. 'pbc'.
+        wpsMethodWithPin = _savedConfig.wpsMethod;
+      }
+
+      _sm.pause();
+
+      aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function(gc) {
+        debug('group capabilities of ' + _savedConfig.address + ': ' + gc);
+
+        let isPeerGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER;
+        let config = { address:           _savedConfig.address,
+                       wpsMethodWithPin:  wpsMethodWithPin,
+                       goIntent:          _savedConfig.goIntent,
+                       joinExistingGroup: isPeerGroupOwner };
+
+        aP2pCommand.p2pConnect(config, function (success) {
+          if (!success) {
+            debug('Failed to send p2p_connect');
+            _sm.gotoState(stateInactive);
+            return;
+          }
+          debug('Waiting for EVENT_P2P_GROUP_STARTED.');
+          self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+          _sm.resume();
+        });
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_GROUP_STARTED:
+          _sm.pause();
+          handleGroupStarted(aEvent.info, function (success) {
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_P2P_GO_NEG_FAILURE:
+          debug('Negotiation failure. Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_TIMEOUT_CONNECTING:
+          debug('Connecting timeout! Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+        case EVENT_P2P_GO_NEG_SUCCESS:
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_FAILURE:
+          debug('Group formation failure');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_REMOVED:
+          debug('Received P2P-GROUP-REMOVED due to previous failed ' +
+                'handleGroupdStarted()');
+          _removedGroupInfo = {
+            role:   aEvent.info.role,
+            ifname: aEvent.info.ifname
+          };
+          _sm.gotoState(stateDisconnecting);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+    }
+  });
+
+  var stateConnected = _sm.makeState("CONNECTED", {
+    groupOwner: null,
+
+    enter: function() {
+      this.groupOwner = {
+        macAddress: _groupInfo.goAddress,
+        ipAddress:  _groupInfo.networkInterface.gateway,
+        passphrase: _groupInfo.passphrase,
+        ssid:       _groupInfo.ssid,
+        freq:       _groupInfo.freq,
+        isLocal:    _groupInfo.isGroupOwner
+      };
+
+      if (!_groupInfo.isGroupOwner) {
+        _observer.onConnected(this.groupOwner, _savedConfig);
+      } else {
+        // If I am a group owner, notify onConnected until EVENT_AP_STA_CONNECTED
+        // is received.
+      }
+
+      _removedGroupInfo = null;
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_AP_STA_CONNECTED:
+          if (_groupInfo.isGroupOwner) {
+            _observer.onConnected(this.groupOwner, _savedConfig);
+          }
+          break;
+
+        case EVENT_P2P_GROUP_REMOVED:
+          _removedGroupInfo = {
+            role:   aEvent.info.role,
+            ifname: aEvent.info.ifname
+          };
+          _sm.gotoState(stateDisconnecting);
+          break;
+
+        case EVENT_AP_STA_DISCONNECTED:
+          debug('Client disconnected: ' + aEvent.info.address);
+
+          // Now we suppose it's the only client. Remove my group.
+          _sm.pause();
+          aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function (success) {
+            debug('Requested to remove p2p group. Wait for EVENT_P2P_GROUP_REMOVED.');
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_P2P_CMD_DISCONNECT:
+          // Since we only support single connection, we can ignore
+          // the given peer address.
+          _sm.pause();
+          aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function(success) {
+            aEvent.info.onDoDisconnect(true);
+            _sm.resume();
+          });
+
+          debug('Sent disconnect command. Wait for EVENT_P2P_GROUP_REMOVED.');
+          break;
+
+        case EVENT_P2P_PROV_DISC_PBC_REQ:
+        case EVENT_P2P_PROV_DISC_SHOW_PIN:
+        case EVENT_P2P_PROV_DISC_ENTER_PIN:
+          debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info));
+
+          _savedConfig = {
+            name:      aEvent.info.name,
+            address:   aEvent.info.address,
+            wpsMethod: aEvent.info.wpsMethod,
+            pin:       aEvent.info.pin
+          };
+
+          _sm.gotoState(stateWaitingForJoiningConfirmation);
+          break;
+
+        default:
+          return false;
+      } // end of switch
+      return true;
+    }
+  });
+
+  var stateWaitingForJoiningConfirmation = _sm.makeState("WAITING_FOR_JOINING_CONFIRMATION", {
+    timeoutTimer: null,
+
+    enter: function() {
+      gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+      this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+    },
+
+    handleEvent: function (aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+          if (!aEvent.info.accepted) {
+            debug('User rejected invitation!');
+            _sm.gotoState(stateConnected);
+            break;
+          }
+
+          let onWpsCommandSent = function(success) {
+            _observer.onConnecting(_savedConfig);
+            _sm.gotoState(stateConnected);
+          };
+
+          _sm.pause();
+          if (WPS_METHOD_PBC === _savedConfig.wpsMethod) {
+            aP2pCommand.wpsPbc(_groupInfo.ifname, onWpsCommandSent);
+          } else {
+            let detail = { pin: _savedConfig.pin, iface: _groupInfo.ifname };
+            aP2pCommand.wpsPin(detail, onWpsCommandSent);
+          }
+          break;
+
+        case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+          debug('WAITING_FOR_JOINING_CONFIRMATION timeout!');
+          _sm.gotoState(stateConnected);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  var stateDisconnecting = _sm.makeState("DISCONNECTING", {
+    enter: function() {
+      _sm.pause();
+      handleGroupRemoved(_removedGroupInfo, function (success) {
+        if (!success) {
+          debug('Failed to handle group removed event. What can I do?');
+        }
+        _sm.gotoState(stateInactive);
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      return false; // We will not receive any event in this state.
+    }
+  });
+
+  var stateDisabling = _sm.makeState("DISABLING", {
+    enter: function() {
+      _sm.pause();
+      aNetUtil.stopDhcpServer(function (success) { // Stopping DHCP server is harmless.
+        debug('Stop DHCP server result: ' + success);
+        aP2pCommand.p2pDisable(function(success) {
+          debug('P2P function disabled');
+          aP2pCommand.closeSupplicantConnection(function (status) {
+            debug('Supplicant connection closed');
+            aNetUtil.disableInterface(P2P_INTERFACE_NAME, function (success){
+              debug('Disabled interface: ' + P2P_INTERFACE_NAME);
+              _onDisabled(true);
+              _sm.gotoState(stateDisabled);
+            });
+          });
+        });
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      return false; // We will not receive any event in this state.
+    }
+  });
+
+  //----------------------------------------------------------
+  // Helper functions.
+  //----------------------------------------------------------
+
+  // Handle 'P2P_GROUP_STARTED' event. Note that this function
+  // will also do the state transitioning and error handling.
+  //
+  // @param aInfo Information carried by "P2P_GROUP_STARTED" event:
+  //   .role: P2P_ROLE_GO or P2P_ROLE_CLIENT
+  //   .ssid:
+  //   .freq:
+  //   .passphrase: Used to connect to GO for legacy device.
+  //   .goAddress:
+  //   .ifname: e.g. p2p-p2p0
+  //
+  // @param aCallback Callback function.
+  function handleGroupStarted(aInfo, aCallback) {
+    debug('handleGroupStarted: ' + JSON.stringify(aInfo));
+
+    function onSuccess()
+    {
+      _sm.gotoState(stateConnected);
+      aCallback(true);
+    }
+
+    function onFailure()
+    {
+      debug('Failed to handleGroupdStarted(). Remove the group...');
+      aP2pCommand.p2pGroupRemove(aInfo.ifname, function (success) {
+        aCallback(false);
+
+        if (success) {
+          return; // Stay in current state and wait for EVENT_P2P_GROUP_REMOVED.
+        }
+
+        debug('p2pGroupRemove command error!');
+        _sm.gotoState(stateInactive);
+      });
+    }
+
+    // Save this group information.
+    _groupInfo = aInfo;
+    _groupInfo.isGroupOwner = (P2P_ROLE_GO === aInfo.role);
+
+    if (_groupInfo.isGroupOwner) {
+      debug('Group owner. Start DHCP server');
+      let dhcpServerConfig = { ifname: aInfo.ifname,
+                               startIp: GO_DHCP_SERVER_IP_RANGE.startIp,
+                               endIp: GO_DHCP_SERVER_IP_RANGE.endIp,
+                               serverIp: GO_NETWORK_INTERFACE.ip,
+                               maskLength: GO_NETWORK_INTERFACE.maskLength };
+
+      aNetUtil.startDhcpServer(dhcpServerConfig, function (success) {
+        if (!success) {
+          debug('Failed to start DHCP server');
+          onFailure();
+          return;
+        }
+
+        // Update p2p network interface.
+        _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
+        _p2pNetworkInterface.ip = GO_NETWORK_INTERFACE.ip;
+        _p2pNetworkInterface.netmask = GO_NETWORK_INTERFACE.netmask;
+        _p2pNetworkInterface.gateway = GO_NETWORK_INTERFACE.ip;
+        handleP2pNetworkInterfaceStateChanged();
+
+        _groupInfo.networkInterface = _p2pNetworkInterface;
+
+        debug('Everything is done. Happy p2p GO~');
+        onSuccess();
+      });
+
+      return;
+    }
+
+    // We are the client.
+
+    debug("Client. Request IP from DHCP server on interface: " + _groupInfo.ifname);
+
+    aNetUtil.runDhcp(aInfo.ifname, function(dhcpData) {
+      if(!dhcpData || !dhcpData.info) {
+        debug('Failed to run DHCP client');
+        onFailure();
+        return;
+      }
+
+      // Save network interface.
+      debug("DHCP request success: " + JSON.stringify(dhcpData.info));
+
+      // Update p2p network interface.
+      _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
+      _p2pNetworkInterface.ip = dhcpData.info.ipaddr_str;
+      _p2pNetworkInterface.netmask = dhcpData.info.mask_str;
+      _p2pNetworkInterface.broadcast = dhcpData.info.broadcast_str;
+      _p2pNetworkInterface.dns1 = dhcpData.info.dns1_str;
+      _p2pNetworkInterface.dns2 = dhcpData.info.dns2_str;
+      _p2pNetworkInterface.gateway = dhcpData.info.gateway_str;
+      handleP2pNetworkInterfaceStateChanged();
+
+      _groupInfo.networkInterface = _p2pNetworkInterface;
+
+      debug('Happy p2p client~');
+      onSuccess();
+    });
+  }
+
+  function resetP2pNetworkInterface() {
+    _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
+    _p2pNetworkInterface.ip = null;
+    _p2pNetworkInterface.netmask = null;
+    _p2pNetworkInterface.broadcast = null;
+    _p2pNetworkInterface.dns1 = null;
+    _p2pNetworkInterface.dns2 = null;
+    _p2pNetworkInterface.gateway = null;
+  }
+
+  function registerP2pNetworkInteface() {
+    if (!_p2pNetworkInterface.registered) {
+      resetP2pNetworkInterface();
+      gNetworkManager.registerNetworkInterface(_p2pNetworkInterface);
+      _p2pNetworkInterface.registered = true;
+    }
+  }
+
+  function unregisterP2pNetworkInteface() {
+    if (_p2pNetworkInterface.registered) {
+      resetP2pNetworkInterface();
+      gNetworkManager.unregisterNetworkInterface(_p2pNetworkInterface);
+      _p2pNetworkInterface.registered = false;
+    }
+  }
+
+  function handleP2pNetworkInterfaceStateChanged() {
+    Services.obs.notifyObservers(_p2pNetworkInterface,
+                                 kNetworkInterfaceStateChangedTopic,
+                                 null);
+  }
+
+  // Handle 'P2P_GROUP_STARTED' event.
+  //
+  // @param aInfo information carried by "P2P_GROUP_REMOVED" event:
+  //   .ifname
+  //   .role: "GO" or "client".
+  //
+  // @param aCallback Callback function.
+  function handleGroupRemoved(aInfo, aCallback) {
+    if (!_groupInfo) {
+      debug('No group info. Why?');
+      aCallback(true);
+      return;
+    }
+    if (_groupInfo.ifname !== aInfo.ifname ||
+        _groupInfo.role   !== aInfo.role) {
+      debug('Unmatched group info: ' + JSON.stringify(_groupInfo) +
+            ' v.s. ' + JSON.stringify(aInfo));
+    }
+
+    // Update p2p network interface.
+    _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
+    handleP2pNetworkInterfaceStateChanged();
+
+    if (P2P_ROLE_GO === aInfo.role) {
+      aNetUtil.stopDhcpServer(function(success) {
+        debug('Stop DHCP server result: ' + success);
+        aCallback(true);
+      });
+    } else {
+      aNetUtil.stopDhcp(aInfo.ifname, function() {
+        aCallback(true);
+      });
+    }
+  }
+
+  // Non state-specific event handler.
+  function handleEventCommon(aEvent) {
+    switch (aEvent.id) {
+      case EVENT_P2P_DEVICE_FOUND:
+        _observer.onPeerFound(aEvent.info);
+        break;
+
+      case EVENT_P2P_DEVICE_LOST:
+        _observer.onPeerLost(aEvent.info);
+        break;
+
+      case EVENT_P2P_CMD_DISABLE:
+        _onDisabled = aEvent.info.onDisabled;
+        _sm.gotoState(stateDisabling);
+        break;
+
+      case EVENT_P2P_CMD_ENABLE_SCAN:
+        if (_scanBlocked) {
+          _scanPostponded = true;
+          aEvent.info.callback(true);
+          break;
+        }
+        aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, aEvent.info.callback);
+        break;
+
+      case EVENT_P2P_CMD_DISABLE_SCAN:
+        aP2pCommand.p2pDisableScan(aEvent.info.callback);
+        break;
+
+      case EVENT_P2P_FIND_STOPPED:
+        break;
+
+      case EVENT_P2P_CMD_BLOCK_SCAN:
+        _scanBlocked = true;
+        aP2pCommand.p2pDisableScan(function(success) {});
+        break;
+
+      case EVENT_P2P_CMD_UNBLOCK_SCAN:
+        _scanBlocked = false;
+        if (_scanPostponded) {
+          aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, function(success) {});
+        }
+        break;
+
+      case EVENT_P2P_CMD_CONNECT:
+      case EVENT_P2P_CMD_DISCONNECT:
+        debug("The current state couldn't handle connect/disconnect request. Ignore it.");
+        break;
+
+      default:
+        return false;
+    } // End of switch.
+    return true;
+  }
+
+  function isInP2pManagedState(aState) {
+    let p2pManagedStates = [stateWaitingForConfirmation,
+                            stateWaitingForNegReq,
+                            stateProvisionDiscovery,
+                            stateWaitingForInvitationConfirmation,
+                            stateGroupAdding,
+                            stateReinvoking,
+                            stateConnecting,
+                            stateConnected,
+                            stateDisconnecting];
+
+    for (let i = 0; i < p2pManagedStates.length; i++) {
+      if (aState === p2pManagedStates[i]) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  function initTimeoutTimer(aTimeoutMs, aTimeoutEvent) {
+    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    function onTimerFired() {
+      _sm.sendEvent({ id: aTimeoutEvent });
+      timer = null;
+    }
+    timer.initWithCallback(onTimerFired.bind(this), aTimeoutMs,
+                           Ci.nsITimer.TYPE_ONE_SHOT);
+    return timer;
+  }
+
+  // Converts local WPS method to peer WPS method.
+  function toPeerWpsMethod(aLocalWpsMethod) {
+    switch (aLocalWpsMethod) {
+      case WPS_METHOD_DISPLAY:
+        return WPS_METHOD_KEYPAD;
+      case WPS_METHOD_KEYPAD:
+        return WPS_METHOD_DISPLAY;
+      case WPS_METHOD_PBC:
+        return WPS_METHOD_PBC;
+      default:
+        return WPS_METHOD_PBC; // Use "push button" as the default method.
+    }
+  }
+
+  return p2pSm;
+}
+
+this.WifiP2pManager.INTERFACE_NAME = P2P_INTERFACE_NAME;
new file mode 100644
--- /dev/null
+++ b/dom/wifi/WifiP2pWorkerObserver.jsm
@@ -0,0 +1,303 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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;
+
+const CONNECTION_STATUS_DISCONNECTED  = "disconnected";
+const CONNECTION_STATUS_CONNECTING    = "connecting";
+const CONNECTION_STATUS_CONNECTED     = "connected";
+const CONNECTION_STATUS_DISCONNECTING = "disconnecting";
+
+const DEBUG = false;
+
+this.EXPORTED_SYMBOLS = ["WifiP2pWorkerObserver"];
+
+// WifiP2pWorkerObserver resides in WifiWorker to handle DOM message
+// by either 1) returning internally maintained information or
+//           2) delegating to aDomMsgResponder. It is also responsible
+// for observing events from WifiP2pManager and dispatch to DOM.
+//
+// @param aDomMsgResponder handles DOM messages, including
+//        - setScanEnabled
+//        - connect
+//        - disconnect
+//        - setPairingConfirmation
+//        The instance is actually WifiP2pManager.
+this.WifiP2pWorkerObserver = function(aDomMsgResponder) {
+  function debug(aMsg) {
+    if (DEBUG) {
+      dump('-------------- WifiP2pWorkerObserver: ' + aMsg);
+    }
+  }
+
+  // Private member variables.
+  let _localDevice;
+  let _peerList = {}; // List of P2pDevice.
+  let _domManagers = [];
+
+  // Constructor of P2pDevice. It will be exposed to DOM.
+  //
+  // @param aPeer object representing a P2P device:
+  //   .name: string for the device name.
+  //   .address: Mac address.
+  //   .isGroupOwner: boolean to indicate if this device is the group owner.
+  //   .wpsCapabilities: array of string of {"pbc", "display", "keypad"}.
+  function P2pDevice(aPeer) {
+    this.address = aPeer.address;
+    this.name = (aPeer.name ? aPeer.name : aPeer.address);
+    this.isGroupOwner = aPeer.isGroupOwner;
+    this.wpsCapabilities = aPeer.wpsCapabilities;
+    this.connectionStatus = CONNECTION_STATUS_DISCONNECTED;
+
+    // Since this object will be exposed to web, defined the exposed
+    // properties here.
+    this.__exposedProps__ = {
+      address: "r",
+      name: "r",
+      isGroupOwner: "r",
+      wpsCapabilities: "r",
+      connectionStatus: "r"
+    };
+  }
+
+  // Constructor of P2pGroupOwner.
+  //
+  // @param aGroupOwner:
+  //   .macAddress
+  //   .ipAddress
+  //   .passphrase
+  //   .ssid
+  //   .freq
+  //   .isLocal
+  function P2pGroupOwner(aGroupOwner) {
+    this.macAddress = aGroupOwner.macAddress; // The identifier to get further information.
+    this.ipAddress = aGroupOwner.ipAddress;
+    this.passphrase = aGroupOwner.passphrase;
+    this.ssid = aGroupOwner.ssid; // e.g. DIRECT-xy.
+    this.freq = aGroupOwner.freq;
+    this.isLocal = aGroupOwner.isLocal;
+
+    let detail = _peerList[aGroupOwner.macAddress];
+    if (detail) {
+      this.name = detail.name;
+      this.wpsCapabilities = detail.wpsCapabilities;
+    } else if (_localDevice.address === this.macAddress) {
+      this.name = _localDevice.name;
+      this.wpsCapabilities = _localDevice.wpsCapabilities;
+    } else {
+      debug("We don't know this group owner: " + aGroupOwner.macAddress);
+      this.name = aGroupOwner.macAddress;
+      this.wpsCapabilities = [];
+    }
+  }
+
+  function fireEvent(aMessage, aData) {
+    debug('domManager: ' + JSON.stringify(_domManagers));
+    _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("WifiP2pManager:" + aMessage, aData);
+    });
+  }
+
+  function addDomManager(aMsg) {
+    if (-1 === _domManagers.indexOf(aMsg.manager)) {
+      _domManagers.push(aMsg.manager);
+    }
+  }
+
+  function returnMessage(aMessage, aSuccess, aData, aMsg) {
+    let rMsg = aMessage + ":Return:" + (aSuccess ? "OK" : "NO");
+    aMsg.manager.sendAsyncMessage(rMsg,
+                                 { data: aData, rid: aMsg.rid, mid: aMsg.mid });
+  }
+
+  function handlePeerListUpdated() {
+    fireEvent("onpeerinfoupdate", {});
+  }
+
+  // Return a literal object as the constructed object.
+  return {
+    onLocalDeviceChanged: function(aDevice) {
+      _localDevice = aDevice;
+      debug('Local device updated to: ' + JSON.stringify(_localDevice));
+    },
+
+    onEnabled: function() {
+      _peerList = [];
+      fireEvent("p2pUp", {});
+    },
+
+    onDisbaled: function() {
+      fireEvent("p2pDown", {});
+    },
+
+    onPeerFound: function(aPeer) {
+      let newFoundPeer = new P2pDevice(aPeer);
+      let origianlPeer = _peerList[aPeer.address];
+      _peerList[aPeer.address] = newFoundPeer;
+      if (origianlPeer) {
+        newFoundPeer.connectionStatus = origianlPeer.connectionStatus;
+      }
+      handlePeerListUpdated();
+    },
+
+    onPeerLost: function(aPeer) {
+      let lostPeer = _peerList[aPeer.address];
+      if (!lostPeer) {
+        debug('Unknown peer lost: ' + aPeer.address);
+        return;
+      }
+      delete _peerList[aPeer.address];
+      handlePeerListUpdated();
+    },
+
+    onConnecting: function(aPeer) {
+      let peer = _peerList[aPeer.address];
+      if (!peer) {
+        debug('Unknown peer connecting: ' + aPeer.address);
+        peer = new P2pDevice(aPeer);
+        _peerList[aPeer.address] = peer;
+        handlePeerListUpdated();
+      }
+      peer.connectionStatus = CONNECTION_STATUS_CONNECTING;
+
+      fireEvent('onconnecting', { peer: peer });
+    },
+
+    onConnected: function(aGroupOwner, aPeer) {
+      let go = new P2pGroupOwner(aGroupOwner);
+      let peer = _peerList[aPeer.address];
+      if (!peer) {
+        debug('Unknown peer connected: ' + aPeer.address);
+        peer = new P2pDevice(aPeer);
+        _peerList[aPeer.address] = peer;
+        handlePeerListUpdated();
+      }
+      peer.connectionStatus = CONNECTION_STATUS_CONNECTED;
+      peer.isGroupOwner = (aPeer.address === aGroupOwner.address);
+
+      fireEvent('onconnected', { groupOwner: go, peer: peer });
+    },
+
+    onDisconnected: function(aPeer) {
+      let peer = _peerList[aPeer.address];
+      if (!peer) {
+        debug('Unknown peer disconnected: ' + aPeer.address);
+        return;
+      }
+
+      peer.connectionStatus = CONNECTION_STATUS_DISCONNECTED;
+      fireEvent('ondisconnected', { peer: peer });
+    },
+
+    getObservedDOMMessages: function() {
+      return [
+        "WifiP2pManager:getState",
+        "WifiP2pManager:getPeerList",
+        "WifiP2pManager:setScanEnabled",
+        "WifiP2pManager:connect",
+        "WifiP2pManager:disconnect",
+        "WifiP2pManager:setPairingConfirmation",
+        "WifiP2pManager:setDeviceName"
+      ];
+    },
+
+    onDOMMessage: function(aMessage) {
+      let msg = aMessage.data || {};
+      msg.manager = aMessage.target;
+
+      if ("child-process-shutdown" === aMessage.name) {
+        let i;
+        if (-1 !== (i = _domManagers.indexOf(msg.manager))) {
+          _domManagers.splice(i, 1);
+        }
+        return;
+      }
+
+      if (!aMessage.target.assertPermission("wifi-manage")) {
+        return;
+      }
+
+      switch (aMessage.name) {
+        case "WifiP2pManager:getState": // A new DOM manager is created.
+          addDomManager(msg);
+          return { peerList: _peerList, }; // Synchronous call. Simply return it.
+
+        case "WifiP2pManager:setScanEnabled":
+          {
+            let enabled = msg.data;
+
+            aDomMsgResponder.setScanEnabled(enabled, function(success) {
+              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+            });
+          }
+          break;
+
+        case "WifiP2pManager:getPeerList":
+          {
+            // Convert the object to an array.
+            let peerArray = [];
+            for (let key in _peerList) {
+              if (_peerList.hasOwnProperty(key)) {
+                peerArray.push(_peerList[key]);
+              }
+            }
+
+            returnMessage(aMessage.name, true, peerArray, msg);
+          }
+          break;
+
+        case "WifiP2pManager:connect":
+          {
+            let peer = msg.data;
+
+            let onDoConnect = function(success) {
+              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+            };
+
+            aDomMsgResponder.connect(peer.address, peer.wpsMethod,
+                                     peer.goIntent, onDoConnect);
+          }
+          break;
+
+        case "WifiP2pManager:disconnect":
+          {
+            let address = msg.data;
+
+            aDomMsgResponder.disconnect(address, function(success) {
+              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+            });
+          }
+          break;
+
+        case "WifiP2pManager:setPairingConfirmation":
+          {
+            let result = msg.data;
+            aDomMsgResponder.setPairingConfirmation(result);
+            returnMessage(aMessage.name, true, true, msg);
+          }
+          break;
+
+        case "WifiP2pManager:setDeviceName":
+          {
+            let newDeviceName = msg.data;
+            aDomMsgResponder.setDeviceName(newDeviceName, function(success) {
+              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+            });
+          }
+          break;
+
+        default:
+          if (0 === aMessage.name.indexOf("WifiP2pManager:")) {
+            debug("DOM WifiP2pManager message not handled: " + aMessage.name);
+          }
+      } // End of switch.
+    }
+  };
+};
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -8,16 +8,18 @@
 
 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/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";
 
@@ -103,26 +105,40 @@ 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",
       ifname: libcutils.property_get("wifi.interface")
     };
   }
 
-  let {sdkVersion, unloadDriverEnabled, schedScanRecovery, driverDelay, ifname} = getStartupPrefs();
+  let {sdkVersion, unloadDriverEnabled, schedScanRecovery, driverDelay, p2pSupported, ifname} = getStartupPrefs();
 
   let wifiListener = {
     onWaitEvent: function(event, iface) {
       if (manager.ifname === iface && handleEvent(event)) {
         waitForEvent(iface);
+      } else if (p2pSupported) {
+        if (WifiP2pManager.INTERFACE_NAME === iface) {
+          // 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);
     }
   }
 
@@ -130,28 +146,39 @@ var WifiManager = (function() {
   // 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);
+
+  // Wifi P2P stuff
+  var p2pManager;
+  if (p2pSupported) {
+    let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME);
+    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!");
   }
 
-  var wifiCommand = WifiCommand(controlMessage, manager.ifname);
-  var netUtil = WifiNetUtil(controlMessage);
-
   // 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) {
@@ -235,39 +262,42 @@ var WifiManager = (function() {
   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;
       });
+      p2pManager.setDebug(DEBUG);
     }
   }
 
   function getDebugEnabled(callback) {
     wifiCommand.getLogLevel(function(level) {
       if (level === null) {
         debug("Unable to get wpa_supplicant's log level");
         callback(false);
@@ -746,16 +776,17 @@ var WifiManager = (function() {
       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("WPS-TIMEOUT") === 0) {
       notifyStateChange({ state: "WPS_TIMEOUT", BSSID: null, id: -1 });
       return true;
     }
     if (eventData.indexOf("WPS-FAIL") === 0) {
@@ -777,16 +808,20 @@ var WifiManager = (function() {
     getDebugEnabled(function(ok) {
       syncDebug();
     });
     wifiCommand.status(function(status) {
       parseStatus(status);
       notify("supplicantconnection");
       callback();
     });
+
+    if (p2pSupported) {
+      manager.enableP2p(function(success) {});
+    }
   }
 
   function prepareForStartup(callback) {
     let status = libcutils.property_get(DHCP_PROP + "_" + manager.ifname);
     if (status !== "running") {
       tryStopSupplicant();
       return;
     }
@@ -905,29 +940,37 @@ var WifiManager = (function() {
             }
           });
         });
       });
     } else {
       // 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.
-      manager.state = "DISABLING";
-      wifiCommand.terminateSupplicant(function (ok) {
-        manager.connectionDropped(function () {
-          wifiCommand.stopSupplicant(function (status) {
-            wifiCommand.closeSupplicantConnection(function () {
-              manager.state = "UNINITIALIZED";
-              netUtil.disableInterface(manager.ifname, function (ok) {
-                unloadDriver(callback);
+      let doDisableWifi = function() {
+        manager.state = "DISABLING";
+        wifiCommand.terminateSupplicant(function (ok) {
+          manager.connectionDropped(function () {
+            wifiCommand.stopSupplicant(function (status) {
+              wifiCommand.closeSupplicantConnection(function () {
+                manager.state = "UNINITIALIZED";
+                netUtil.disableInterface(manager.ifname, function (ok) {
+                  unloadDriver(callback);
+                });
               });
             });
           });
         });
-      });
+      }
+
+      if (p2pSupported) {
+        p2pManager.setEnabled(false, { onDisabled: doDisableWifi });
+      } else {
+        doDisableWifi();
+      }
     }
   }
 
   // Get wifi interface and load wifi driver when enable Ap mode.
   manager.setWifiApEnabled = function(enabled, configuration, callback) {
     if (enabled) {
       manager.tetheringState = "INITIALIZING";
       loadDriver(function (status) {
@@ -1129,16 +1172,21 @@ var WifiManager = (function() {
     }
     return ntohl(mask);
   }
 
   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) {
@@ -1207,16 +1255,56 @@ var WifiManager = (function() {
     } 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() {
+        wifiService.waitForEvent(WifiP2pManager.INTERFACE_NAME);
+      },
+
+      onEnabled: function(success) {
+        callback(success);
+      }
+    });
+  };
+
   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 = "",
@@ -1489,16 +1577,27 @@ function WifiWorker() {
   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
@@ -1521,16 +1620,17 @@ function WifiWorker() {
   }
 
   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
@@ -2296,16 +2396,25 @@ WifiWorker.prototype = {
     msg.manager.sendAsyncMessage(message + (success ? ":OK" : ":NO"),
                                  { data: data, rid: msg.rid, mid: msg.mid });
   },
 
   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);
       }
@@ -2385,89 +2494,16 @@ WifiWorker.prototype = {
 
       // 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;
-
-    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;
-            this.wantScanResults.splice(this.wantScanResults.indexOf(waitForScanCallback), 1);
-            callback.onfailure();
-            return;
-          }
-
-          // Else it's still running, continue waiting.
-          timer.initWithCallback(doScan, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
-          return;
-        }
-      });
-    }
-
-    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) {
@@ -2714,17 +2750,17 @@ WifiWorker.prototype = {
     }).bind(this));
   },
 
   wps: function(msg) {
     const message = "WifiManager:wps:Return";
     let self = this;
     let detail = msg.data;
     if (detail.method === "pbc") {
-      WifiManager.wpsPbc(function(ok) {
+      WifiManager.wpsPbc(null, 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)
--- a/dom/wifi/moz.build
+++ b/dom/wifi/moz.build
@@ -1,33 +1,39 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # 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',
     'nsIDOMMozWifiStatusChangeEvent.idl',
     'nsIWifi.idl',
     'nsIWifiService.idl',
 ]
 
 XPIDL_MODULE = 'dom_wifi'
 
 EXTRA_COMPONENTS += [
     'DOMWifiManager.js',
     'DOMWifiManager.manifest',
+    'DOMWifiP2pManager.js',
+    'DOMWifiP2pManager.manifest',
     'WifiWorker.js',
     'WifiWorker.manifest',
 ]
 
 EXTRA_JS_MODULES += [
+    'StateMachine.jsm',
     'WifiCommand.jsm',
     'WifiNetUtil.jsm',
+    'WifiP2pManager.jsm',
+    'WifiP2pWorkerObserver.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     SOURCES = [
         'NetUtils.cpp',
         'WifiProxyService.cpp',
         'WifiUtils.cpp',
     ]