Merge "Enhance WPS"
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 6b4c895..8363e6e 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -297,6 +297,10 @@
                     mWifiStateMachine.sendMessage(Message.obtain(msg));
                     break;
                 }
+                case WifiManager.CANCEL_WPS: {
+                    mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    break;
+                }
                 case WifiManager.DISABLE_NETWORK: {
                     mWifiStateMachine.sendMessage(Message.obtain(msg));
                     break;
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 8305714..46ad036 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -286,7 +286,10 @@
                     config.status = Status.CURRENT;
                     break;
                 case DISCONNECTED:
-                    config.status = Status.ENABLED;
+                    //If network is already disabled, keep the status
+                    if (config.status == Status.CURRENT) {
+                        config.status = Status.ENABLED;
+                    }
                     break;
                 default:
                     //do nothing, retain the existing state
@@ -906,7 +909,7 @@
                         }
                     }
                 } else {
-                    loge("Missing id while parsing configuration");
+                    if (DBG) log("Missing id while parsing configuration");
                 }
             }
         } catch (EOFException ignore) {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 54dc047..d746810 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1069,15 +1069,22 @@
     public static final int START_WPS_SUCCEEDED             = BASE + 11;
     /** @hide */
     public static final int WPS_FAILED                      = BASE + 12;
-   /** @hide */
+    /** @hide */
     public static final int WPS_COMPLETED                   = BASE + 13;
 
     /** @hide */
-    public static final int DISABLE_NETWORK                 = BASE + 14;
+    public static final int CANCEL_WPS                      = BASE + 14;
     /** @hide */
-    public static final int DISABLE_NETWORK_FAILED          = BASE + 15;
+    public static final int CANCEL_WPS_FAILED               = BASE + 15;
     /** @hide */
-    public static final int DISABLE_NETWORK_SUCCEEDED       = BASE + 16;
+    public static final int CANCEL_WPS_SUCCEDED             = BASE + 16;
+
+    /** @hide */
+    public static final int DISABLE_NETWORK                 = BASE + 17;
+    /** @hide */
+    public static final int DISABLE_NETWORK_FAILED          = BASE + 18;
+    /** @hide */
+    public static final int DISABLE_NETWORK_SUCCEEDED       = BASE + 19;
 
     /* For system use only */
     /** @hide */
@@ -1091,14 +1098,14 @@
      * Indicates that the operation failed due to an internal error.
      * @hide
      */
-    public static final int ERROR               = 0;
+    public static final int ERROR                       = 0;
 
     /**
      * Passed with {@link ActionListener#onFailure}.
      * Indicates that the operation is already in progress
      * @hide
      */
-    public static final int IN_PROGRESS         = 1;
+    public static final int IN_PROGRESS                 = 1;
 
     /**
      * Passed with {@link ActionListener#onFailure}.
@@ -1106,11 +1113,19 @@
      * unable to service the request
      * @hide
      */
-    public static final int BUSY                = 2;
+    public static final int BUSY                        = 2;
 
     /* WPS specific errors */
     /** WPS overlap detected {@hide} */
-    public static final int WPS_OVERLAP_ERROR   = 3;
+    public static final int WPS_OVERLAP_ERROR           = 3;
+    /** WEP on WPS is prohibited {@hide} */
+    public static final int WPS_WEP_PROHIBITED          = 4;
+    /** TKIP only prohibited {@hide} */
+    public static final int WPS_TKIP_ONLY_PROHIBITED    = 5;
+    /** Authentication failure on WPS {@hide} */
+    public static final int WPS_AUTH_FAILURE            = 6;
+    /** WPS timed out {@hide} */
+    public static final int WPS_TIMED_OUT               = 7;
 
     /** Interface for callback invocation when framework channel is lost {@hide} */
     public interface ChannelListener {
@@ -1165,6 +1180,7 @@
         private SparseArray<Object> mListenerMap = new SparseArray<Object>();
         private Object mListenerMapLock = new Object();
         private int mListenerKey = 0;
+        private static final int INVALID_KEY = -1;
 
         AsyncChannel mAsyncChannel;
         WifiHandler mHandler;
@@ -1187,6 +1203,7 @@
                     case WifiManager.CONNECT_NETWORK_FAILED:
                     case WifiManager.FORGET_NETWORK_FAILED:
                     case WifiManager.SAVE_NETWORK_FAILED:
+                    case WifiManager.CANCEL_WPS_FAILED:
                     case WifiManager.DISABLE_NETWORK_FAILED:
                         if (listener != null) {
                             ((ActionListener) listener).onFailure(message.arg1);
@@ -1196,6 +1213,7 @@
                     case WifiManager.CONNECT_NETWORK_SUCCEEDED:
                     case WifiManager.FORGET_NETWORK_SUCCEEDED:
                     case WifiManager.SAVE_NETWORK_SUCCEEDED:
+                    case WifiManager.CANCEL_WPS_SUCCEDED:
                     case WifiManager.DISABLE_NETWORK_SUCCEEDED:
                         if (listener != null) {
                             ((ActionListener) listener).onSuccess();
@@ -1229,16 +1247,19 @@
         }
 
         int putListener(Object listener) {
-            if (listener == null) return 0;
+            if (listener == null) return INVALID_KEY;
             int key;
             synchronized (mListenerMapLock) {
-                key = mListenerKey++;
+                do {
+                    key = mListenerKey++;
+                } while (key == INVALID_KEY);
                 mListenerMap.put(key, listener);
             }
             return key;
         }
 
         Object removeListener(int key) {
+            if (key == INVALID_KEY) return null;
             synchronized (mListenerMapLock) {
                 Object listener = mListenerMap.get(key);
                 mListenerMap.remove(key);
@@ -1388,6 +1409,21 @@
     }
 
     /**
+     * Cancel any ongoing Wi-fi Protected Setup
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param listener for callbacks on success or failure. Can be null.
+     * @hide
+     */
+    public void cancelWps(Channel c, ActionListener listener) {
+        if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+
+        c.mAsyncChannel.sendMessage(CANCEL_WPS, 0, c.putListener(listener));
+    }
+
+
+
+    /**
      * Get a reference to WifiService handler. This is used by a client to establish
      * an AsyncChannel communication with WifiService
      *
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index e1cfba3..c406fa0 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -65,7 +65,20 @@
 
     /* WPS events */
     private static final String WPS_SUCCESS_STR = "WPS-SUCCESS";
+
+    /* Format: WPS-FAIL msg=%d [config_error=%d] [reason=%d (%s)] */
     private static final String WPS_FAIL_STR    = "WPS-FAIL";
+    private static final String WPS_FAIL_PATTERN =
+            "WPS-FAIL msg=\\d+(?: config_error=(\\d+))?(?: reason=(\\d+))?";
+
+    /* config error code values for config_error=%d */
+    private static final int CONFIG_MULTIPLE_PBC_DETECTED = 12;
+    private static final int CONFIG_AUTH_FAILURE = 18;
+
+    /* reason code values for reason=%d */
+    private static final int REASON_TKIP_ONLY_PROHIBITED = 1;
+    private static final int REASON_WEP_PROHIBITED = 2;
+
     private static final String WPS_OVERLAP_STR = "WPS-OVERLAP-DETECTED";
     private static final String WPS_TIMEOUT_STR = "WPS-TIMEOUT";
 
@@ -316,7 +329,7 @@
                     } else if (eventStr.startsWith(WPS_SUCCESS_STR)) {
                         mStateMachine.sendMessage(WPS_SUCCESS_EVENT);
                     } else if (eventStr.startsWith(WPS_FAIL_STR)) {
-                        mStateMachine.sendMessage(WPS_FAIL_EVENT);
+                        handleWpsFailEvent(eventStr);
                     } else if (eventStr.startsWith(WPS_OVERLAP_STR)) {
                         mStateMachine.sendMessage(WPS_OVERLAP_EVENT);
                     } else if (eventStr.startsWith(WPS_TIMEOUT_STR)) {
@@ -458,6 +471,43 @@
             }
         }
 
+        private void handleWpsFailEvent(String dataString) {
+            final Pattern p = Pattern.compile(WPS_FAIL_PATTERN);
+            Matcher match = p.matcher(dataString);
+            if (match.find()) {
+                String cfgErr = match.group(1);
+                String reason = match.group(2);
+
+                if (reason != null) {
+                    switch(Integer.parseInt(reason)) {
+                        case REASON_TKIP_ONLY_PROHIBITED:
+                            mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+                                    WifiManager.WPS_TKIP_ONLY_PROHIBITED, 0));
+                            return;
+                        case REASON_WEP_PROHIBITED:
+                            mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+                                    WifiManager.WPS_WEP_PROHIBITED, 0));
+                            return;
+                    }
+                }
+                if (cfgErr != null) {
+                    switch(Integer.parseInt(cfgErr)) {
+                        case CONFIG_AUTH_FAILURE:
+                            mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+                                    WifiManager.WPS_AUTH_FAILURE, 0));
+                            return;
+                        case CONFIG_MULTIPLE_PBC_DETECTED:
+                            mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+                                    WifiManager.WPS_OVERLAP_ERROR, 0));
+                            return;
+                    }
+                }
+            }
+            //For all other errors, return a generic internal error
+            mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+                    WifiManager.ERROR, 0));
+        }
+
         /**
          * Handle p2p events
          */
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index e3dd3a6..ecd4073 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -388,27 +388,37 @@
         return doStringCommand("SIGNAL_POLL");
     }
 
-    public boolean startWpsPbc() {
-        return doBooleanCommand("WPS_PBC");
-    }
-
     public boolean startWpsPbc(String bssid) {
-        return doBooleanCommand("WPS_PBC " + bssid);
+        if (TextUtils.isEmpty(bssid)) {
+            return doBooleanCommand("WPS_PBC");
+        } else {
+            return doBooleanCommand("WPS_PBC " + bssid);
+        }
     }
 
     public boolean startWpsPinKeypad(String pin) {
+        if (TextUtils.isEmpty(pin)) return false;
         return doBooleanCommand("WPS_PIN any " + pin);
     }
 
     public String startWpsPinDisplay(String bssid) {
-        return doStringCommand("WPS_PIN " + bssid);
+        if (TextUtils.isEmpty(bssid)) {
+            return doStringCommand("WPS_PIN any");
+        } else {
+            return doStringCommand("WPS_PIN " + bssid);
+        }
     }
 
     /* Configures an access point connection */
     public boolean startWpsRegistrar(String bssid, String pin) {
+        if (TextUtils.isEmpty(bssid) || TextUtils.isEmpty(pin)) return false;
         return doBooleanCommand("WPS_REG " + bssid + " " + pin);
     }
 
+    public boolean cancelWps() {
+        return doBooleanCommand("WPS_CANCEL");
+    }
+
     public boolean setPersistentReconnect(boolean enabled) {
         int value = (enabled == true) ? 1 : 0;
         return doBooleanCommand("SET persistent_reconnect " + value);
@@ -539,7 +549,7 @@
     }
 
     public boolean p2pGroupRemove(String iface) {
-        if (iface == null) return false;
+        if (TextUtils.isEmpty(iface)) return false;
         return doBooleanCommand("P2P_GROUP_REMOVE " + iface);
     }
 
@@ -549,7 +559,7 @@
 
     /* Invite a peer to a group */
     public boolean p2pInvite(WifiP2pGroup group, String deviceAddress) {
-        if (deviceAddress == null) return false;
+        if (TextUtils.isEmpty(deviceAddress)) return false;
 
         if (group == null) {
             return doBooleanCommand("P2P_INVITE peer=" + deviceAddress);
@@ -561,19 +571,19 @@
 
     /* Reinvoke a persistent connection */
     public boolean p2pReinvoke(int netId, String deviceAddress) {
-        if (deviceAddress == null || netId < 0) return false;
+        if (TextUtils.isEmpty(deviceAddress) || netId < 0) return false;
 
         return doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + deviceAddress);
     }
 
 
     public String p2pGetInterfaceAddress(String deviceAddress) {
-        if (deviceAddress == null) return null;
+        if (TextUtils.isEmpty(deviceAddress)) return null;
 
         //  "p2p_peer deviceAddress" returns a multi-line result containing
         //      intended_addr=fa:7b:7a:42:82:13
         String peerInfo = p2pPeer(deviceAddress);
-        if (peerInfo == null) return null;
+        if (TextUtils.isEmpty(peerInfo)) return null;
         String[] tokens= peerInfo.split("\n");
 
         for (String token : tokens) {
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index d6988cd..843620c 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1844,6 +1844,10 @@
                     replyToMessage(message, WifiManager.WPS_FAILED,
                             WifiManager.BUSY);
                     break;
+                case WifiManager.CANCEL_WPS:
+                    replyToMessage(message, WifiManager.CANCEL_WPS_FAILED,
+                            WifiManager.BUSY);
+                    break;
                 case WifiManager.DISABLE_NETWORK:
                     replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
                             WifiManager.BUSY);
@@ -3321,8 +3325,15 @@
                     transitionTo(mDisconnectedState);
                     break;
                 case WifiMonitor.WPS_FAIL_EVENT:
+                    //arg1 has the reason for the failure
+                    replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, message.arg1);
+                    mSourceMessage.recycle();
+                    mSourceMessage = null;
+                    transitionTo(mDisconnectedState);
+                    break;
                 case WifiMonitor.WPS_TIMEOUT_EVENT:
-                    replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, WifiManager.ERROR);
+                    replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
+                            WifiManager.WPS_TIMED_OUT);
                     mSourceMessage.recycle();
                     mSourceMessage = null;
                     transitionTo(mDisconnectedState);
@@ -3330,6 +3341,14 @@
                 case WifiManager.START_WPS:
                     replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.IN_PROGRESS);
                     break;
+                case WifiManager.CANCEL_WPS:
+                    if (mWifiNative.cancelWps()) {
+                        replyToMessage(message, WifiManager.CANCEL_WPS_SUCCEDED);
+                    } else {
+                        replyToMessage(message, WifiManager.CANCEL_WPS_FAILED, WifiManager.ERROR);
+                    }
+                    transitionTo(mDisconnectedState);
+                    break;
                 /* Defer all commands that can cause connections to a different network
                  * or put the state machine out of connect mode
                  */
@@ -3346,6 +3365,11 @@
                     if (DBG) log("Network connection lost");
                     handleNetworkDisconnect();
                     break;
+                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+                    //Throw away supplicant state changes when WPS is running.
+                    //We will start getting supplicant state changes once we get
+                    //a WPS success or failure
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -3353,6 +3377,7 @@
             return HANDLED;
         }
 
+        @Override
         public void exit() {
             mWifiConfigStore.enableAllNetworks();
             mWifiConfigStore.loadConfiguredNetworks();
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 5b0e424..399dc9d 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -996,7 +996,7 @@
                     break;
                 case PEER_CONNECTION_USER_ACCEPT:
                     if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
-                        mWifiNative.startWpsPbc();
+                        mWifiNative.startWpsPbc(null);
                     } else {
                         mWifiNative.startWpsPinKeypad(mSavedPeerConfig.wps.pin);
                     }