Merge "StreamingZipInflater: fix mmap'd end of read"
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 152827d..9522112 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -169,10 +169,10 @@
      * </ul>
      */
     public static final Pattern PHONE
-        = Pattern.compile(                                  // sdd = space, dot, or dash
-                "(\\+[0-9]+[\\- \\.]*)?"                    // +<digits><sdd>*
-                + "(\\([0-9]+\\)[\\- \\.]*)?"               // (<digits>)<sdd>*
-                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit> 
+        = Pattern.compile(                      // sdd = space, dot, or dash
+                "(\\+[0-9]+[\\- \\.]*)?"        // +<digits><sdd>*
+                + "(\\([0-9]+\\)[\\- \\.]*)?"   // (<digits>)<sdd>*
+                + "([0-9][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
 
     /**
      *  Convenience method to take all of the non-null matching groups in a
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index ae56e6b..7154f95 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -1258,6 +1258,40 @@
 
         mAutoFillData = new WebViewCore.AutoFillData();
         mEditTextScroller = new Scroller(context);
+
+        // Calculate channel distance
+        calculateChannelDistance(context);
+    }
+
+    /**
+     * Calculate sChannelDistance based on the screen information.
+     * @param context A Context object used to access application assets.
+     */
+    private void calculateChannelDistance(Context context) {
+        // The channel distance is adjusted for density and screen size
+        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        final double screenSize = Math.hypot((double)(metrics.widthPixels/metrics.densityDpi),
+                (double)(metrics.heightPixels/metrics.densityDpi));
+        if (screenSize < 3.0) {
+            sChannelDistance = 16;
+        } else if (screenSize < 5.0) {
+            sChannelDistance = 22;
+        } else if (screenSize < 7.0) {
+            sChannelDistance = 28;
+        } else {
+            sChannelDistance = 34;
+        }
+        sChannelDistance = (int)(sChannelDistance * metrics.density);
+        if (sChannelDistance < 16) sChannelDistance = 16;
+
+        if (DebugFlags.WEB_VIEW) {
+            Log.v(LOGTAG, "sChannelDistance : " + sChannelDistance
+                    + ", density : " + metrics.density
+                    + ", screenSize : " + screenSize
+                    + ", metrics.heightPixels : " + metrics.heightPixels
+                    + ", metrics.widthPixels : " + metrics.widthPixels
+                    + ", metrics.densityDpi : " + metrics.densityDpi);
+        }
     }
 
     // WebViewProvider bindings
@@ -5715,32 +5749,13 @@
         }
         return mWebViewPrivate.super_dispatchKeyEvent(event);
     }
-
-    /*
-     * Here is the snap align logic:
-     * 1. If it starts nearly horizontally or vertically, snap align;
-     * 2. If there is a dramitic direction change, let it go;
-     *
-     * Adjustable parameters. Angle is the radians on a unit circle, limited
-     * to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical)
-     */
-    private static final float HSLOPE_TO_START_SNAP = .25f;
-    private static final float HSLOPE_TO_BREAK_SNAP = .4f;
-    private static final float VSLOPE_TO_START_SNAP = 1.25f;
-    private static final float VSLOPE_TO_BREAK_SNAP = .95f;
-    /*
-     *  These values are used to influence the average angle when entering
-     *  snap mode. If is is the first movement entering snap, we set the average
-     *  to the appropriate ideal. If the user is entering into snap after the
-     *  first movement, then we average the average angle with these values.
-     */
-    private static final float ANGLE_VERT = 2f;
-    private static final float ANGLE_HORIZ = 0f;
-    /*
-     *  The modified moving average weight.
-     *  Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n
-     */
-    private static final float MMA_WEIGHT_N = 5;
+    
+    private static final int SNAP_BOUND = 16;
+    private static int sChannelDistance = 16;
+    private int mFirstTouchX = -1; // the first touched point
+    private int mFirstTouchY = -1;
+    private int mDistanceX = 0;
+    private int mDistanceY = 0;
 
     private boolean inFullScreenMode() {
         return mFullScreenHolder != null;
@@ -5830,12 +5845,6 @@
         }
     }
 
-    private float calculateDragAngle(int dx, int dy) {
-        dx = Math.abs(dx);
-        dy = Math.abs(dy);
-        return (float) Math.atan2(dy, dx);
-    }
-
     /*
     * Common code for single touch and multi-touch.
     * (x, y) denotes current focus point, which is the touch point for single touch
@@ -5861,6 +5870,12 @@
         switch (action) {
             case MotionEvent.ACTION_DOWN: {
                 mConfirmMove = false;
+
+                // Channel Scrolling
+                mFirstTouchX = x;
+                mFirstTouchY = y;
+                mDistanceX = mDistanceY = 0;
+
                 if (!mEditTextScroller.isFinished()) {
                     mEditTextScroller.abortAnimation();
                 }
@@ -5998,20 +6013,16 @@
                         break;
                     }
 
-                    // Only lock dragging to one axis if we don't have a scale in progress.
-                    // Scaling implies free-roaming movement. Note this is only ever a question
-                    // if mZoomManager.supportsPanDuringZoom() is true.
-                    mAverageAngle = calculateDragAngle(deltaX, deltaY);
-                    if (detector == null || !detector.isInProgress()) {
-                        // if it starts nearly horizontal or vertical, enforce it
-                        if (mAverageAngle < HSLOPE_TO_START_SNAP) {
-                            mSnapScrollMode = SNAP_X;
-                            mSnapPositive = deltaX > 0;
-                            mAverageAngle = ANGLE_HORIZ;
-                        } else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
+                    if ((detector == null || !detector.isInProgress())
+                            && SNAP_NONE == mSnapScrollMode) {
+                        int ax = Math.abs(x - mFirstTouchX);
+                        int ay = Math.abs(y - mFirstTouchY);
+                        if (ax < SNAP_BOUND && ay < SNAP_BOUND) {
+                            break;
+                        } else if (ax < SNAP_BOUND) {
                             mSnapScrollMode = SNAP_Y;
-                            mSnapPositive = deltaY > 0;
-                            mAverageAngle = ANGLE_VERT;
+                        } else if (ay < SNAP_BOUND) {
+                            mSnapScrollMode = SNAP_X;
                         }
                     }
 
@@ -6030,31 +6041,21 @@
                 if (deltaX == 0 && deltaY == 0) {
                     keepScrollBarsVisible = true;
                 } else {
-                    mAverageAngle +=
-                        (calculateDragAngle(deltaX, deltaY) - mAverageAngle)
-                        / MMA_WEIGHT_N;
-                    if (mSnapScrollMode != SNAP_NONE) {
-                        if (mSnapScrollMode == SNAP_Y) {
-                            // radical change means getting out of snap mode
-                            if (mAverageAngle < VSLOPE_TO_BREAK_SNAP) {
-                                mSnapScrollMode = SNAP_NONE;
-                            }
-                        }
+                    if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
+                        mDistanceX += Math.abs(deltaX);
+                        mDistanceY += Math.abs(deltaY);
                         if (mSnapScrollMode == SNAP_X) {
-                            // radical change means getting out of snap mode
-                            if (mAverageAngle > HSLOPE_TO_BREAK_SNAP) {
+                            if (mDistanceY > sChannelDistance) {
                                 mSnapScrollMode = SNAP_NONE;
-                            }
+                            } else if (mDistanceX > sChannelDistance) {
+                                mDistanceX = mDistanceY = 0;
                         }
                     } else {
-                        if (mAverageAngle < HSLOPE_TO_START_SNAP) {
-                            mSnapScrollMode = SNAP_X;
-                            mSnapPositive = deltaX > 0;
-                            mAverageAngle = (mAverageAngle + ANGLE_HORIZ) / 2;
-                        } else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
-                            mSnapScrollMode = SNAP_Y;
-                            mSnapPositive = deltaY > 0;
-                            mAverageAngle = (mAverageAngle + ANGLE_VERT) / 2;
+                            if (mDistanceX > sChannelDistance) {
+                                mSnapScrollMode = SNAP_NONE;
+                            } else if (mDistanceY > sChannelDistance) {
+                                mDistanceX = mDistanceY = 0;
+                            }
                         }
                     }
                     if (mSnapScrollMode != SNAP_NONE) {
@@ -6089,6 +6090,7 @@
                 break;
             }
             case MotionEvent.ACTION_UP: {
+                mFirstTouchX  = mFirstTouchY = -1;
                 if (mIsEditingText && mSelectionStarted) {
                     endScrollEdit();
                     mPrivateHandler.sendEmptyMessageDelayed(SCROLL_HANDLE_INTO_VIEW,
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
index a6057de..0461c0b 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
@@ -28,6 +28,7 @@
 import android.net.wifi.WifiConfiguration.IpAssignment;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.ProxySettings;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.RouteInfo;
@@ -67,7 +68,6 @@
  *      networkprefixlength.
  */
 public class AccessPointParserHelper {
-    private static final String KEYSTORE_SPACE = "keystore://";
     private static final String TAG = "AccessPointParserHelper";
     static final int NONE = 0;
     static final int WEP = 1;
@@ -212,14 +212,11 @@
                         config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
                         config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
                         // Initialize other fields.
-                        config.phase2.setValue("");
-                        config.ca_cert.setValue("");
-                        config.client_cert.setValue("");
-                        config.engine.setValue("");
-                        config.engine_id.setValue("");
-                        config.key_id.setValue("");
-                        config.identity.setValue("");
-                        config.anonymous_identity.setValue("");
+                        config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+                        config.enterpriseConfig.setCaCertificateAlias("");
+                        config.enterpriseConfig.setClientCertificateAlias("");
+                        config.enterpriseConfig.setIdentity("");
+                        config.enterpriseConfig.setAnonymousIdentity("");
                         break;
                     default:
                         throw new SAXException();
@@ -246,7 +243,7 @@
                         config.preSharedKey = '"' + passwordStr + '"';
                     }
                 } else if (securityType == EAP) {
-                    config.password.setValue(passwordStr);
+                    config.enterpriseConfig.setPassword(passwordStr);
                 } else {
                     throw new SAXException();
                 }
@@ -257,33 +254,46 @@
                 if (!validateEapValue(eapValue)) {
                     throw new SAXException();
                 }
-                config.eap.setValue(eapValue);
+		if (eapValue.equals("TLS")) {
+		    config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+		} else if (eapValue.equals("TTLS")) {
+		    config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+		} else if (eapValue.equals("PEAP")) {
+		    config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
+		}
                 eap = false;
             }
             if (phase2) {
                 String phase2Value = new String(ch, start, length);
-                config.phase2.setValue("auth=" + phase2Value);
+		if (phase2Value.equals("PAP")) {
+                    config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.PAP);
+		} else if (phase2Value.equals("MSCHAP")) {
+                    config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAP);
+		} else if (phase2Value.equals("MSCHAPV2")) {
+                    config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAPV2);
+		} else if (phase2Value.equals("GTC")) {
+                    config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.GTC);
+		}
                 phase2 = false;
             }
             if (identity) {
                 String identityValue = new String(ch, start, length);
-                config.identity.setValue(identityValue);
+                config.enterpriseConfig.setIdentity(identityValue);
                 identity = false;
             }
             if (anonymousidentity) {
                 String anonyId = new String(ch, start, length);
-                config.anonymous_identity.setValue(anonyId);
+                config.enterpriseConfig.setAnonymousIdentity(anonyId);
                 anonymousidentity = false;
             }
             if (cacert) {
                 String cacertValue = new String(ch, start, length);
-                // need to install the credentail to "keystore://"
-                config.ca_cert.setValue(KEYSTORE_SPACE);
+                config.enterpriseConfig.setCaCertificateAlias(cacertValue);
                 cacert = false;
             }
             if (usercert) {
                 String usercertValue = new String(ch, start, length);
-                config.client_cert.setValue(KEYSTORE_SPACE);
+                config.enterpriseConfig.setClientCertificateAlias(usercertValue);
                 usercert = false;
             }
             if (ip) {
diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java
index 9519b9f..ebdbb0e 100644
--- a/core/tests/coretests/src/android/util/PatternsTest.java
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -156,6 +156,8 @@
                 "Me: 16505551212 this\n",
                 "Me: 6505551212 this\n",
                 "Me: 5551212 this\n",
+                "Me: 2211 this\n",
+                "Me: 112 this\n",
 
                 "Me: 1-650-555-1212 this\n",
                 "Me: (650) 555-1212 this\n",
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 84506b6..f119a4b 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -25,7 +25,6 @@
 import android.net.NetworkInfo.DetailedState;
 import android.net.ProxyProperties;
 import android.net.RouteInfo;
-import android.net.wifi.WifiConfiguration.EnterpriseField;
 import android.net.wifi.WifiConfiguration.IpAssignment;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.ProxySettings;
@@ -37,6 +36,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.UserHandle;
+import android.security.KeyStore;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -144,6 +144,7 @@
     private static final String EOS = "eos";
 
     private WifiNative mWifiNative;
+    private final KeyStore mKeyStore = KeyStore.getInstance();
 
     WifiConfigStore(Context c, WifiNative wn) {
         mContext = c;
@@ -295,16 +296,7 @@
     boolean forgetNetwork(int netId) {
         if (mWifiNative.removeNetwork(netId)) {
             mWifiNative.saveConfig();
-            WifiConfiguration target = null;
-            WifiConfiguration config = mConfiguredNetworks.get(netId);
-            if (config != null) {
-                target = mConfiguredNetworks.remove(netId);
-                mNetworkIds.remove(configKey(config));
-            }
-            if (target != null) {
-                writeIpAndProxyConfigurations();
-                sendConfiguredNetworksChangedBroadcast(target, WifiManager.CHANGE_REASON_REMOVED);
-            }
+            removeConfigAndSendBroadcastIfNeeded(netId);
             return true;
         } else {
             loge("Failed to remove network " + netId);
@@ -342,20 +334,27 @@
      */
     boolean removeNetwork(int netId) {
         boolean ret = mWifiNative.removeNetwork(netId);
-        WifiConfiguration config = null;
         if (ret) {
-            config = mConfiguredNetworks.get(netId);
-            if (config != null) {
-                config = mConfiguredNetworks.remove(netId);
-                mNetworkIds.remove(configKey(config));
-            }
-        }
-        if (config != null) {
-            sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+            removeConfigAndSendBroadcastIfNeeded(netId);
         }
         return ret;
     }
 
+    private void removeConfigAndSendBroadcastIfNeeded(int netId) {
+        WifiConfiguration config = mConfiguredNetworks.get(netId);
+        if (config != null) {
+            // Remove any associated keys
+            if (config.enterpriseConfig != null) {
+                config.enterpriseConfig.removeKeys(mKeyStore);
+            }
+            mConfiguredNetworks.remove(netId);
+            mNetworkIds.remove(configKey(config));
+
+            writeIpAndProxyConfigurations();
+            sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+        }
+    }
+
     /**
      * Enable a network. Note that there is no saveConfig operation.
      * This function is retained for compatibility with the public
@@ -1122,34 +1121,57 @@
                 break setVariables;
             }
 
-            for (WifiConfiguration.EnterpriseField field
-                    : config.enterpriseFields) {
-                String varName = field.varName();
-                String value = field.value();
-                if (value != null) {
-                    if (field == config.engine) {
-                        /*
-                         * If the field is declared as an integer, it must not
-                         * be null
-                         */
-                        if (value.length() == 0) {
-                            value = "0";
-                        }
-                    } else if (field != config.eap) {
-                        value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
+            if (config.enterpriseConfig != null) {
+
+                WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+
+                if (enterpriseConfig.needsKeyStore()) {
+                    /**
+                     * Keyguard settings may eventually be controlled by device policy.
+                     * We check here if keystore is unlocked before installing
+                     * credentials.
+                     * TODO: Figure a way to store these credentials for wifi alone
+                     * TODO: Do we need a dialog here ?
+                     */
+                    if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+                        loge(config.SSID + ": key store is locked");
+                        break setVariables;
                     }
-                    if (!mWifiNative.setNetworkVariable(
-                                netId,
-                                varName,
-                                value)) {
-                        loge(config.SSID + ": failed to set " + varName +
-                                ": " + value);
+
+                    try {
+                        /* config passed may include only fields being updated.
+                         * In order to generate the key id, fetch uninitialized
+                         * fields from the currently tracked configuration
+                         */
+                        WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
+                        String keyId = config.getKeyIdForCredentials(currentConfig);
+
+                        if (!enterpriseConfig.installKeys(mKeyStore, keyId)) {
+                            loge(config.SSID + ": failed to install keys");
+                            break setVariables;
+                        }
+                    } catch (IllegalStateException e) {
+                        loge(config.SSID + " invalid config for key installation");
                         break setVariables;
                     }
                 }
+
+                HashMap<String, String> enterpriseFields = enterpriseConfig.getFields();
+                for (String key : enterpriseFields.keySet()) {
+                        String value = enterpriseFields.get(key);
+                        if (!mWifiNative.setNetworkVariable(
+                                    netId,
+                                    key,
+                                    value)) {
+                            enterpriseConfig.removeKeys(mKeyStore);
+                            loge(config.SSID + ": failed to set " + key +
+                                    ": " + value);
+                            break setVariables;
+                        }
+                }
             }
             updateFailed = false;
-        }
+        } //end of setVariables
 
         if (updateFailed) {
             if (newNetwork) {
@@ -1445,78 +1467,31 @@
             }
         }
 
-        for (WifiConfiguration.EnterpriseField field :
-                config.enterpriseFields) {
-            value = mWifiNative.getNetworkVariable(netId,
-                    field.varName());
+        if (config.enterpriseConfig == null) {
+            config.enterpriseConfig = new WifiEnterpriseConfig();
+        }
+        HashMap<String, String> enterpriseFields = config.enterpriseConfig.getFields();
+        for (String key : WifiEnterpriseConfig.getSupplicantKeys()) {
+            value = mWifiNative.getNetworkVariable(netId, key);
             if (!TextUtils.isEmpty(value)) {
-                if (field != config.eap && field != config.engine) {
-                    value = removeDoubleQuotes(value);
-                }
-                field.setValue(value);
+                enterpriseFields.put(key, removeDoubleQuotes(value));
+            } else {
+                enterpriseFields.put(key, WifiEnterpriseConfig.EMPTY_VALUE);
             }
         }
 
-        migrateOldEapTlsIfNecessary(config, netId);
-    }
-
-    /**
-     * Migration code for old EAP-TLS configurations. This should only be used
-     * when restoring an old wpa_supplicant.conf or upgrading from a previous
-     * platform version.
-     *
-     * @param config the configuration to be migrated
-     * @param netId the wpa_supplicant's net ID
-     * @param value the old private_key value
-     */
-    private void migrateOldEapTlsIfNecessary(WifiConfiguration config, int netId) {
-        String value = mWifiNative.getNetworkVariable(netId,
-                WifiConfiguration.OLD_PRIVATE_KEY_NAME);
-        /*
-         * If the old configuration value is not present, then there is nothing
-         * to do.
-         */
-        if (TextUtils.isEmpty(value)) {
-            return;
-        } else {
-            // Also ignore it if it's empty quotes.
-            value = removeDoubleQuotes(value);
-            if (TextUtils.isEmpty(value)) {
-                return;
-            }
+        if (config.enterpriseConfig.migrateOldEapTlsNative(mWifiNative, netId)) {
+            saveConfig();
         }
-
-        config.engine.setValue(WifiConfiguration.ENGINE_ENABLE);
-        config.engine_id.setValue(convertToQuotedString(WifiConfiguration.KEYSTORE_ENGINE_ID));
-
-        /*
-         * The old key started with the keystore:// URI prefix, but we don't
-         * need that anymore. Trim it off if it exists.
-         */
-        final String keyName;
-        if (value.startsWith(WifiConfiguration.KEYSTORE_URI)) {
-            keyName = new String(value.substring(WifiConfiguration.KEYSTORE_URI.length()));
-        } else {
-            keyName = value;
-        }
-        config.key_id.setValue(convertToQuotedString(keyName));
-
-        // Now tell the wpa_supplicant the new configuration values.
-        final EnterpriseField needsUpdate[] = { config.engine, config.engine_id, config.key_id };
-        for (EnterpriseField field : needsUpdate) {
-            mWifiNative.setNetworkVariable(netId, field.varName(), field.value());
-        }
-
-        // Remove old private_key string so we don't run this again.
-        mWifiNative.setNetworkVariable(netId, WifiConfiguration.OLD_PRIVATE_KEY_NAME,
-                convertToQuotedString(""));
-
-        saveConfig();
     }
 
     private String removeDoubleQuotes(String string) {
-        if (string.length() <= 2) return "";
-        return string.substring(1, string.length() - 1);
+        int length = string.length();
+        if ((length > 1) && (string.charAt(0) == '"')
+                && (string.charAt(length - 1) == '"')) {
+            return string.substring(1, length - 1);
+        }
+        return string;
     }
 
     private String convertToQuotedString(String string) {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index c4fe1b4..bf82792 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -19,49 +19,16 @@
 import android.net.LinkProperties;
 import android.os.Parcelable;
 import android.os.Parcel;
+import android.text.TextUtils;
 
 import java.util.BitSet;
 
 /**
  * A class representing a configured Wi-Fi network, including the
- * security configuration. Android will not necessarily support
- * all of these security schemes initially.
+ * security configuration.
  */
 public class WifiConfiguration implements Parcelable {
-
-    /**
-     * In old configurations, the "private_key" field was used. However, newer
-     * configurations use the key_id field with the engine_id set to "keystore".
-     * If this field is found in the configuration, the migration code is
-     * triggered.
-     * @hide
-     */
-    public static final String OLD_PRIVATE_KEY_NAME = "private_key";
-
-    /**
-     * String representing the keystore OpenSSL ENGINE's ID.
-     * @hide
-     */
-    public static final String KEYSTORE_ENGINE_ID = "keystore";
-
-    /**
-     * String representing the keystore URI used for wpa_supplicant.
-     * @hide
-     */
-    public static final String KEYSTORE_URI = "keystore://";
-
-    /**
-     * String to set the engine value to when it should be enabled.
-     * @hide
-     */
-    public static final String ENGINE_ENABLE = "1";
-
-    /**
-     * String to set the engine value to when it should be disabled.
-     * @hide
-     */
-    public static final String ENGINE_DISABLE = "0";
-
+    private static final String TAG = "WifiConfiguration";
     /** {@hide} */
     public static final String ssidVarName = "ssid";
     /** {@hide} */
@@ -78,56 +45,6 @@
     public static final String hiddenSSIDVarName = "scan_ssid";
     /** {@hide} */
     public static final int INVALID_NETWORK_ID = -1;
-
-    /** {@hide} */
-    public class EnterpriseField {
-        private String varName;
-        private String value;
-
-        private EnterpriseField(String varName) {
-            this.varName = varName;
-            this.value = null;
-        }
-
-        public void setValue(String value) {
-            this.value = value;
-        }
-
-        public String varName() {
-            return varName;
-        }
-
-        public String value() {
-            return value;
-        }
-    }
-
-    /** {@hide} */
-    public EnterpriseField eap = new EnterpriseField("eap");
-    /** {@hide} */
-    public EnterpriseField phase2 = new EnterpriseField("phase2");
-    /** {@hide} */
-    public EnterpriseField identity = new EnterpriseField("identity");
-    /** {@hide} */
-    public EnterpriseField anonymous_identity = new EnterpriseField("anonymous_identity");
-    /** {@hide} */
-    public EnterpriseField password = new EnterpriseField("password");
-    /** {@hide} */
-    public EnterpriseField client_cert = new EnterpriseField("client_cert");
-    /** {@hide} */
-    public EnterpriseField engine = new EnterpriseField("engine");
-    /** {@hide} */
-    public EnterpriseField engine_id = new EnterpriseField("engine_id");
-    /** {@hide} */
-    public EnterpriseField key_id = new EnterpriseField("key_id");
-    /** {@hide} */
-    public EnterpriseField ca_cert = new EnterpriseField("ca_cert");
-
-    /** {@hide} */
-    public EnterpriseField[] enterpriseFields = {
-            eap, phase2, identity, anonymous_identity, password, client_cert,
-            engine, engine_id, key_id, ca_cert };
-
     /**
      * Recognized key management schemes.
      */
@@ -357,6 +274,12 @@
      * Defaults to CCMP TKIP WEP104 WEP40.
      */
     public BitSet allowedGroupCiphers;
+    /**
+     * The enterprise configuration details specifying the EAP method,
+     * certificates and other settings associated with the EAP.
+     * @hide
+     */
+    public WifiEnterpriseConfig enterpriseConfig;
 
     /**
      * @hide
@@ -412,11 +335,10 @@
         allowedPairwiseCiphers = new BitSet();
         allowedGroupCiphers = new BitSet();
         wepKeys = new String[4];
-        for (int i = 0; i < wepKeys.length; i++)
+        for (int i = 0; i < wepKeys.length; i++) {
             wepKeys[i] = null;
-        for (EnterpriseField field : enterpriseFields) {
-            field.setValue(null);
         }
+        enterpriseConfig = new WifiEnterpriseConfig();
         ipAssignment = IpAssignment.UNASSIGNED;
         proxySettings = ProxySettings.UNASSIGNED;
         linkProperties = new LinkProperties();
@@ -496,12 +418,9 @@
             sbuf.append('*');
         }
 
-        for (EnterpriseField field : enterpriseFields) {
-            sbuf.append('\n').append(" " + field.varName() + ": ");
-            String value = field.value();
-            if (value != null) sbuf.append(value);
-        }
+        sbuf.append(enterpriseConfig);
         sbuf.append('\n');
+
         sbuf.append("IP assignment: " + ipAssignment.toString());
         sbuf.append("\n");
         sbuf.append("Proxy settings: " + proxySettings.toString());
@@ -545,12 +464,54 @@
         return SSID;
     }
 
+    /**
+     * Get an identifier for associating credentials with this config
+     * @param current configuration contains values for additional fields
+     *                that are not part of this configuration. Used
+     *                when a config with some fields is passed by an application.
+     * @throws IllegalStateException if config is invalid for key id generation
+     * @hide
+     */
+    String getKeyIdForCredentials(WifiConfiguration current) {
+        String keyMgmt = null;
+
+        try {
+            // Get current config details for fields that are not initialized
+            if (TextUtils.isEmpty(SSID)) SSID = current.SSID;
+            if (allowedKeyManagement.cardinality() == 0) {
+                allowedKeyManagement = current.allowedKeyManagement;
+            }
+            if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
+                keyMgmt = KeyMgmt.strings[KeyMgmt.WPA_EAP];
+            }
+            if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+                keyMgmt += KeyMgmt.strings[KeyMgmt.IEEE8021X];
+            }
+
+            if (TextUtils.isEmpty(keyMgmt)) {
+                throw new IllegalStateException("Not an EAP network");
+            }
+
+            return trimStringForKeyId(SSID) + "_" + keyMgmt + "_" +
+                    trimStringForKeyId(enterpriseConfig.getKeyId(current != null ?
+                            current.enterpriseConfig : null));
+        } catch (NullPointerException e) {
+            throw new IllegalStateException("Invalid config details");
+        }
+    }
+
+    private String trimStringForKeyId(String string) {
+        // Remove quotes and spaces
+        return string.replace("\"", "").replace(" ", "");
+    }
+
     private static BitSet readBitSet(Parcel src) {
         int cardinality = src.readInt();
 
         BitSet set = new BitSet();
-        for (int i = 0; i < cardinality; i++)
+        for (int i = 0; i < cardinality; i++) {
             set.set(src.readInt());
+        }
 
         return set;
     }
@@ -560,12 +521,16 @@
 
         dest.writeInt(set.cardinality());
 
-        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1)
+        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
             dest.writeInt(nextSetBit);
+        }
     }
 
     /** @hide */
     public int getAuthType() {
+        if (allowedKeyManagement.cardinality() > 1) {
+            throw new IllegalStateException("More than one auth type set");
+        }
         if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
             return KeyMgmt.WPA_PSK;
         } else if (allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
@@ -594,8 +559,9 @@
             preSharedKey = source.preSharedKey;
 
             wepKeys = new String[4];
-            for (int i = 0; i < wepKeys.length; i++)
+            for (int i = 0; i < wepKeys.length; i++) {
                 wepKeys[i] = source.wepKeys[i];
+            }
 
             wepTxKeyIndex = source.wepTxKeyIndex;
             priority = source.priority;
@@ -606,9 +572,8 @@
             allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
             allowedGroupCiphers    = (BitSet) source.allowedGroupCiphers.clone();
 
-            for (int i = 0; i < source.enterpriseFields.length; i++) {
-                enterpriseFields[i].setValue(source.enterpriseFields[i].value());
-            }
+            enterpriseConfig = new WifiEnterpriseConfig(source.enterpriseConfig);
+
             ipAssignment = source.ipAssignment;
             proxySettings = source.proxySettings;
             linkProperties = new LinkProperties(source.linkProperties);
@@ -623,8 +588,9 @@
         dest.writeString(SSID);
         dest.writeString(BSSID);
         dest.writeString(preSharedKey);
-        for (String wepKey : wepKeys)
+        for (String wepKey : wepKeys) {
             dest.writeString(wepKey);
+        }
         dest.writeInt(wepTxKeyIndex);
         dest.writeInt(priority);
         dest.writeInt(hiddenSSID ? 1 : 0);
@@ -635,9 +601,8 @@
         writeBitSet(dest, allowedPairwiseCiphers);
         writeBitSet(dest, allowedGroupCiphers);
 
-        for (EnterpriseField field : enterpriseFields) {
-            dest.writeString(field.value());
-        }
+        dest.writeParcelable(enterpriseConfig, flags);
+
         dest.writeString(ipAssignment.name());
         dest.writeString(proxySettings.name());
         dest.writeParcelable(linkProperties, flags);
@@ -654,8 +619,9 @@
                 config.SSID = in.readString();
                 config.BSSID = in.readString();
                 config.preSharedKey = in.readString();
-                for (int i = 0; i < config.wepKeys.length; i++)
+                for (int i = 0; i < config.wepKeys.length; i++) {
                     config.wepKeys[i] = in.readString();
+                }
                 config.wepTxKeyIndex = in.readInt();
                 config.priority = in.readInt();
                 config.hiddenSSID = in.readInt() != 0;
@@ -665,13 +631,12 @@
                 config.allowedPairwiseCiphers = readBitSet(in);
                 config.allowedGroupCiphers    = readBitSet(in);
 
-                for (EnterpriseField field : config.enterpriseFields) {
-                    field.setValue(in.readString());
-                }
+                config.enterpriseConfig = in.readParcelable(null);
 
                 config.ipAssignment = IpAssignment.valueOf(in.readString());
                 config.proxySettings = ProxySettings.valueOf(in.readString());
                 config.linkProperties = in.readParcelable(null);
+
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl b/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl
new file mode 100644
index 0000000..b0f5f84
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+parcelable WifiEnterpriseConfig;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
new file mode 100644
index 0000000..7313e7e
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -0,0 +1,666 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.security.Credentials;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.org.bouncycastle.asn1.ASN1Sequence;
+import com.android.org.bouncycastle.asn1.DEROctetString;
+import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Enterprise configuration details for Wi-Fi @hide */
+public class WifiEnterpriseConfig implements Parcelable {
+    private static final String TAG = "WifiEnterpriseConfig";
+    /**
+     * In old configurations, the "private_key" field was used. However, newer
+     * configurations use the key_id field with the engine_id set to "keystore".
+     * If this field is found in the configuration, the migration code is
+     * triggered.
+     */
+    private static final String OLD_PRIVATE_KEY_NAME = "private_key";
+
+    /**
+     * String representing the keystore OpenSSL ENGINE's ID.
+     */
+    private static final String ENGINE_ID_KEYSTORE = "keystore";
+
+    /**
+     * String representing the keystore URI used for wpa_supplicant.
+     */
+    private static final String KEYSTORE_URI = "keystore://";
+
+    /**
+     * String to set the engine value to when it should be enabled.
+     */
+    private static final String ENGINE_ENABLE = "1";
+
+    /**
+     * String to set the engine value to when it should be disabled.
+     */
+    private static final String ENGINE_DISABLE = "0";
+
+    private static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
+    private static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + Credentials.USER_CERTIFICATE;
+
+    private static final String EAP_KEY             = "eap";
+    private static final String PHASE2_KEY          = "phase2";
+    private static final String IDENTITY_KEY        = "identity";
+    private static final String ANON_IDENTITY_KEY   = "anonymous_identity";
+    private static final String PASSWORD_KEY        = "password";
+    private static final String CLIENT_CERT_KEY     = "client_cert";
+    private static final String CA_CERT_KEY         = "ca_cert";
+    private static final String SUBJECT_MATCH_KEY   = "subject_match";
+    private static final String ENGINE_KEY          = "engine";
+    private static final String ENGINE_ID_KEY       = "engine_id";
+    private static final String PRIVATE_KEY_ID_KEY  = "key_id";
+
+    private HashMap<String, String> mFields = new HashMap<String, String>();
+    private X509Certificate mCaCert;
+    private PrivateKey mClientPrivateKey;
+    private X509Certificate mClientCertificate;
+
+    /** This represents an empty value of an enterprise field.
+     * NULL is used at wpa_supplicant to indicate an empty value
+     */
+    static final String EMPTY_VALUE = "NULL";
+
+    public WifiEnterpriseConfig() {
+        // Do not set defaults so that the enterprise fields that are not changed
+        // by API are not changed underneath
+        // This is essential because an app may not have all fields like password
+        // available. It allows modification of subset of fields.
+
+    }
+
+    /** Copy constructor */
+    public WifiEnterpriseConfig(WifiEnterpriseConfig source) {
+        for (String key : source.mFields.keySet()) {
+            mFields.put(key, source.mFields.get(key));
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mFields.size());
+        for (Map.Entry<String, String> entry : mFields.entrySet()) {
+            dest.writeString(entry.getKey());
+            dest.writeString(entry.getValue());
+        }
+
+        writeCertificate(dest, mCaCert);
+
+        if (mClientPrivateKey != null) {
+            String algorithm = mClientPrivateKey.getAlgorithm();
+            byte[] userKeyBytes = mClientPrivateKey.getEncoded();
+            dest.writeInt(userKeyBytes.length);
+            dest.writeByteArray(userKeyBytes);
+            dest.writeString(algorithm);
+        } else {
+            dest.writeInt(0);
+        }
+
+        writeCertificate(dest, mClientCertificate);
+    }
+
+    private void writeCertificate(Parcel dest, X509Certificate cert) {
+        if (cert != null) {
+            try {
+                byte[] certBytes = cert.getEncoded();
+                dest.writeInt(certBytes.length);
+                dest.writeByteArray(certBytes);
+            } catch (CertificateEncodingException e) {
+                dest.writeInt(0);
+            }
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
+    public static final Creator<WifiEnterpriseConfig> CREATOR =
+            new Creator<WifiEnterpriseConfig>() {
+                public WifiEnterpriseConfig createFromParcel(Parcel in) {
+                    WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+                    int count = in.readInt();
+                    for (int i = 0; i < count; i++) {
+                        String key = in.readString();
+                        String value = in.readString();
+                        enterpriseConfig.mFields.put(key, value);
+                    }
+
+                    enterpriseConfig.mCaCert = readCertificate(in);
+
+                    PrivateKey userKey = null;
+                    int len = in.readInt();
+                    if (len > 0) {
+                        try {
+                            byte[] bytes = new byte[len];
+                            in.readByteArray(bytes);
+                            String algorithm = in.readString();
+                            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+                            userKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
+                        } catch (NoSuchAlgorithmException e) {
+                            userKey = null;
+                        } catch (InvalidKeySpecException e) {
+                            userKey = null;
+                        }
+                    }
+
+                    enterpriseConfig.mClientPrivateKey = userKey;
+                    enterpriseConfig.mClientCertificate = readCertificate(in);
+                    return enterpriseConfig;
+                }
+
+                private X509Certificate readCertificate(Parcel in) {
+                    X509Certificate cert = null;
+                    int len = in.readInt();
+                    if (len > 0) {
+                        try {
+                            byte[] bytes = new byte[len];
+                            in.readByteArray(bytes);
+                            CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
+                            cert = (X509Certificate) cFactory
+                                    .generateCertificate(new ByteArrayInputStream(bytes));
+                        } catch (CertificateException e) {
+                            cert = null;
+                        }
+                    }
+                    return cert;
+                }
+
+                public WifiEnterpriseConfig[] newArray(int size) {
+                    return new WifiEnterpriseConfig[size];
+                }
+            };
+
+    public static final class Eap {
+        /* NONE represents an empty enterprise config */
+        public static final int NONE    = -1;
+        public static final int PEAP    = 0;
+        public static final int TLS     = 1;
+        public static final int TTLS    = 2;
+        public static final int PWD     = 3;
+        /** @hide */
+        public static final String[] strings = { "PEAP", "TLS", "TTLS", "PWD" };
+    }
+
+    public static final class Phase2 {
+        public static final int NONE        = 0;
+        public static final int PAP         = 1;
+        public static final int MSCHAP      = 2;
+        public static final int MSCHAPV2    = 3;
+        public static final int GTC         = 4;
+        private static final String PREFIX = "auth=";
+        /** @hide */
+        public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP", "MSCHAPV2", "GTC" };
+    }
+
+    /** Internal use only */
+    HashMap<String, String> getFields() {
+        return mFields;
+    }
+
+    /** Internal use only */
+    static String[] getSupplicantKeys() {
+        return new String[] { EAP_KEY, PHASE2_KEY, IDENTITY_KEY, ANON_IDENTITY_KEY, PASSWORD_KEY,
+                CLIENT_CERT_KEY, CA_CERT_KEY, SUBJECT_MATCH_KEY, ENGINE_KEY, ENGINE_ID_KEY,
+                PRIVATE_KEY_ID_KEY };
+    }
+
+    /**
+     * Set the EAP authentication method.
+     * @param  eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or
+     *                   {@link Eap#PWD}
+     */
+    public void setEapMethod(int eapMethod) {
+        switch (eapMethod) {
+            /** Valid methods */
+            case Eap.PEAP:
+            case Eap.PWD:
+            case Eap.TLS:
+            case Eap.TTLS:
+                mFields.put(EAP_KEY, Eap.strings[eapMethod]);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown EAP method");
+        }
+    }
+
+    /**
+     * Get the eap method.
+     * @return eap method configured
+     */
+    public int getEapMethod() {
+        String eapMethod  = mFields.get(EAP_KEY);
+        return getStringIndex(Eap.strings, eapMethod, Eap.NONE);
+    }
+
+    /**
+     * Set Phase 2 authentication method. Sets the inner authentication method to be used in
+     * phase 2 after setting up a secure channel
+     * @param phase2Method is the inner authentication method and can be one of {@link Phase2#NONE},
+     *                     {@link Phase2#PAP}, {@link Phase2#MSCHAP}, {@link Phase2#MSCHAPV2},
+     *                     {@link Phase2#GTC}
+     *
+     */
+    public void setPhase2Method(int phase2Method) {
+        switch (phase2Method) {
+            case Phase2.NONE:
+                mFields.put(PHASE2_KEY, EMPTY_VALUE);
+                break;
+            /** Valid methods */
+            case Phase2.PAP:
+            case Phase2.MSCHAP:
+            case Phase2.MSCHAPV2:
+            case Phase2.GTC:
+                mFields.put(PHASE2_KEY, convertToQuotedString(
+                        Phase2.PREFIX + Phase2.strings[phase2Method]));
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown Phase 2 method");
+        }
+    }
+
+    /**
+     * Get the phase 2 authentication method.
+     * @return a phase 2 method defined at {@link Phase2}
+     * */
+    public int getPhase2Method() {
+        String phase2Method = removeDoubleQuotes(mFields.get(PHASE2_KEY));
+        // Remove auth= prefix
+        if (phase2Method.startsWith(Phase2.PREFIX)) {
+            phase2Method = phase2Method.substring(Phase2.PREFIX.length());
+        }
+        return getStringIndex(Phase2.strings, phase2Method, Phase2.NONE);
+    }
+
+    /**
+     * Set the identity
+     * @param identity
+     */
+    public void setIdentity(String identity) {
+        setFieldValue(IDENTITY_KEY, identity, "");
+    }
+
+    /**
+     * Get the identity
+     * @return the identity
+     */
+    public String getIdentity() {
+        return getFieldValue(IDENTITY_KEY, "");
+    }
+
+    /**
+     * Set anonymous identity. This is used as the unencrypted identity with
+     * certain EAP types
+     * @param anonymousIdentity the anonymous identity
+     */
+    public void setAnonymousIdentity(String anonymousIdentity) {
+        setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity, "");
+    }
+
+    /** Get the anonymous identity
+     * @return anonymous identity
+     */
+    public String getAnonymousIdentity() {
+        return getFieldValue(ANON_IDENTITY_KEY, "");
+    }
+
+    /**
+     * Set the password.
+     * @param password the password
+     */
+    public void setPassword(String password) {
+        setFieldValue(PASSWORD_KEY, password, "");
+    }
+
+    /**
+     * Set CA certificate alias.
+     *
+     * <p> See the {@link android.security.KeyChain} for details on installing or choosing
+     * a certificate
+     * </p>
+     * @param alias identifies the certificate
+     * @hide
+     */
+    public void setCaCertificateAlias(String alias) {
+        setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX);
+    }
+
+    /**
+     * Get CA certificate alias
+     * @return alias to the CA certificate
+     * @hide
+     */
+    public String getCaCertificateAlias() {
+        return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
+    }
+
+    /**
+     * Specify a X.509 certificate that identifies the server.
+     *
+     * <p>A default name is automatically assigned to the certificate and used
+     * with this configuration.
+     * @param cert X.509 CA certificate
+     * @throws IllegalArgumentException if not a CA certificate
+     */
+    public void setCaCertificate(X509Certificate cert) {
+        if (cert.getBasicConstraints() >= 0) {
+            mCaCert = cert;
+        } else {
+            throw new IllegalArgumentException("Not a CA certificate");
+        }
+    }
+
+    /**
+     * Set Client certificate alias.
+     *
+     * <p> See the {@link android.security.KeyChain} for details on installing or choosing
+     * a certificate
+     * </p>
+     * @param alias identifies the certificate
+     * @hide
+     */
+    public void setClientCertificateAlias(String alias) {
+        setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
+        setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY);
+        // Also, set engine parameters
+        if (TextUtils.isEmpty(alias)) {
+            mFields.put(ENGINE_KEY, ENGINE_DISABLE);
+            mFields.put(ENGINE_ID_KEY, EMPTY_VALUE);
+        } else {
+            mFields.put(ENGINE_KEY, ENGINE_ENABLE);
+            mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE));
+        }
+    }
+
+    /**
+     * Get client certificate alias
+     * @return alias to the client certificate
+     * @hide
+     */
+    public String getClientCertificateAlias() {
+        return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
+    }
+
+    /**
+     * Specify a private key and client certificate for client authorization.
+     *
+     * <p>A default name is automatically assigned to the key entry and used
+     * with this configuration.
+     * @param privateKey
+     * @param clientCertificate
+     */
+    public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
+        if (clientCertificate != null) {
+            if (clientCertificate.getBasicConstraints() != -1) {
+                throw new IllegalArgumentException("Cannot be a CA certificate");
+            }
+            if (privateKey == null) {
+                throw new IllegalArgumentException("Client cert without a private key");
+            }
+            if (privateKey.getEncoded() == null) {
+                throw new IllegalArgumentException("Private key cannot be encoded");
+            }
+        }
+
+        mClientPrivateKey = privateKey;
+        mClientCertificate = clientCertificate;
+    }
+
+    boolean needsKeyStore() {
+        // Has no keys to be installed
+        if (mClientCertificate == null && mCaCert == null) return false;
+        return true;
+    }
+
+    boolean installKeys(android.security.KeyStore keyStore, String name) {
+        boolean ret = true;
+        String privKeyName = Credentials.USER_PRIVATE_KEY + name;
+        String userCertName = Credentials.USER_CERTIFICATE + name;
+        String caCertName = Credentials.CA_CERTIFICATE + name;
+        if (mClientCertificate != null) {
+            byte[] privKeyData = mClientPrivateKey.getEncoded();
+            ret = keyStore.importKey(privKeyName, privKeyData);
+            if (ret == false) {
+                return ret;
+            }
+
+            ret = putCertInKeyStore(keyStore, userCertName, mClientCertificate);
+            if (ret == false) {
+                // Remove private key installed
+                keyStore.delKey(privKeyName);
+                return ret;
+            }
+        }
+
+        if (mCaCert != null) {
+            ret = putCertInKeyStore(keyStore, caCertName, mCaCert);
+            if (ret == false) {
+                if (mClientCertificate != null) {
+                    // Remove client key+cert
+                    keyStore.delKey(privKeyName);
+                    keyStore.delete(userCertName);
+                }
+                return ret;
+            }
+        }
+
+        // Set alias names
+        if (mClientCertificate != null) {
+            setClientCertificateAlias(name);
+            mClientPrivateKey = null;
+            mClientCertificate = null;
+        }
+
+        if (mCaCert != null) {
+            setCaCertificateAlias(name);
+            mCaCert = null;
+        }
+
+        return ret;
+    }
+
+    private boolean putCertInKeyStore(android.security.KeyStore keyStore, String name,
+            Certificate cert) {
+        try {
+            byte[] certData = Credentials.convertToPem(cert);
+            return keyStore.put(name, certData);
+        } catch (IOException e1) {
+            return false;
+        } catch (CertificateException e2) {
+            return false;
+        }
+    }
+
+    void removeKeys(android.security.KeyStore keyStore) {
+        String client = getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
+        // a valid client certificate is configured
+        if (!TextUtils.isEmpty(client)) {
+            keyStore.delKey(Credentials.USER_PRIVATE_KEY + client);
+            keyStore.delete(Credentials.USER_CERTIFICATE + client);
+        }
+
+        String ca = getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
+        // a valid ca certificate is configured
+        if (!TextUtils.isEmpty(ca)) {
+            keyStore.delete(Credentials.CA_CERTIFICATE + ca);
+        }
+    }
+
+    /**
+     * Set subject match. This is the substring to be matched against the subject of the
+     * authentication server certificate.
+     * @param subjectMatch substring to be matched
+     */
+    public void setSubjectMatch(String subjectMatch) {
+        setFieldValue(SUBJECT_MATCH_KEY, subjectMatch, "");
+    }
+
+    /**
+     * Get subject match
+     * @return the subject match string
+     */
+    public String getSubjectMatch() {
+        return getFieldValue(SUBJECT_MATCH_KEY, "");
+    }
+
+    /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
+    String getKeyId(WifiEnterpriseConfig current) {
+        String eap = mFields.get(EAP_KEY);
+        String phase2 = mFields.get(PHASE2_KEY);
+
+        // If either eap or phase2 are not initialized, use current config details
+        if (TextUtils.isEmpty((eap))) {
+            eap = current.mFields.get(EAP_KEY);
+        }
+        if (TextUtils.isEmpty(phase2)) {
+            phase2 = current.mFields.get(PHASE2_KEY);
+        }
+        return eap + "_" + phase2;
+    }
+
+    /** Migrates the old style TLS config to the new config style. This should only be used
+     * when restoring an old wpa_supplicant.conf or upgrading from a previous
+     * platform version.
+     * @return true if the config was updated
+     * @hide
+     */
+    boolean migrateOldEapTlsNative(WifiNative wifiNative, int netId) {
+        String oldPrivateKey = wifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME);
+        /*
+         * If the old configuration value is not present, then there is nothing
+         * to do.
+         */
+        if (TextUtils.isEmpty(oldPrivateKey)) {
+            return false;
+        } else {
+            // Also ignore it if it's empty quotes.
+            oldPrivateKey = removeDoubleQuotes(oldPrivateKey);
+            if (TextUtils.isEmpty(oldPrivateKey)) {
+                return false;
+            }
+        }
+
+        mFields.put(ENGINE_KEY, ENGINE_ENABLE);
+        mFields.put(ENGINE_ID_KEY, convertToQuotedString(ENGINE_ID_KEYSTORE));
+
+        /*
+        * The old key started with the keystore:// URI prefix, but we don't
+        * need that anymore. Trim it off if it exists.
+        */
+        final String keyName;
+        if (oldPrivateKey.startsWith(KEYSTORE_URI)) {
+            keyName = new String(oldPrivateKey.substring(KEYSTORE_URI.length()));
+        } else {
+            keyName = oldPrivateKey;
+        }
+        mFields.put(PRIVATE_KEY_ID_KEY, convertToQuotedString(keyName));
+
+        wifiNative.setNetworkVariable(netId, ENGINE_KEY, mFields.get(ENGINE_KEY));
+        wifiNative.setNetworkVariable(netId, ENGINE_ID_KEY, mFields.get(ENGINE_ID_KEY));
+        wifiNative.setNetworkVariable(netId, PRIVATE_KEY_ID_KEY, mFields.get(PRIVATE_KEY_ID_KEY));
+        // Remove old private_key string so we don't run this again.
+        wifiNative.setNetworkVariable(netId, OLD_PRIVATE_KEY_NAME, EMPTY_VALUE);
+        return true;
+    }
+
+    private String removeDoubleQuotes(String string) {
+        int length = string.length();
+        if ((length > 1) && (string.charAt(0) == '"')
+                && (string.charAt(length - 1) == '"')) {
+            return string.substring(1, length - 1);
+        }
+        return string;
+    }
+
+    private String convertToQuotedString(String string) {
+        return "\"" + string + "\"";
+    }
+
+    /** Returns the index at which the toBeFound string is found in the array.
+     * @param arr array of strings
+     * @param toBeFound string to be found
+     * @param defaultIndex default index to be returned when string is not found
+     * @return the index into array
+     */
+    private int getStringIndex(String arr[], String toBeFound, int defaultIndex) {
+        if (TextUtils.isEmpty(toBeFound)) return defaultIndex;
+        for (int i = 0; i < arr.length; i++) {
+            if (toBeFound.equals(arr[i])) return i;
+        }
+        return defaultIndex;
+    }
+
+    /** Returns the field value for the key.
+     * @param key into the hash
+     * @param prefix is the prefix that the value may have
+     * @return value
+     */
+    private String getFieldValue(String key, String prefix) {
+        String value = mFields.get(key);
+        // Uninitialized or known to be empty after reading from supplicant
+        if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
+        return removeDoubleQuotes(value).substring(prefix.length());
+    }
+
+    /** Set a value with an optional prefix at key
+     * @param key into the hash
+     * @param value to be set
+     * @param prefix an optional value to be prefixed to actual value
+     */
+    private void setFieldValue(String key, String value, String prefix) {
+        if (TextUtils.isEmpty(value)) {
+            mFields.put(key, EMPTY_VALUE);
+        } else {
+            mFields.put(key, convertToQuotedString(prefix + value));
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        for (String key : mFields.keySet()) {
+            sb.append(key).append(" ").append(mFields.get(key)).append("\n");
+        }
+        return sb.toString();
+    }
+}